"TOMOYO Linux" is our work in the field of security enhanced Linux.
This is the second proposal of TOMOYO Linux.
When we posted our first proposal to LKML, TOMOYO Linux's MAC was
limited to file access control. Now TOMOYO Linux has access control
functionality not only for files but also for networking, signal
transmission and namespace manipulation and we got the source code
cleaned-up.
Patches consist of three types.
* [TOMOYO 01/15]: Mandatory modifications against standard kernel.
* [TOMOYO 02-14/15]: LSM implementation of TOMOYO Linux.
* [TOMOYO 15/15]: Optional modifications against standard kernel.
<<What you can do with TOMOYO Linux.>>
The fundamental concept of TOMOYO Linux is "tracking process
invocation history".
The "struct task_struct"->security member holds a pointer to the
"process invocation history". Thus, every process (the kernel,
/sbin/init process and any children/descendant of /sbin/init) knows
its "process invocation history" (or ancestors). Since every process
knows its ancestors, TOMOYO Linux can enforce access control over all
processes.
TOMOYO Linux splits domains using "process invocation history" and the
process transits to a different domain whenever execution of a program
(i.e. do_execve()) is requested. By transiting to a different domain
whenever execution of a program is requested, each domain will have
the minimal permissions that are essential for processes in that
domain to do their roles.
You don't need to define domains beforehand. TOMOYO Linux kernel will
automatically define new domains whenever execution of a program is
requested, and the process will automatically transit to the new
domain. (If the process's domain is in enforcing mode, TOMOYO Linux
kernel will not define new domains to avoid memory consumption attack.)
TOMOYO Linux can restrict the following requests on a per-a-domain basis:
* opening files
* communicating via PF_INET sockets
* sending signals
TOMOYO Linux can also restrict the following namespace manipulation requests.
* mounting filesystems
* unmounting filesystems
* using pivot_root
<<Example>>
The tree below shows a part of domain transitions generated using
Debian Etch. The domain a process is in is determined based on the
process's "process invocation history". Each domain has permissions
(or ACL), and the behavior (or requests shown above) of a process is
restricted by the ACL of the domain that the process is in.
<kernel>
+ /sbin/init
+ /etc/init.d/rc
+ /etc/init.d/apache2
+ /usr/bin/env
+ /usr/sbin/apache2ctl
+ /usr/sbin/apache2
You can assign different access control modes (or profiles) on a
per-a-domain basis. Thus, you can enforce access control partially or
entirely. Also, by assigning "learning mode" to a domain, ACL that are
requested by processes in that domain are automatically (i.e. in
real-time) accumulated. The following ACL are an excerpt from
/usr/sbin/apache2 domain generated using "learning mode".
4 /etc/apache2/apache2.conf
4 /var/www/apache2-default/index.html
allow_create /var/run/apache2.pid
allow_unlink /var/run/apache2.pid
allow_network TCP bind 192.168.1.135 80
allow_network TCP listen 192.168.1.135 80
allow_network TCP accept 192.168.1.1 2389
The above ACL allows Apache to do the following behavior.
* Opening /etc/apache2/apache2.conf and
/var/www/apache2-default/index.html for reading.
* Creating and deleting /var/run/apache2.pid .
* Binding to local address (IP = 192.168.1.135, port = 80).
* Listening at local address (IP = 192.168.1.135, port = 80).
* Accepting from remote address (IP = 192.168.1.1, port = 2389).
You may use wildcards for pathnames, ranges for IP addresses and port
numbers, groups of pathnames and IP addresses for flexible definition.
<<Features of TOMOYO Linux.>>
TOMOYO Linux is an implementation of MAC, but you can use TOMOYO Linux
not only for MAC, but also to analyze a system's behavior, since
TOMOYO Linux can accumulate access requests raised by applications
sorted by each "process invocation history".
TOMOYO Linux and AppArmor are alike from the point of view of pathname
based access control, but TOMOYO Linux has differences in the
following points:
* TOMOYO Linux can apply access control over the whole process
(from the execution of /sbin/init at the startup procedure,
till the power failure at the shutdown procedure).
* TOMOYO Linux can apply access control not only over files but
also over networking, signals, namespace manipulations.
* TOMOYO Linux can accumulate ACL in real-time using
"learning mode".
* TOMOYO Linux allows the administrator to switch the access
control mode on a per-domain and per-functionality basis.
* TOMOYO Linux allows the administrator to judge (grant/reject)
requests which that violated ACL manually while operating in
"enforcing mode" without once rejecting these requests.
* TOMOYO Linux supports conditional ACL (e.g. owner of
process/files etc.).
Documents about installing and experiencing TOMOYO Linux are available
at http://tomoyo.sourceforge.jp/en/2.1.x/ . Please try TOMOYO Linux.
Feedbacks are most welcome.
<<Related links>>
OLS BoF material: http://sourceforge.jp/projects/tomoyo/document/ols2007-tomoyo-20070629.pdf
Previous submission: http://lkml.org/lkml/2007/6/13/58 , http://lkml.org/lkml/2007/6/14/55
Kentaro Takeda
NTT DATA CORPORATION
TOMOYO Linux uses pathnames for auditing and controlling file access.
Therefore, namespace_sem is needed.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
fs/namespace.c | 2 +-
include/linux/mnt_namespace.h | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
--- linux-2.6.orig/fs/namespace.c 2007-08-23 21:25:13.000000000 +0900
+++ linux-2.6/fs/namespace.c 2007-08-24 15:51:34.000000000 +0900
@@ -38,7 +38,7 @@ static int event;
static struct list_head *mount_hashtable __read_mostly;
static int hash_mask __read_mostly, hash_bits __read_mostly;
static struct kmem_cache *mnt_cache __read_mostly;
-static struct rw_semaphore namespace_sem;
+struct rw_semaphore namespace_sem;
/* /sys/fs */
decl_subsys(fs, NULL, NULL);
--- linux-2.6.orig/include/linux/mnt_namespace.h 2007-08-23 21:25:13.000000000 +0900
+++ linux-2.6/include/linux/mnt_namespace.h 2007-08-24 15:51:34.000000000 +0900
@@ -6,6 +6,8 @@
#include <linux/sched.h>
#include <linux/nsproxy.h>
+extern struct rw_semaphore namespace_sem;
+
struct mnt_namespace {
atomic_t count;
struct vfsmount * root;
Kconfig and Makefile for TOMOYO Linux.
TOMOYO Linux is placed in security/tomoyo .
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/Kconfig | 1 +
security/Makefile | 1 +
security/tomoyo/Kconfig | 18 ++++++++++++++++++
security/tomoyo/Makefile | 3 +++
4 files changed, 23 insertions(+)
--- linux-2.6.orig/security/Kconfig 2007-08-23 21:25:12.000000000 +0900
+++ linux-2.6/security/Kconfig 2007-08-24 15:51:34.000000000 +0900
@@ -94,6 +94,7 @@ config SECURITY_ROOTPLUG
If you are unsure how to answer this question, answer N.
source security/selinux/Kconfig
+source security/tomoyo/Kconfig
endmenu
--- linux-2.6.orig/security/Makefile 2007-08-23 21:25:12.000000000 +0900
+++ linux-2.6/security/Makefile 2007-08-24 15:51:34.000000000 +0900
@@ -16,3 +16,4 @@ obj-$(CONFIG_SECURITY) += security.o d
obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o
obj-$(CONFIG_SECURITY_CAPABILITIES) += commoncap.o capability.o
obj-$(CONFIG_SECURITY_ROOTPLUG) += commoncap.o root_plug.o
+obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/
\ No newline at end of file
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/Kconfig 2007-08-24 15:51:34.000000000 +0900
@@ -0,0 +1,18 @@
+config SECURITY_TOMOYO
+ bool "TOMOYO Linux support"
+ depends on SECURITY
+ select SECURITY_NETWORK
+ select AUDIT
+ default n
+ help
+ This selects TOMOYO Linux.
+
+ TOMOYO Linux is a domain-based access control method using LSM.
+ If you answer Y, you will need a policy loader program
+ (/sbin/tomoyo-init) and some configuration files.
+ You can get them from
+ <http://tomoyo.sourceforge.jp/en/2.1.x/>
+
+ TOMOYO Linux is also applicable to figuring out the behavior
+ of your system, for TOMOYO uses the canonicalized absolute
+ pathnames and TreeView style domain transitions.
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/Makefile 2007-08-24 15:51:34.000000000 +0900
@@ -0,0 +1,3 @@
+obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo.o tomoyo_func.o
+tomoyo_func-objs := domain.o common.o realpath.o audit.o file.o exec.o net.o mount.o signal.o condition.o
+EXTRA_CFLAGS += -Isecurity/tomoyo/include
Data structures and prototype defitions for TOMOYO Linux.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/include/realpath.h | 44 +++
security/tomoyo/include/tomoyo.h | 516 +++++++++++++++++++++++++++++++++++++
2 files changed, 560 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/include/realpath.h 2007-08-24 15:51:34.000000000 +0900
@@ -0,0 +1,44 @@
+/*
+ * security/tomoyo/include/realpath.h
+ *
+ * Get the canonicalized absolute pathnames.
+ * The basis for TOMOYO.
+ */
+
+#ifndef _TMY_REALPATH_H
+#define _TMY_REALPATH_H
+
+struct path_info;
+
+/* Returns realpath(3) of the given pathname but ignores chroot'ed root. */
+int tmy_realpath_dentry2(struct dentry *dentry,
+ struct vfsmount *mnt,
+ char *newname,
+ int newname_len);
+
+/* Returns realpath(3) of the given pathname but ignores chroot'ed root. */
+/* These functions use tmy_alloc(), so caller must tmy_free() */
+/* if these functions didn't return NULL. */
+char *tmy_realpath(const char *pathname);
+char *tmy_realpath_nofollow(const char *pathname);
+char *tmy_realpath_dentry(struct dentry *dentry, struct vfsmount *mnt);
+
+/* Allocate memory for structures. */
+/* The RAM is chunked, so NEVER try to kfree() the returned pointer. */
+void *tmy_alloc_element(const unsigned int size);
+
+/* Get used RAM size for tmy_alloc_elements(). */
+unsigned int tmy_get_memory_used_for_elements(void);
+
+/* Keep the given name on the RAM. */
+/* The RAM is shared, so NEVER try to modify or kfree() the returned name. */
+const struct path_info *tmy_save_name(const char *name);
+
+/* Get used RAM size for tmy_save_name(). */
+unsigned int tmy_get_memory_used_for_save_name(void);
+
+unsigned int tmy_get_memory_used_for_dynamic(void);
+char *sysctlpath_from_table(struct ctl_table *table);
+extern void tmy_realpath_init(void);
+
+#endif
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/include/tomoyo.h 2007-08-24 15:51:34.000000000 +0900
@@ -0,0 +1,516 @@
+/*
+ * security/tomoyo/include/tomoyo.h
+ *
+ * Header for TOMOYO Linux.
+ */
+
+#ifndef _TOMOYO_H
+#define _TOMOYO_H
+
+#define TOMOYO_VERSION_CODE "2.1.0-rc3"
+
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/utime.h>
+#include <linux/file.h>
+#include <linux/smp_lock.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+#include <stdarg.h>
+#include <linux/delay.h>
+
+extern struct semaphore domain_acl_lock;
+extern int sbin_init_started;
+
+struct tmy_security {
+ struct domain_info *domain;
+ struct domain_info *prev_domain;
+ u32 flags;
+};
+
+#define TMY_SECURITY ((struct tmy_security *) current->security)
+
+struct path_info {
+ const char *name;
+ u32 hash; /* = full_name_hash(name, strlen(name)) */
+ u16 total_len; /* = strlen(name) */
+ u16 const_len; /* = tmy_const_part_length(name) */
+ u8 is_dir; /* = tmy_strendswith(name, "/") */
+ u8 is_patterned; /* = PathContainsPattern(name) */
+ u16 depth; /* = tmy_path_depth(name) */
+};
+
+#define TMY_MAX_PATHNAME_LEN 4000
+
+struct group_member {
+ struct group_member *next;
+ const struct path_info *member_name;
+ int is_deleted;
+};
+
+struct group_entry {
+ struct group_entry *next;
+ const struct path_info *group_name;
+ struct group_member *first_member;
+};
+
+
+struct mini_stat {
+ uid_t uid;
+ gid_t gid;
+ ino_t ino;
+};
+struct dentry;
+struct vfsmount;
+struct obj_info {
+ u8 validate_done;
+ u8 path1_valid;
+ u8 path1_parent_valid;
+ u8 path2_parent_valid;
+ struct dentry *path1_dentry;
+ struct vfsmount *path1_vfsmnt;
+ struct dentry *path2_dentry;
+ struct vfsmount *path2_vfsmnt;
+ struct mini_stat path1_stat;
+ /* I don't handle path2_stat for rename operation. */
+ struct mini_stat path1_parent_stat;
+ struct mini_stat path2_parent_stat;
+};
+struct condition_list;
+
+/*
+ * TOMOYO uses the following structures.
+ * Memory allocated for these structures are never kfree()ed.
+ * Since no locks are used for reading,
+ * assignment must be performed atomically.
+ */
+
+/************************ The structure for domains. ************************/
+
+struct acl_info {
+ struct acl_info *next;
+ const struct condition_list *cond;
+ u8 type;
+ u8 is_deleted;
+} __attribute__((__packed__));
+
+struct domain_info {
+ struct domain_info *next; /* NULL if none. */
+ struct acl_info *first_acl_ptr; /* NULL if none. */
+ const struct path_info *domainname; /* Never NULL. */
+ u8 profile;
+ u8 is_deleted;
+ u8 quota_warned;
+};
+
+#define TMY_MAX_PROFILES 256
+
+struct file_acl {
+ /* type = TMY_TYPE_FILE_ACL */
+ struct acl_info head;
+ u8 perm;
+ u8 u_is_group;
+ union {
+ const struct path_info *filename;
+ const struct group_entry *group;
+ } u;
+};
+
+struct argv0_acl {
+ /* type = TMY_TYPE_ARGV0_ACL */
+ struct acl_info head;
+ const struct path_info *filename; /* Pointer to single pathname. */
+ const struct path_info *argv0; /* strrchr(argv[0], '/') + 1 */
+};
+
+struct single_acl {
+ /* type = TMY_TYPE_* */
+ struct acl_info head;
+ u8 u_is_group;
+ union {
+ const struct path_info *filename;
+ const struct group_entry *group;
+ } u;
+};
+
+struct double_acl {
+ /* type = TMY_TYPE_RENAME_ACL or TMY_TYPE_LINK_ACL, */
+ struct acl_info head;
+ u8 u1_is_group;
+ u8 u2_is_group;
+ union {
+ const struct path_info *filename1;
+ const struct group_entry *group1;
+ } u1;
+ union {
+ const struct path_info *filename2;
+ const struct group_entry *group2;
+ } u2;
+};
+
+struct addr_group_member {
+ struct addr_group_member *next;
+ union {
+ u32 ipv4; /* Host byte order */
+ u16 ipv6[8]; /* Network byte order */
+ } min, max;
+ u8 is_deleted;
+ u8 is_ipv6;
+};
+
+struct addr_group_entry {
+ struct addr_group_entry *next;
+ const struct path_info *group_name;
+ struct addr_group_member *first_member;
+};
+
+#define TMY_TYPE_ADDRESS_GROUP 0
+#define TMY_TYPE_IPv4 1
+#define TMY_TYPE_IPv6 2
+
+struct net_acl {
+ /* type = TYPE_IP_NETWORK_ACL, */
+ struct acl_info head;
+ u8 operation_type;
+ u8 record_type;
+ union {
+ struct {
+ u32 min;
+ u32 max;
+ } ipv4;
+ struct {
+ u16 min[8];
+ u16 max[8];
+ } ipv6;
+ const struct addr_group_entry *group;
+ } u;
+ u16 min_port; /* Start of port number range. */
+ u16 max_port; /* End of port number range. */
+};
+
+struct signal_acl {
+ /* type = TYPE_SIGNAL_ACL */
+ struct acl_info head;
+ u16 sig;
+ /* Pointer to destination pattern. */
+ const struct path_info *domainname;
+};
+
+/************************* Keywords for ACLs. *************************/
+
+#define TMY_AGGREGATOR "aggregator "
+#define TMY_AGGREGATOR_LEN (sizeof(TMY_AGGREGATOR) - 1)
+#define TMY_ALIAS "alias "
+#define TMY_ALIAS_LEN (sizeof(TMY_ALIAS) - 1)
+#define TMY_ALLOW_READ "allow_read "
+#define TMY_ALLOW_READ_LEN (sizeof(TMY_ALLOW_READ) - 1)
+#define TMY_DELETE "delete "
+#define TMY_DELETE_LEN (sizeof(TMY_DELETE) - 1)
+#define TMY_DENY_REWRITE "deny_rewrite "
+#define TMY_DENY_REWRITE_LEN (sizeof(TMY_DENY_REWRITE) - 1)
+#define TMY_FILE_PATTERN "file_pattern "
+#define TMY_FILE_PATTERN_LEN (sizeof(TMY_FILE_PATTERN) - 1)
+#define TMY_INITIALIZE_DOMAIN "initialize_domain "
+#define TMY_INITIALIZE_DOMAIN_LEN (sizeof(TMY_INITIALIZE_DOMAIN) - 1)
+#define TMY_KEEP_DOMAIN "keep_domain "
+#define TMY_KEEP_DOMAIN_LEN (sizeof(TMY_KEEP_DOMAIN) - 1)
+#define TMY_NO_INITIALIZE_DOMAIN "no_initialize_domain "
+#define TMY_NO_INITIALIZE_DOMAIN_LEN (sizeof(TMY_NO_INITIALIZE_DOMAIN) - 1)
+#define TMY_NO_KEEP_DOMAIN "no_keep_domain "
+#define TMY_NO_KEEP_DOMAIN_LEN (sizeof(TMY_NO_KEEP_DOMAIN) - 1)
+#define TMY_PATH_GROUP "path_group "
+#define TMY_PATH_GROUP_LEN (sizeof(TMY_PATH_GROUP) - 1)
+#define TMY_SELECT "select "
+#define TMY_SELECT_LEN (sizeof(TMY_SELECT) - 1)
+#define TMY_UNDELETE "undelete "
+#define TMY_UNDELETE_LEN (sizeof(TMY_UNDELETE) - 1)
+
+#define TMY_ALLOW_MOUNT "allow_mount "
+#define TMY_ALLOW_MOUNT_LEN (sizeof(TMY_ALLOW_MOUNT) - 1)
+#define TMY_DENY_UNMOUNT "deny_unmount "
+#define TMY_DENY_UNMOUNT_LEN (sizeof(TMY_DENY_UNMOUNT) - 1)
+#define TMY_ALLOW_PIVOT_ROOT "allow_pivot_root "
+#define TMY_ALLOW_PIVOT_ROOT_LEN (sizeof(TMY_ALLOW_PIVOT_ROOT) - 1)
+
+#define TMY_USE_PROFILE "use_profile "
+
+#define TMY_ROOT_NAME "<kernel>"
+#define TMY_ROOT_NAME_LEN (sizeof(TMY_ROOT_NAME) - 1)
+
+#define TMY_ALLOW_ARGV0 "allow_argv0 "
+#define TMY_ALLOW_ARGV0_LEN (sizeof(TMY_ALLOW_ARGV0) - 1)
+
+#define TMY_ADDRESS_GROUP "address_group "
+#define TMY_ADDRESS_GROUP_LEN (sizeof(TMY_ADDRESS_GROUP) - 1)
+#define TMY_ALLOW_NETWORK "allow_network "
+#define TMY_ALLOW_NETWORK_LEN (sizeof(TMY_ALLOW_NETWORK) - 1)
+
+#define TMY_ALLOW_SIGNAL "allow_signal "
+#define TMY_ALLOW_SIGNAL_LEN (sizeof(TMY_ALLOW_SIGNAL) - 1)
+
+/******************** Index numbers for Access Controls. ********************/
+
+#define TMY_COMMENT 0
+#define TMY_MAC_FOR_FILE 1
+#define TMY_MAC_FOR_ARGV0 2
+#define TMY_MAC_FOR_NETWORK 3
+#define TMY_MAC_FOR_SIGNAL 4
+#define TMY_DENY_CONCEAL_MOUNT 5
+#define TMY_RESTRICT_MOUNT 6
+#define TMY_RESTRICT_UMOUNT 7
+#define TMY_RESTRICT_PIVOT_ROOT 8
+#define TMY_MAX_ACCEPT_ENTRY 9
+#define TMY_AUDIT_GRANT 10
+#define TMY_AUDIT_REJECT 11
+#define TMY_ALLOW_ENFORCE_GRACE 12
+#define TMY_MAX_CONTROL_INDEX 13
+
+#define TMY_NETWORK_ACL_UDP_BIND 0
+#define TMY_NETWORK_ACL_UDP_CONNECT 1
+#define TMY_NETWORK_ACL_TCP_BIND 2
+#define TMY_NETWORK_ACL_TCP_LISTEN 3
+#define TMY_NETWORK_ACL_TCP_CONNECT 4
+#define TMY_NETWORK_ACL_TCP_ACCEPT 5
+#define TMY_NETWORK_ACL_RAW_BIND 6
+#define TMY_NETWORK_ACL_RAW_CONNECT 7
+
+/******************** Index numbers for updates counter. ********************/
+
+#define TMY_UPDATE_DOMAINPOLICY 0
+#define TMY_UPDATE_SYSTEMPOLICY 1
+#define TMY_UPDATE_EXCEPTIONPOLICY 2
+#define TMY_UPDATE_PROFILE 3
+#define TMY_UPDATE_QUERY 4
+#define TMY_UPDATE_MANAGER 5
+#define TMY_MAX_UPDATES_COUNTER 6
+
+/*********************** Indexes for /proc interfaces. **********************/
+
+#define TMY_POLICY_DOMAINPOLICY 0
+#define TMY_POLICY_SYSTEMPOLICY 1
+#define TMY_POLICY_EXCEPTIONPOLICY 2
+#define TMY_POLICY_DOMAIN_STATUS 3
+#define TMY_INFO_PROCESS_STATUS 4
+#define TMY_INFO_MEMINFO 5
+#define TMY_INFO_SELFDOMAIN 6
+#define TMY_PROFILE 7
+#define TMY_POLICY_QUERY 8
+#define TMY_POLICY_MANAGER 9
+#define TMY_INFO_UPDATESCOUNTER 10
+#define TMY_VERSION 11
+
+/******************** The structure for /proc interfaces. *******************/
+
+struct io_buffer {
+ int (*read) (struct io_buffer *);
+ struct semaphore read_sem;
+ int (*write) (struct io_buffer *);
+ struct semaphore write_sem;
+ int (*poll) (struct file *file, poll_table *wait);
+ void *read_var1; /* The position currently reading from. */
+ void *read_var2; /* Extra variables for reading. */
+ struct domain_info *write_var1; /* The position currently writing to.*/
+ int read_step; /* The step for reading. */
+ char *read_buf; /* Buffer for reading. */
+ int read_eof; /* EOF flag for reading. */
+ int read_avail; /* Bytes available for reading. */
+ int readbuf_size; /* Size of read buffer. */
+ char *write_buf; /* Buffer for writing. */
+ int write_avail; /* Bytes available for writing. */
+ int writebuf_size; /* Size of write buffer. */
+};
+
+/************************* PROTOTYPES *************************/
+
+char *tmy_find_condition_part(char *data);
+const struct condition_list *tmy_assign_condition(const char *condition);
+int tmy_check_condition(const struct condition_list *ptr,
+ struct obj_info *obj);
+int tmy_dump_condition(struct io_buffer *head,
+ const struct condition_list *ptr);
+const char *tmy_get_exe(void);
+const char *tmy_acltype2keyword(const unsigned int acl_type);
+
+int tmy_mount_perm(char *dev_name,
+ char *dir_name,
+ char *type,
+ unsigned long flags);
+int tmy_conceal_mount(struct nameidata *nd);
+int tmy_umount_perm(struct vfsmount *mnt);
+int tmy_add_mount_policy(char *data, const int is_delete);
+int tmy_read_mount_policy(struct io_buffer *head);
+int tmy_add_no_umount_policy(char *data, const int is_delete);
+int tmy_read_no_umount_policy(struct io_buffer *head);
+int tmy_pivot_root_perm(struct nameidata *old_nd,
+ struct nameidata *new_nd);
+int tmy_add_pivot_root_policy(char *data, const int is_delete);
+int tmy_read_pivot_root_policy(struct io_buffer *head);
+
+int tmy_add_aggregator_policy(char *data, const int is_delete);
+int tmy_add_addr_group_policy(char *data, const int is_delete);
+int tmy_add_alias_policy(char *data, const int is_delete);
+int tmy_add_argv0_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const int is_delete);
+int tmy_add_acl(struct acl_info *ptr,
+ struct domain_info *domain,
+ struct acl_info *new_ptr);
+int tmy_add_domain_initializer_policy(char *data,
+ const int is_not,
+ const int is_delete);
+int tmy_add_domain_keeper_policy(char *data,
+ const int is_not,
+ const int is_delete);
+int tmy_file_perm(const char *filename0, const u8 perm, const char *operation);
+int tmy_add_file_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const int is_delete);
+int tmy_add_globally_readable_policy(char *data, const int is_delete);
+int tmy_add_group_policy(char *data, const int is_delete);
+int tmy_add_network_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const int is_delete);
+int tmy_add_no_rewrite_policy(char *pattern, const int is_delete);
+int tmy_add_pattern_policy(char *data, const int is_delete);
+int tmy_supervisor(const char *fmt, ...)
+ __attribute__((format(printf, 1, 2)));
+
+int tmy_del_acl(struct acl_info *ptr);
+int tmy_delete_domain(char *data);
+int tmy_is_correct_domain(const unsigned char *domainname,
+ const char *function);
+int tmy_correct_path(const char *filename,
+ const int start_type,
+ const int pattern_type,
+ const int end_type,
+ const char *function);
+int tmy_is_domain_def(const unsigned char *buffer);
+int tmy_path_match(const struct path_info *pathname0,
+ const struct path_info *pattern0);
+int tmy_read_aggregator_policy(struct io_buffer *head);
+int tmy_read_alias_policy(struct io_buffer *head);
+int tmy_read_domain_initializer_policy(struct io_buffer *head);
+int tmy_read_domain_keeper_policy(struct io_buffer *head);
+int tmy_read_globally_readable_policy(struct io_buffer *head);
+int tmy_read_group_policy(struct io_buffer *head);
+int tmy_read_no_rewrite_policy(struct io_buffer *head);
+int tmy_read_pattern_policy(struct io_buffer *head);
+int tmy_read_addr_group_policy(struct io_buffer *head);
+int tmy_argv0_perm(const struct path_info *filename, const char *argv0);
+int tmy_network_listen_acl(const int is_ipv6,
+ const u8 *address,
+ const u16 port);
+int tmy_network_connect_acl(const int is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port);
+int tmy_network_bind_acl(const int is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port);
+int tmy_network_sendmsg_acl(const int is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port);
+int tmy_network_accept_acl(const int is_ipv6,
+ const u8 *address,
+ const u16 port);
+int tmy_network_recvmsg_acl(const int is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port);
+
+int tmy_signal_acl(int sig, int pid);
+int tmy_add_signal_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const int is_delete);
+
+char *tmy_init_audit_log(int *len);
+int tmy_write_audit_log(char *log, const int is_granted, const int is_enforce);
+int tmy_acltype2paths(const unsigned int acl_type);
+int tmy_io_printf(struct io_buffer *head, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+struct domain_info *tmy_find_domain(const char *domainname);
+struct domain_info *tmy_new_domain(const char *domainname, const u8 profile);
+struct domain_info *tmy_undelete_domain(const char *domainname0);
+unsigned int tmy_accept(const unsigned int index);
+unsigned int tmy_enforce(const unsigned int index);
+unsigned int tmy_flags(const unsigned int index);
+unsigned int tmy_audit_grant(void);
+unsigned int tmy_audit_reject(void);
+void tmy_update_counter(const unsigned char index);
+void *tmy_alloc(const size_t size);
+void tmy_free(const void *p);
+void tmy_fill_path_info(struct path_info *ptr);
+
+static inline int tmy_pathcmp(const struct path_info *a,
+ const struct path_info *b)
+{
+ return a->hash != b->hash || strcmp(a->name, b->name);
+}
+
+extern struct domain_info KERNEL_DOMAIN;
+void tmy_load_policy(const char *filename);
+int tmy_find_next_domain(struct linux_binprm *,
+ struct domain_info **);
+void tmy_proc_init(void);
+
+struct path_info;
+
+int tmy_exec_perm(const struct path_info *filename, struct file *filp);
+/* Check whether the given dentry is allowed to read/write/execute. */
+int tmy_open_perm(struct dentry *dentry, struct vfsmount *mnt, const int flag);
+/* Check whether the given dentry is allowed to write. */
+int tmy_single_write_perm(const unsigned int operation,
+ struct dentry *dentry,
+ struct vfsmount *mnt);
+int tmy_double_write_perm(const unsigned int operation,
+ struct dentry *dentry1,
+ struct vfsmount *mnt1,
+ struct dentry *dentry2,
+ struct vfsmount *mnt2);
+int tmy_rewrite_perm(struct file *filp);
+
+struct inode;
+
+/******************** Index numbers for Access Controls. ********************/
+
+#define TMY_TYPE_CREATE_ACL 0
+#define TMY_TYPE_UNLINK_ACL 1
+#define TMY_TYPE_MKDIR_ACL 2
+#define TMY_TYPE_RMDIR_ACL 3
+#define TMY_TYPE_MKFIFO_ACL 4
+#define TMY_TYPE_MKSOCK_ACL 5
+#define TMY_TYPE_MKBLOCK_ACL 6
+#define TMY_TYPE_MKCHAR_ACL 7
+#define TMY_TYPE_TRUNCATE_ACL 8
+#define TMY_TYPE_SYMLINK_ACL 9
+#define TMY_TYPE_LINK_ACL 10
+#define TMY_TYPE_RENAME_ACL 11
+#define TMY_TYPE_REWRITE_ACL 12
+
+#define TMY_TYPE_FILE_ACL 100
+#define TMY_TYPE_ARGV0_ACL 101
+#define TMY_TYPE_IP_NETWORK_ACL 103
+#define TMY_TYPE_SIGNAL_ACL 104
+
+struct linux_binprm;
+struct pt_regs;
+
+#define TMY_CHECK_READ_FOR_OPEN_EXEC 1
+
+int tmy_too_many_acl(struct domain_info * const domain);
+char *tmy_print_ipv6(char *buffer, const int buffer_len, const u16 *ip);
+const char *tmy_network2keyword(const unsigned int operation);
+
+/* to check "if task.parent.pid" condition. */
+extern asmlinkage long sys_getppid(void);
+
+#endif
Basic functions to get canonicalized absolute pathnames
for TOMOYO Linux. Even the requested pathname is symlink()ed
or chroot()ed, TOMOYO Linux uses the original pathname.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/realpath.c | 697 +++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 697 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/realpath.c 2007-08-24 15:51:35.000000000 +0900
@@ -0,0 +1,697 @@
+/*
+ * security/tomoyo/realpath.c
+ *
+ * Get the canonicalized absolute pathnames.
+ * The basis for TOMOYO Linux.
+ */
+
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/utime.h>
+#include <linux/file.h>
+#include <linux/smp_lock.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <asm/atomic.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/proc_fs.h>
+#include <linux/sysctl.h>
+#include "realpath.h"
+#include "tomoyo.h"
+
+/***** realpath handler *****/
+
+static int tmy_print_ascii(const char *sp, const char *cp,
+ int *buflen0, char **end0)
+{
+ int buflen = *buflen0;
+ char *end = *end0;
+
+ while (sp <= cp) {
+ unsigned char c;
+
+ c = * (unsigned char *) cp;
+ if (c == '\\') {
+ buflen -= 2;
+ if (buflen < 0)
+ goto out;
+ *--end = '\\';
+ *--end = '\\';
+ } else if (c > ' ' && c < 127) {
+ if (--buflen < 0)
+ goto out;
+ *--end = (char) c;
+ } else {
+ buflen -= 4;
+ if (buflen < 0)
+ goto out;
+ *--end = (c & 7) + '0';
+ *--end = ((c >> 3) & 7) + '0';
+ *--end = (c >> 6) + '0';
+ *--end = '\\';
+ }
+ cp--;
+ }
+
+ *buflen0 = buflen;
+ *end0 = end;
+
+ return 0;
+out: ;
+ return -ENOMEM;
+}
+
+/**
+ * tmy_get_absolute_path - return the realpath of a dentry.
+ * @dentry: pointer to "struct dentry".
+ * @vfsmnt: pointer to "struct vfsmount" to which the @dentry belongs.
+ * @buffer: size of buffer to save the result.
+ * @buflen: size of @buffer .
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * Caller holds the dcache_lock.
+ * Based on __d_path() in fs/dcache.c
+ *
+ * Unlike d_path(), this function traverses upto the root directory of
+ * process's namespace.
+ *
+ * If @dentry is a directory, trailing '/' is appended.
+ * Characters other than ' ' < c < 127 are converted to \ooo style octal string.
+ * Character \ is converted to \\ string.
+ */
+static int tmy_get_absolute_path(struct dentry *dentry,
+ struct vfsmount *vfsmnt,
+ char *buffer,
+ int buflen)
+{
+ char *start = buffer;
+ char *end = buffer + buflen;
+ int is_dir = (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode));
+ const char *sp;
+ const char *cp;
+
+ if (buflen < 256)
+ goto out;
+
+ *--end = '\0';
+ buflen--;
+
+ while (1) {
+ struct dentry *parent;
+
+ if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+ /* Global root? */
+ spin_lock(&vfsmount_lock);
+ if (vfsmnt->mnt_parent == vfsmnt) {
+ spin_unlock(&vfsmount_lock);
+ break;
+ }
+ dentry = vfsmnt->mnt_mountpoint;
+ vfsmnt = vfsmnt->mnt_parent;
+ spin_unlock(&vfsmount_lock);
+ continue;
+ }
+
+ if (is_dir) {
+ is_dir = 0;
+ *--end = '/';
+ buflen--;
+ }
+
+ parent = dentry->d_parent;
+ sp = dentry->d_name.name;
+ cp = sp + dentry->d_name.len - 1;
+
+ /* Exception: Use /proc/self/ rather than */
+ /* /proc/\$/ for current process. */
+ if (IS_ROOT(parent) &&
+ *sp > '0' && *sp <= '9' && parent->d_sb &&
+ parent->d_sb->s_magic == PROC_SUPER_MAGIC) {
+
+ char *ep;
+ const pid_t pid = (pid_t) simple_strtoul(sp, &ep, 10);
+
+ if (!*ep && pid == current->tgid) {
+ sp = "self";
+ cp = sp + 3;
+ }
+
+ }
+
+ if (tmy_print_ascii(sp, cp, &buflen, &end))
+ goto out;
+
+ if (--buflen < 0)
+ goto out;
+ *--end = '/';
+
+ dentry = parent;
+ }
+ if (*end == '/') {
+ buflen++;
+ end++;
+ }
+
+ sp = dentry->d_name.name;
+ cp = sp + dentry->d_name.len - 1;
+
+ if (tmy_print_ascii(sp, cp, &buflen, &end))
+ goto out;
+
+ /* Move the pathname to the top of the buffer. */
+ memmove(start, end, strlen(end) + 1);
+ return 0;
+out: ;
+ return -ENOMEM;
+}
+
+/**
+ * tmy_realpath_dentry2 - return the realpath of a dentry.
+ * @dentry: pointer to "struct dentry".
+ * @mnt: pointer to "struct vfsmount" to which the @dentry belongs.
+ * @newname: buffer to save the result.
+ * @newname_len: size of @newname .
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_realpath_dentry2(struct dentry *dentry,
+ struct vfsmount *mnt,
+ char *newname,
+ int newname_len)
+{
+ int error;
+ struct dentry *d_dentry;
+ struct vfsmount *d_mnt;
+
+ if (!dentry || !mnt || !newname || newname_len <= 0)
+ return -EINVAL;
+
+ d_dentry = dget(dentry);
+ d_mnt = mntget(mnt);
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&dcache_lock);
+ error = tmy_get_absolute_path(d_dentry, d_mnt, newname, newname_len);
+ spin_unlock(&dcache_lock);
+ /***** CRITICAL SECTION END *****/
+
+ dput(d_dentry);
+ mntput(d_mnt);
+ return error;
+}
+
+/**
+ * tmy_realpath_dentry - return the realpath of a dentry.
+ * @dentry: pointer to "struct dentry".
+ * @mnt: pointer to "struct vfsmount" to which the @dentry belongs.
+ *
+ * Returns realpath(3) of the @dentry on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *tmy_realpath_dentry(struct dentry *dentry, struct vfsmount *mnt)
+{
+ char *buf = tmy_alloc(TMY_MAX_PATHNAME_LEN);
+
+ if (buf &&
+ tmy_realpath_dentry2(dentry, mnt, buf,
+ TMY_MAX_PATHNAME_LEN - 1) == 0)
+ return buf;
+
+ tmy_free(buf);
+ return NULL;
+}
+
+/**
+ * tmy_realpath - return the realpath of a pathname.
+ * @pathname: pathname to report.
+ *
+ * Returns realpath(3) of the @pathname on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *tmy_realpath(const char *pathname)
+{
+ struct nameidata nd;
+
+ if (pathname && path_lookup(pathname, LOOKUP_FOLLOW, &nd) == 0) {
+ char *buf = tmy_realpath_dentry(nd.dentry, nd.mnt);
+
+ path_release(&nd);
+ return buf;
+ }
+
+ return NULL;
+}
+
+/**
+ * tmy_realpath_nofollow - return the realpath of a pathname.
+ * @pathname: pathname to report.
+ *
+ * Returns realpath(3) of the @pathname on success.
+ * Returns NULL on failure.
+ *
+ * Unlike tmy_realpath(), this function doesn't follow if @pathname is
+ * a symbolic link.
+ *
+ * This function uses tmy_alloc(), so caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *tmy_realpath_nofollow(const char *pathname)
+{
+ struct nameidata nd;
+
+ if (pathname && path_lookup(pathname, 0, &nd) == 0) {
+ char *buf = tmy_realpath_dentry(nd.dentry, nd.mnt);
+
+ path_release(&nd);
+ return buf;
+ }
+
+ return NULL;
+}
+
+/* tmy_get_absolute_path() for "struct ctl_table". */
+static int tmy_sysctl_path(struct ctl_table *table, char *buffer, int buflen)
+{
+ char *end = buffer + buflen;
+
+ if (buflen < 256)
+ goto out;
+
+ *--end = '\0';
+ buflen--;
+
+ buflen -= 9; /* for "/proc/sys" prefix */
+
+ while (table) {
+ char buf[32];
+ const char *sp = table->procname;
+ const char *cp;
+
+ if (!sp) {
+ memset(buf, 0, sizeof(buf));
+ snprintf(buf, sizeof(buf) - 1, "=%d=", table->ctl_name);
+ sp = buf;
+ }
+ cp = strchr(sp, '\0') - 1;
+
+ if (tmy_print_ascii(sp, cp, &buflen, &end))
+ goto out;
+
+ if (--buflen < 0)
+ goto out;
+
+ *--end = '/';
+ table = table->parent;
+ }
+
+ /* Move the pathname to the top of the buffer. */
+ memmove(buffer, "/proc/sys", 9);
+ memmove(buffer + 9, end, strlen(end) + 1);
+ return 0;
+out: ;
+ return -ENOMEM;
+}
+
+/**
+ * sysctlpath_from_table - return the realpath of a ctl_table.
+ * @table: pointer to "struct ctl_table".
+ *
+ * Returns realpath(3) of the @table on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *sysctlpath_from_table(struct ctl_table *table)
+{
+ char *buf = tmy_alloc(TMY_MAX_PATHNAME_LEN);
+
+ if (buf && tmy_sysctl_path(table, buf, TMY_MAX_PATHNAME_LEN - 1) == 0)
+ return buf;
+
+ tmy_free(buf);
+ return NULL;
+}
+
+/***** Private memory allocator. *****/
+
+/*
+ * Round up an integer so that the returned pointers are appropriately aligned.
+ * FIXME: Are there more requirements that is needed for assigning value
+ * atomically?
+ */
+static inline unsigned int tmy_roundup(const unsigned int size)
+{
+ if (sizeof(void *) >= sizeof(long))
+ return ((size + sizeof(void *) - 1) / sizeof(void *))
+ * sizeof(void *);
+
+ return ((size + sizeof(long) - 1) / sizeof(long)) * sizeof(long);
+}
+
+static unsigned int allocated_memory_for_elements;
+
+/**
+ * tmy_get_memory_used_for_elements - get memory usage for private data.
+ *
+ * Returns size of memory allocated for keeping individual ACL elements.
+ */
+unsigned int tmy_get_memory_used_for_elements(void)
+{
+ return allocated_memory_for_elements;
+}
+
+/**
+ * tmy_alloc_element - allocate memory for structures.
+ * @size: requested size in bytes.
+ *
+ * Returns '\0'-initialized memory region on success.
+ * Returns NULL on failure.
+ *
+ * This function allocates memory for keeping ACL entries.
+ * The RAM is chunked, so NEVER try to kfree() the returned pointer.
+ */
+void *tmy_alloc_element(const unsigned int size)
+{
+ static DECLARE_MUTEX(lock);
+ static char *buf;
+ static unsigned int buf_used_len = PAGE_SIZE;
+ char *ptr = NULL;
+ const unsigned int word_aligned_size = tmy_roundup(size);
+
+ if (word_aligned_size > PAGE_SIZE)
+ return NULL;
+
+ down(&lock);
+
+ if (buf_used_len + word_aligned_size > PAGE_SIZE) {
+ ptr = kmalloc(PAGE_SIZE, GFP_KERNEL);
+
+ if (!ptr) {
+ printk(KERN_INFO "ERROR: "
+ "Out of memory for tmy_alloc_element().\n");
+ if (!sbin_init_started)
+ panic("MAC Initialization failed.\n");
+ } else {
+ memset(ptr, 0, PAGE_SIZE);
+ buf = ptr;
+ allocated_memory_for_elements += PAGE_SIZE;
+ buf_used_len = word_aligned_size;
+ ptr = buf;
+ }
+
+ } else if (word_aligned_size) {
+ unsigned int i;
+
+ ptr = buf + buf_used_len;
+ buf_used_len += word_aligned_size;
+
+ for (i = 0; i < word_aligned_size; i++) {
+ if (ptr[i]) { /* This must not happen! */
+ printk(KERN_ERR
+ "WARNING: Reserved memory was tainted! "
+ "The system might go wrong.\n");
+ ptr[i] = '\0';
+ }
+ }
+
+ }
+
+ up(&lock);
+ return ptr;
+}
+
+/***** Shared memory allocator. *****/
+
+static unsigned int allocated_memory_for_savename;
+
+/**
+ * tmy_get_memory_used_for_save_name - get memory usage for shared data.
+ *
+ * Returns size of memory allocated for keeping string tokens.
+ */
+unsigned int tmy_get_memory_used_for_save_name(void)
+{
+ return allocated_memory_for_savename;
+}
+
+#define MAX_HASH 256
+
+/* List of tokens. */
+struct name_entry {
+ struct name_entry *next; /* NULL if none. */
+ struct path_info entry;
+};
+
+/* List of free memory. */
+struct free_memory_block_list {
+ struct free_memory_block_list *next; /* NULL if none. */
+ char *ptr; /* Pointer to a free area. */
+ int len; /* Length of the area. */
+};
+
+/**
+ * tmy_save_name - keep the given token on the RAM.
+ * @name: the string token to remember.
+ *
+ * Returns pointer to memory region on success.
+ * Returns NULL on failure.
+ *
+ * This function allocates memory for keeping any string data.
+ * The RAM is shared, so NEVER try to modify or kfree() the returned name.
+ */
+const struct path_info *tmy_save_name(const char *name)
+{
+ static struct free_memory_block_list fmb_list = { NULL, NULL, 0 };
+ static struct name_entry name_list[MAX_HASH]; /* The list of names. */
+ static DECLARE_MUTEX(lock);
+ struct name_entry *ptr;
+ struct name_entry *prev = NULL;
+ unsigned int hash;
+ struct free_memory_block_list *fmb = &fmb_list;
+ int len;
+ static int first_call = 1;
+
+ if (!name)
+ return NULL;
+
+ len = strlen(name) + 1;
+ if (len > TMY_MAX_PATHNAME_LEN) {
+ printk(KERN_INFO "ERROR: Name too long for tmy_save_name().\n");
+ return NULL;
+ }
+
+ hash = full_name_hash((const unsigned char *) name, len - 1);
+
+ down(&lock);
+
+ if (first_call) {
+ int i;
+
+ first_call = 0;
+ memset(&name_list, 0, sizeof(name_list));
+
+ for (i = 0; i < MAX_HASH; i++) {
+ name_list[i].entry.name = "/";
+ tmy_fill_path_info(&name_list[i].entry);
+ }
+
+ if (TMY_MAX_PATHNAME_LEN > PAGE_SIZE)
+ panic("Bad size.");
+ }
+
+ ptr = &name_list[hash % MAX_HASH];
+ while (ptr) {
+ if (hash == ptr->entry.hash &&
+ strcmp(name, ptr->entry.name) == 0)
+ goto out;
+ prev = ptr;
+ ptr = ptr->next;
+ }
+
+ while (len > fmb->len) {
+ char *cp;
+
+ if (fmb->next) {
+ fmb = fmb->next;
+ continue;
+ }
+
+ cp = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!cp)
+ goto out2;
+
+ fmb->next = tmy_alloc_element(sizeof(*fmb));
+ if (!fmb->next) {
+ kfree(cp);
+out2: ;
+ printk(KERN_INFO
+ "ERROR: Out of memory for tmy_save_name().\n");
+ if (!sbin_init_started)
+ panic("MAC Initialization failed.\n");
+ goto out; /* ptr == NULL */
+ }
+
+ memset(cp, 0, PAGE_SIZE);
+ allocated_memory_for_savename += PAGE_SIZE;
+ fmb = fmb->next;
+ fmb->ptr = cp;
+ fmb->len = PAGE_SIZE;
+
+ }
+
+ 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;
+
+ prev->next = ptr; /* prev != NULL because name_list is not empty. */
+ if (fmb->len == 0) {
+ struct free_memory_block_list *ptr = &fmb_list;
+ while (ptr->next != fmb)
+ ptr = ptr->next;
+ ptr->next = fmb->next;
+ }
+out: ;
+
+ up(&lock);
+
+ return ptr ? &ptr->entry : NULL;
+}
+
+/***** Dynamic memory allocator. *****/
+
+/* List of temporary memory blocks. */
+struct cache_entry {
+ struct list_head list;
+ void *ptr;
+ int size;
+};
+
+static struct kmem_cache *tmy_cachep;
+
+/**
+ * tmy_realpath_init - initialize memory allocator.
+ */
+void tmy_realpath_init(void)
+{
+ tmy_cachep = kmem_cache_create("tomoyo_cache",
+ sizeof(struct cache_entry),
+ 0, SLAB_PANIC, NULL);
+}
+
+static LIST_HEAD(cache_list);
+static spinlock_t cache_list_lock = SPIN_LOCK_UNLOCKED;
+static unsigned int dynamic_memory_size;
+
+/**
+ * tmy_get_memory_used_for_dynamic - get memory usage for temporary purpose.
+ *
+ * Returns size of memory allocated for keeping temporary purpose.
+ */
+unsigned int tmy_get_memory_used_for_dynamic(void)
+{
+ return dynamic_memory_size;
+}
+
+/**
+ * tmy_alloc - allocate memory for temporary purpose.
+ * @size: requested size in bytes.
+ *
+ * Returns '\0'-initialized memory region on success.
+ * Returns NULL on failure.
+ *
+ * This function allocates memory for keeping ACL entries.
+ * The caller has to call tmy_free() the returned pointer
+ * when memory is no longer needed.
+ */
+void *tmy_alloc(const size_t size)
+{
+ void *ret = kmalloc(size, GFP_KERNEL);
+ struct cache_entry *new_entry;
+
+ if (!ret) {
+ printk(KERN_INFO "ERROR: "
+ "kmalloc(): Out of memory for tmy_alloc().\n");
+ return ret;
+ }
+
+ new_entry = kmem_cache_alloc(tmy_cachep, GFP_KERNEL);
+
+ if (!new_entry) {
+ printk(KERN_INFO "ERROR: "
+ "kmem_cache_alloc(): Out of memory for tmy_alloc().\n");
+ kfree(ret);
+ ret = NULL;
+ } else {
+ INIT_LIST_HEAD(&new_entry->list);
+ new_entry->ptr = ret;
+ new_entry->size = size;
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&cache_list_lock);
+ list_add_tail(&new_entry->list, &cache_list);
+ dynamic_memory_size += size;
+ spin_unlock(&cache_list_lock);
+ /***** CRITICAL SECTION END *****/
+
+ memset(ret, 0, size);
+ }
+
+ return ret;
+}
+
+/**
+ * tmy_free - free memory allocated by tmy_alloc().
+ * @p: pointer to memory block allocated by tmy_alloc().
+ *
+ * If caller calls this function for multiple times (i.e. double-free() bug) or
+ * calls this function with invalid pointer, warning message will appear.
+ * If caller forgets to call this function,
+ * tmy_get_memory_used_for_dynamic() will indicate memory leaks.
+ */
+void tmy_free(const void *p)
+{
+ struct list_head *v;
+ struct cache_entry *entry = NULL;
+ if (!p)
+ return;
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&cache_list_lock);
+ list_for_each_prev(v, &cache_list) {
+ entry = list_entry(v, struct cache_entry, list);
+ if (entry->ptr != p) {
+ entry = NULL;
+ continue;
+ }
+ list_del(&entry->list);
+ dynamic_memory_size -= entry->size;
+ break;
+ }
+ spin_unlock(&cache_list_lock);
+ /***** CRITICAL SECTION END *****/
+
+ if (entry) {
+ kfree(p);
+ kmem_cache_free(tmy_cachep, entry);
+ } else
+ printk(KERN_INFO "BUG: tmy_free() with invalid pointer.\n");
+
+}
Common functions for TOMOYO Linux.
TOMOYO Linux uses /proc/tomoyo interface for configuration.
/proc/tomoyo/domain_policy is the domain-based access policy.
Access control list for files, networks, argv[0] and signal is
stored in domain_policy.
/proc/tomoyo/system_policy is the system-wide access policy.
Access control list for mount, umount and pivot_root is
stored in system_policy.
/proc/tomoyo/exception_policy is the other settings such as
globally readable library files, domain transition configurations
or pre-defined patterned pathnames.
/proc/tomoyo/profile has some profiles, which configure the access
control level of TOMOYO Linux. A profile is assigned to a domain.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/common.c | 2385 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 2385 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/common.c 2007-08-24 15:51:35.000000000 +0900
@@ -0,0 +1,2385 @@
+/*
+ * security/tomoyo/common.c
+ *
+ * Common functions for TOMOYO Linux.
+ */
+
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/utime.h>
+#include <linux/file.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <stdarg.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/proc_fs.h>
+#include "realpath.h"
+#include "tomoyo.h"
+
+#define MAX_ACCEPT_ENTRY 2048
+
+static int tmy_read_control(struct file *file,
+ char __user *buffer,
+ const int buffer_len);
+
+/************************* VARIABLES *************************/
+
+/* /sbin/init started? */
+int sbin_init_started;
+
+static struct {
+ const char *keyword;
+ unsigned int current_value;
+ const unsigned int max_value;
+} tmy_control[TMY_MAX_CONTROL_INDEX] = {
+ [TMY_COMMENT] = { "COMMENT", 0, 0 },
+ [TMY_MAC_FOR_FILE] = { "MAC_FOR_FILE", 0, 3 },
+ [TMY_MAC_FOR_ARGV0] = { "MAC_FOR_ARGV0", 0, 3 },
+ [TMY_MAC_FOR_NETWORK] = { "MAC_FOR_NETWORK", 0, 3 },
+ [TMY_MAC_FOR_SIGNAL] = { "MAC_FOR_SIGNAL", 0, 3 },
+ [TMY_DENY_CONCEAL_MOUNT] = { "DENY_CONCEAL_MOUNT", 0, 3 },
+ [TMY_RESTRICT_MOUNT] = { "RESTRICT_MOUNT", 0, 3 },
+ [TMY_RESTRICT_UMOUNT] = { "RESTRICT_UNMOUNT", 0, 3 },
+ [TMY_RESTRICT_PIVOT_ROOT] = { "RESTRICT_PIVOT_ROOT", 0, 3 },
+ [TMY_MAX_ACCEPT_ENTRY] =
+ { "MAX_ACCEPT_ENTRY", MAX_ACCEPT_ENTRY, INT_MAX },
+ [TMY_AUDIT_GRANT] = { "AUDIT_GRANT", 0, 1 },
+ [TMY_AUDIT_REJECT] = { "AUDIT_REJECT", 1, 1 },
+ [TMY_ALLOW_ENFORCE_GRACE] = { "ALLOW_ENFORCE_GRACE", 0, 1 },
+};
+
+struct profile {
+ unsigned int value[TMY_MAX_CONTROL_INDEX];
+ const struct path_info *comment;
+};
+
+static struct profile *profile_ptr[TMY_MAX_PROFILES];
+
+/************************* UTILITY FUNCTIONS *************************/
+
+/* Is the current process running as root? */
+static int tmy_is_root(void)
+{
+ return !current->uid && !current->euid;
+}
+
+/**
+ * tmy_normalize_line - make a line tidy.
+ * @buffer: the line to make tidy.
+ *
+ * All tokens (such as pathnames) used in TOMOYO Linux contains
+ * only ASCII printable (i.e. 0x21-0x7E) range characters.
+ * This allows policy files and auditing logs split monotonically
+ * using space (i.e. ' ') and new line (i.e. '\n') characters.
+ *
+ * Remove leading and trailing non ASCII printable chracters and
+ * replace one or more non ASCII printable chracters with single space.
+ */
+static void tmy_normalize_line(unsigned char *buffer)
+{
+ unsigned char *sp = buffer;
+ unsigned char *dp = buffer;
+ int first = 1;
+
+ while (*sp && (*sp <= 0x20 || *sp >= 0x7F))
+ sp++;
+
+ while (*sp) {
+ if (!first)
+ *dp++ = ' ';
+ first = 0;
+ while (*sp > 0x20 && *sp < 0x7F)
+ *dp++ = *sp++;
+ while (*sp && (*sp <= 0x20 || *sp >= 0x7F))
+ sp++;
+ }
+
+ *dp = '\0';
+}
+
+/* Is @c the first letter of "\ooo" expression? */
+static int tmy_char_is_0to3(const unsigned char c)
+{
+ return c >= '0' && c <= '3';
+}
+
+/* Is @c the second or third letter of "\ooo" expression? */
+static int tmy_char_is_0to7(const unsigned char c)
+{
+ return c >= '0' && c <= '7';
+}
+
+/* Is @c a decimal letter? */
+static int tmy_char_is_0to9(const unsigned char c)
+{
+ return c >= '0' && c <= '9';
+}
+
+/* Is @c a hexadecimal letter? */
+static int tmy_char_is_hex(const unsigned char c)
+{
+ return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')
+ || (c >= 'a' && c <= 'f');
+}
+
+/* Is @c an alphabet letter? */
+static int tmy_char_is_alpha(const unsigned char c)
+{
+ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+}
+
+/* Convert \ooo style expression to a byte. */
+static unsigned char tmy_str2chr(const unsigned char c,
+ const unsigned char d,
+ const unsigned char e)
+{
+ return (((unsigned char) (c - '0')) << 6) +
+ (((unsigned char) (d - '0')) << 3) +
+ (((unsigned char) (e - '0')));
+}
+
+/* Does the @src starts with @find? */
+static int tmy_strstarts(char **src, const char *find)
+{
+ const int len = strlen(find);
+ char *tmp = *src;
+
+ if (strncmp(tmp, find, len) == 0) {
+ tmp += len;
+ *src = tmp;
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * tmy_correct_path - validate a pathname.
+ * @filename: the pathname to check
+ * @start_type: 1 if the pathname must start with '/'
+ * -1 if the pathname must not start with '/'
+ * 0 if it does not matter
+ * @pattern_type: 1 if the pathname must contain patterns
+ * -1 if the pathname must not contain patterns
+ * 0 if it does not matter
+ * @end_type: 1 if the pathname must end with '/'
+ * -1 if the pathname must not end with '/'
+ * 0 if it does not matter
+ * @function: string to display if the @filename is invalid
+ *
+ * Returns nonzero if the pathname is valid.
+ * Returns zero otherwise.
+ *
+ * Check whether the given pathname follows the naming rules.
+ */
+int tmy_correct_path(const char *filename,
+ const int start_type,
+ const int pattern_type,
+ const int end_type,
+ const char *function)
+{
+ int contains_pattern = 0;
+ char c;
+ char d;
+ char e;
+ const char *original_filename = filename;
+
+ if (!filename)
+ goto out;
+
+ c = *filename;
+ if (start_type == 1) { /* Must start with '/' */
+ if (c != '/')
+ goto out;
+ } else if (start_type == -1) { /* Must not start with '/' */
+ if (c == '/')
+ goto out;
+ }
+
+ if (c)
+ c = * (strchr(filename, '\0') - 1);
+ if (end_type == 1) { /* Must end with '/' */
+ if (c != '/')
+ goto out;
+ } else if (end_type == -1) { /* Must not end with '/' */
+ if (c == '/')
+ goto out;
+ }
+
+ while ((c = *filename++) != '\0') {
+ if (c == '\\') {
+ unsigned char f;
+ switch ((c = *filename++)) {
+ case '\\': /* "\\" */
+ continue;
+ case '$': /* "\$" */
+ case '+': /* "\+" */
+ case '?': /* "\?" */
+ case '*': /* "\*" */
+ case '@': /* "\@" */
+ case 'x': /* "\x" */
+ case 'X': /* "\X" */
+ case 'a': /* "\a" */
+ case 'A': /* "\A" */
+ case '-': /* "\-" */
+ if (pattern_type == -1)
+ goto out; /* Must not contain pattern */
+ contains_pattern = 1;
+ continue;
+ case '0': /* "\ooo" */
+ case '1':
+ case '2':
+ case '3':
+ d = *filename++;
+ if (!tmy_char_is_0to7(d))
+ goto out;
+ e = *filename++;
+ if (!tmy_char_is_0to7(e))
+ goto out;
+ f = tmy_str2chr(c, d, e);
+ if (f && (f <= 0x20 || f >= 0x7F))
+ /* valid \ooo expression */
+ continue;
+ }
+ goto out;
+ } else if (c <= 0x20 || c >= 0x7F)
+ goto out;
+ }
+
+ if (pattern_type == 1) { /* Must contain pattern */
+ if (!contains_pattern)
+ goto out;
+ }
+
+ return 1;
+
+out: ;
+ printk(KERN_DEBUG "%s: Invalid pathname '%s'\n",
+ function, original_filename);
+ return 0;
+}
+
+/**
+ * tmy_is_correct_domain - validate a domainname.
+ * @domainname: the domainname to check.
+ * @function: string to display if the @domainname is invalid.
+ *
+ * Returns nonzero if the domainname is valid.
+ * Returns zero otherwise.
+ *
+ * Check whether the given domainname follows the naming rules.
+ */
+int tmy_is_correct_domain(const unsigned char *domainname,
+ const char *function)
+{
+ unsigned char c;
+ unsigned char d;
+ unsigned char e;
+ const char *org_domainname = domainname;
+
+ if (!domainname || !tmy_strstarts((char **) &domainname, TMY_ROOT_NAME))
+ goto out;
+
+ if (!*domainname)
+ return 1;
+
+ do {
+ /* 0x20 is a domainname separator. */
+ if (*domainname++ != ' ')
+ goto out;
+ /* Must starts with '/'. */
+ if (*domainname++ != '/')
+ goto out;
+ while ((c = *domainname) != '\0' && c != ' ') {
+ domainname++;
+ if (c == '\\') {
+ unsigned char f;
+ switch ((c = *domainname++)) {
+ case '\\': /* "\\" */
+ continue;
+ case '0': /* "\ooo" */
+ case '1':
+ case '2':
+ case '3':
+ d = *domainname++;
+ if (!tmy_char_is_0to7(d))
+ goto out;
+ e = *domainname++;
+ if (!tmy_char_is_0to7(e))
+ goto out;
+ f = tmy_str2chr(c, d, e);
+ if (f && (f <= 0x20 || f >= 0x7F))
+ continue;
+ }
+ goto out;
+ } else if (c < 0x20 || c >= 0x7F)
+ /* 0x20 is a domainname separator. */
+ goto out;
+ }
+ } while (*domainname);
+
+ return 1;
+
+out: ;
+ printk(KERN_DEBUG "%s: Invalid domainname '%s'\n",
+ function, org_domainname);
+ return 0;
+}
+
+/**
+ * tmy_path_depth - evaluate the number of '/' characters in a token.
+ * @pathname: the token to evaluate.
+ *
+ * Each '/' character but the trailing '/' scores 2.
+ * The trailing '/' scores 1.
+ *
+ * If @pathname ends with '/', the return value is an odd integer.
+ * If @pathname does not end with '/', the return value is an even integer.
+ */
+static int tmy_path_depth(const char *pathname)
+{
+ int i = 0;
+
+ if (pathname) {
+ char *ep = strchr(pathname, '\0');
+
+ if (pathname < ep--) {
+ if (*ep != '/')
+ i++;
+ while (pathname <= ep)
+ if (*ep-- == '/')
+ i += 2;
+ }
+ }
+
+ return i;
+}
+
+/**
+ * tmy_const_part_length - calculate the constant part in a token.
+ * @filename: the token to calculate.
+ *
+ * Returns leading length of @filename that can be compared using strncmp().
+ */
+static int tmy_const_part_length(const char *filename)
+{
+ int len = 0;
+
+ if (filename) {
+ char c;
+
+ while ((c = *filename++) != '\0') {
+ if (c != '\\') {
+ len++;
+ continue;
+ }
+ switch (c = *filename++) {
+ case '\\': /* "\\" */
+ len += 2;
+ continue;
+ case '0': /* "\ooo" */
+ case '1':
+ case '2':
+ case '3':
+ if (!tmy_char_is_0to7(*filename++))
+ break;
+ if (!tmy_char_is_0to7(*filename++))
+ break;
+ len += 4;
+ continue;
+ }
+ break;
+ }
+ }
+
+ return len;
+}
+
+/**
+ * tmy_fill_path_info - fill "struct path_info" entry.
+ * @ptr: pointer to "struct path_info".
+ *
+ * Caller stores a token in "struct path_info"->name .
+ * This function will fill rest of "struct path_info" members.
+ */
+void tmy_fill_path_info(struct path_info *ptr)
+{
+ const char *name = ptr->name;
+ const int len = strlen(name);
+ ptr->total_len = len;
+ ptr->const_len = tmy_const_part_length(name);
+ ptr->is_dir = len && (name[len - 1] == '/');
+ ptr->is_patterned = (ptr->const_len < len);
+ ptr->hash = full_name_hash(name, len);
+ ptr->depth = tmy_path_depth(name);
+}
+
+/**
+ * tmy_file_match2 - internal function for tmy_path_match().
+ * @filename: start address of the token.
+ * @filename_end: end address of the token.
+ * @pattern: start address of the pattern.
+ * @pattern_end: end address of the pattern.
+ *
+ * Handle all patterns other than '\-' pattern.
+ * Returns nonzero if matches.
+ * Returns zero otherwise.
+ *
+ * @filename and @pattern do not contain '/'.
+ * @filename and @pattern are not '\0'-terminated.
+ * @pattern does not contain '\-'.
+ */
+static int tmy_file_match2(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end)
+{
+ while (filename < filename_end && pattern < pattern_end) {
+ char c;
+ int i;
+ int j;
+
+ if (*pattern != '\\') {
+ if (*filename++ != *pattern++)
+ goto out;
+ continue;
+ }
+
+ c = *filename;
+ pattern++;
+
+ switch (*pattern) {
+ case '?':
+ if (c == '/')
+ goto out;
+ if (c != '\\')
+ break;
+ /* filename[0] != '\0' */
+ c = filename[1];
+ if (c == '\\')
+ filename++;
+ else if (tmy_char_is_0to3(c) &&
+ /* filename[1] != '\0' */
+ tmy_char_is_0to7(filename[2]) &&
+ /* filename[2] != '\0' */
+ tmy_char_is_0to7(filename[3]))
+ /* filename[3] != '\0' */
+ filename += 3;
+ /*
+ * Why not "filename += 4;" here
+ * because it processed 4 (i.e. "\ooo") bytes?
+ * - Because "filename++;" is done
+ * after "break;".
+ */
+ else
+ goto out;
+ break;
+ case '\\':
+ if (c != '\\')
+ goto out;
+ /* filename[0] != '\0' */
+ if (*++filename != '\\')
+ goto out;
+ break;
+ case '+':
+ if (!tmy_char_is_0to9(c))
+ goto out;
+ break;
+ case 'x':
+ if (!tmy_char_is_hex(c))
+ goto out;
+ break;
+ case 'a':
+ if (!tmy_char_is_alpha(c))
+ goto out;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ if (c != '\\')
+ goto out;
+ /* filename[0] != '\0' */
+ c = filename[1];
+ if (!tmy_char_is_0to3(c) || c != *pattern)
+ goto out;
+ /* filename[1] != '\0' */
+ c = filename[2];
+ /* pattern[0] != '\0' */
+ if (!tmy_char_is_0to7(c) || c != pattern[1])
+ goto out;
+ /* filename[2] != '\0' */
+ c = filename[3];
+ /* pattern[1] != '\0' */
+ if (!tmy_char_is_0to7(c) || c != pattern[2])
+ goto out;
+ /* filename[3] != '\0' */
+ filename += 3;
+ /* pattern[2] != '\0' */
+ pattern += 2;
+ break;
+ case '*':
+ case '@':
+ for (i = 0; i <= filename_end - filename; i++) {
+ if (tmy_file_match2(filename + i,
+ filename_end,
+ pattern + 1,
+ pattern_end))
+ return 1;
+ c = filename[i];
+ if (c == '.' && *pattern == '@')
+ break;
+ if (c != '\\')
+ continue;
+ /* filename[i] != '\0' */
+ c = filename[i + 1];
+ if (c == '\\')
+ /* filename[i + 1] != '\0' */
+ i++;
+ else if (tmy_char_is_0to3(c) &&
+ /* filename[i + 1] != '\0' */
+ tmy_char_is_0to7(filename[i + 2]) &&
+ /* filename[i + 2] != '\0' */
+ tmy_char_is_0to7(filename[i + 3]))
+ /* filename[i + 3] != '\0' */
+ i += 3;
+ else
+ break; /* Bad pattern. */
+ }
+ goto out;
+ default:
+ j = 0;
+ /*
+ * If j == 0, for() loop does nothing.
+ * So I can use while() without checking first letter.
+ */
+ c = *pattern;
+ if (c == '$')
+ while (tmy_char_is_0to9(filename[j]))
+ j++;
+ else if (c == 'X')
+ while (tmy_char_is_hex(filename[j]))
+ j++;
+ else if (c == 'A')
+ while (tmy_char_is_alpha(filename[j]))
+ j++;
+ for (i = 1; i <= j; i++)
+ if (tmy_file_match2(filename + i,
+ filename_end,
+ pattern + 1,
+ pattern_end))
+ return 1;
+ goto out; /* Not matched or bad pattern. */
+ }
+ filename++; /* filename[0] != '\0' */
+ pattern++; /* pattern[0] != '\0' */
+ }
+
+ /* Skip trailing "\*" and "\@" patterns. */
+ while (*pattern == '\\' &&
+ (*(pattern + 1) == '*' ||
+ *(pattern + 1) == '@'))
+ pattern += 2;
+
+ /* If both are at ending position, they are equals. */
+ return (filename == filename_end && pattern == pattern_end);
+out: ;
+ return 0;
+}
+
+/**
+ * tmy_file_match - internal function for tmy_path_match().
+ * @filename: start address of the token.
+ * @filename_end: end address of the token.
+ * @pattern: start address of the pattern.
+ * @pattern_end: end address of the pattern.
+ *
+ * Handle patterns containing '\-' pattern.
+ * Returns nonzero if matches.
+ * Returns zero otherwise.
+ *
+ * @filename and @pattern do not contain '/'.
+ * @filename and @pattern are not '\0'-terminated.
+ */
+static int tmy_file_match(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end)
+{
+ const char *pattern_start = pattern;
+ int first = 1;
+ int result;
+
+ while (pattern < pattern_end - 1) {
+ /* Split at "\-" pattern. */
+ if (*pattern++ != '\\' || *pattern++ != '-')
+ continue;
+ result = tmy_file_match2(filename,
+ filename_end,
+ pattern_start,
+ pattern - 2);
+ /* If before "\-" pattern, invert the result. */
+ if (first)
+ result = !result;
+ /*
+ * If not matched before first "\-" pattern, return 0.
+ * If matched after first "\-" pattern, return 0.
+ */
+ if (result)
+ return 0;
+ first = 0;
+ pattern_start = pattern;
+ }
+
+ result = tmy_file_match2(filename,
+ filename_end, pattern_start, pattern_end);
+ /* If after first "\-" pattern, invert the result. */
+ return first ? result : !result;
+}
+
+/**
+ * tmy_path_match - do a pattern matching.
+ * @pathname0: pointer to a token to compare.
+ * @pattern0: pointer to a pattern to compare.
+ *
+ * Returns nonzero if @pathname0 matches to @pattern0 .
+ * Returns zero otherwise.
+ *
+ *
+ * Check whether the given token matches to the given pattern.
+ *
+ * The following patterns are available.
+ * \\ \ itself.
+ * \ooo Octal representation of a byte.
+ * \* More than or equals to 0 character other than '/'.
+ * \@ More than or equals to 0 character other than '/' or '.'.
+ * \? 1 byte character other than '/'.
+ * \$ More than or equals to 1 decimal digit.
+ * \+ 1 decimal digit.
+ * \X More than or equals to 1 hexadecimal digit.
+ * \x 1 hexadecimal digit.
+ * \A More than or equals to 1 alphabet character.
+ * \a 1 alphabet character.
+ * \- Subtraction operator.
+ */
+int tmy_path_match(const struct path_info *pathname0,
+ const struct path_info *pattern0)
+{
+ const char *pathname = pathname0->name;
+ const char *pattern = pattern0->name;
+ const int len = pattern0->const_len;
+
+ /* If it doesn't contains patterns, I can use tmy_pathcmp() */
+ if (!pattern0->is_patterned)
+ return !tmy_pathcmp(pathname0, pattern0);
+ /* Check the depth of directory. */
+ if (pathname0->depth != pattern0->depth)
+ return 0;
+ /* Use strncmp() for constant part. */
+ if (strncmp(pathname, pattern, len))
+ return 0;
+
+ pathname += len;
+ pattern += len;
+
+ /* Split at '/' character, and compare. */
+ while (*pathname && *pattern) {
+ const char *pathname_delimiter = strchr(pathname, '/');
+ const char *pattern_delimiter = strchr(pattern, '/');
+
+ if (!pathname_delimiter)
+ pathname_delimiter = strchr(pathname, '\0');
+ if (!pattern_delimiter)
+ pattern_delimiter = strchr(pattern, '\0');
+ if (!tmy_file_match(pathname,
+ pathname_delimiter,
+ pattern,
+ pattern_delimiter))
+ return 0;
+
+ pathname = *pathname_delimiter ?
+ pathname_delimiter + 1 :
+ pathname_delimiter;
+ pattern = *pattern_delimiter ?
+ pattern_delimiter + 1 :
+ pattern_delimiter;
+ }
+
+ /* Skip trailing "\*" and "\@" patterns. */
+ while (*pattern == '\\' &&
+ (*(pattern + 1) == '*' ||
+ *(pattern + 1) == '@'))
+ pattern += 2;
+
+ /* If both are at '\0' position, they are equals. */
+ return (!*pathname && !*pattern);
+}
+
+/**
+ * tmy_io_printf - transactional printf() for "struct io_buffer".
+ * @head: pointer to "struct io_buffer".
+ * @fmt: format strings for printf().
+ *
+ * Returns zero on success.
+ * Returns nonzero otherwise.
+ *
+ * Transactional printf() to "struct io_buffer" structure.
+ * snprintf() will truncate, but tmy_io_printf() won't.
+ * This is needed because dumping partially truncated tokens
+ * is not acceptable for TOMOYO Linux.
+ */
+int tmy_io_printf(struct io_buffer *head, const char *fmt, ...)
+{
+ va_list args;
+ int len;
+ int pos = head->read_avail;
+ int size = head->readbuf_size - pos;
+
+ if (size <= 0)
+ return -ENOMEM;
+
+ va_start(args, fmt);
+ len = vsnprintf(head->read_buf + pos, size, fmt, args);
+ va_end(args);
+
+ if (pos + len >= head->readbuf_size)
+ return -ENOMEM;
+
+ head->read_avail += len;
+
+ return 0;
+}
+
+/**
+ * tmy_get_exe - get realpath of current process.
+ *
+ * Returns realpath(3) of current process on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so caller must tmy_free()
+ * if this function didn't return NULL.
+ */
+const char *tmy_get_exe(void)
+{
+ struct vm_area_struct *vma;
+
+ if (!current->mm)
+ return NULL;
+ for (vma = current->mm->mmap; vma; vma = vma->vm_next)
+ if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file)
+ return tmy_realpath_dentry(vma->vm_file->f_dentry,
+ vma->vm_file->f_vfsmnt);
+
+ return NULL;
+}
+
+/************************* DOMAIN POLICY HANDLER *************************/
+
+/**
+ * tmy_flags - get flags of given access control.
+ * @index: index to retrieve flags.
+ *
+ * Returns current value of given access control.
+ */
+unsigned int tmy_flags(const unsigned int index)
+{
+ const u8 profile =
+ TMY_SECURITY->domain->profile;
+
+ /* All operations might sleep. See tmy_supervisor(). */
+ might_sleep();
+
+ return sbin_init_started && index < TMY_MAX_CONTROL_INDEX
+ && profile_ptr[profile] ?
+ profile_ptr[profile]->value[index] :
+ 0;
+}
+
+/**
+ * tmy_audit_grant - get flags of auditing grant logs.
+ *
+ * Returns current value of auditing grant log flags.
+ */
+unsigned int tmy_audit_grant(void)
+{
+ return tmy_flags(TMY_AUDIT_GRANT);
+}
+
+/**
+ * tmy_audit_reject - get flags of auditing reject logs.
+ *
+ * Returns current value of auditing reject log flags.
+ */
+unsigned int tmy_audit_reject(void)
+{
+ return tmy_flags(TMY_AUDIT_REJECT);
+}
+
+/**
+ * tmy_enforce - check whether the given access control is enforce mode.
+ * @index: index to check.
+ *
+ * Returns nonzero if given access control is enforce mode.
+ * Returns zero otherwise.
+ */
+unsigned int tmy_enforce(const unsigned int index)
+{
+ return tmy_flags(index) == 3;
+}
+
+/**
+ * tmy_accept - check whether the given access control is learning mode.
+ * @index: index to check.
+ *
+ * Returns nonzero if given access control is learning mode.
+ * Returns zero otherwise.
+ */
+unsigned int tmy_accept(const unsigned int index)
+{
+ return tmy_flags(index) == 1;
+}
+
+/* Create a new profile. */
+static struct profile *tmy_find_new_profile(const unsigned int profile)
+{
+ static DECLARE_MUTEX(profile_lock);
+ struct profile *ptr = NULL;
+
+ down(&profile_lock);
+
+ ptr = profile_ptr[profile];
+ if (profile < TMY_MAX_PROFILES && ptr == NULL) {
+ ptr = tmy_alloc_element(sizeof(*ptr));
+ if (ptr != NULL) {
+ int i;
+ for (i = 0; i < TMY_MAX_CONTROL_INDEX; i++)
+ ptr->value[i] = tmy_control[i].current_value;
+ mb(); /* Instead of using spinlock. */
+ profile_ptr[profile] = ptr;
+ }
+ }
+
+ up(&profile_lock);
+
+ return ptr;
+}
+
+/* Loading policy done? */
+static int profile_loaded;
+
+/* Update profile values. */
+static int tmy_set_profile(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ unsigned int i;
+ unsigned int value;
+ char *cp;
+ struct profile *profile;
+
+ if (!tmy_is_root())
+ return -EPERM;
+
+ /* If profile index is not given, assume 0. */
+ i = simple_strtoul(data, &cp, 10);
+ if (data != cp) {
+ if (*cp != '-')
+ return -EINVAL;
+ data = cp + 1;
+ }
+
+ profile = tmy_find_new_profile(i);
+ if (!profile)
+ return -EINVAL;
+ cp = strchr(data, '=');
+ if (!cp)
+ return -EINVAL;
+
+ *cp = '\0';
+ profile_loaded = 1;
+ tmy_update_counter(TMY_UPDATE_PROFILE);
+ if (strcmp(data, tmy_control[TMY_COMMENT].keyword) == 0) {
+ profile->comment = tmy_save_name(cp + 1);
+ return 0;
+ }
+
+ if (sscanf(cp + 1, "%u", &value) != 1)
+ return -EINVAL;
+
+ for (i = 0; i < TMY_MAX_CONTROL_INDEX; i++) {
+ if (strcmp(data, tmy_control[i].keyword))
+ continue;
+ if (value > tmy_control[i].max_value)
+ value = tmy_control[i].max_value;
+ profile->value[i] = value;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/* Read profile values. */
+static int tmy_read_profile(struct io_buffer *head)
+{
+ int step;
+
+ if (head->read_eof)
+ return 0;
+ if (!tmy_is_root())
+ return -EPERM;
+
+ for (step = head->read_step;
+ step < TMY_MAX_PROFILES * TMY_MAX_CONTROL_INDEX;
+ step++) {
+ const int i = step / TMY_MAX_CONTROL_INDEX;
+ const int j = step % TMY_MAX_CONTROL_INDEX;
+ const struct profile *profile = profile_ptr[i];
+
+ head->read_step = step;
+ if (!profile)
+ continue;
+ if (j != TMY_COMMENT)
+ goto non_comment;
+ if (tmy_io_printf(head, "%u-%s=%s\n",
+ i, tmy_control[TMY_COMMENT].keyword,
+ profile->comment ?
+ profile->comment->name : ""))
+ break;
+ goto comment_ok;
+non_comment: ;
+ if (tmy_io_printf(head, "%u-%s=%u\n",
+ i, tmy_control[j].keyword,
+ profile->value[j]))
+ break;
+comment_ok: ;
+ }
+
+ if (step == TMY_MAX_PROFILES * TMY_MAX_CONTROL_INDEX)
+ head->read_eof = 1;
+
+ return 0;
+}
+
+/************************* POLICY MANAGER HANDLER *************************/
+
+struct policy_manager_entry {
+ struct policy_manager_entry *next;
+ const struct path_info *manager;
+ u8 is_domain;
+ u8 is_deleted;
+};
+
+static struct policy_manager_entry *policy_manager_list;
+
+/* Update manager list. */
+static int tmy_add_manager_entry(const char *manager, u8 is_delete)
+{
+ struct policy_manager_entry *new_entry;
+ struct policy_manager_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved_manager;
+ int error = -ENOMEM;
+
+ u8 is_domain = 0;
+ if (!tmy_is_root())
+ return -EPERM;
+ if (tmy_is_domain_def(manager)) {
+ if (!tmy_is_correct_domain(manager, __FUNCTION__))
+ return -EINVAL;
+ is_domain = 1;
+ } else {
+ if (!tmy_correct_path(manager, 1, -1, -1, __FUNCTION__))
+ return -EINVAL;
+ }
+
+ saved_manager = tmy_save_name(manager);
+ if (saved_manager == NULL)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = policy_manager_list; ptr; ptr = ptr->next) {
+ if (ptr->manager == saved_manager) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (new_entry == NULL)
+ goto out;
+ new_entry->manager = saved_manager;
+ new_entry->is_domain = is_domain;
+ mb(); /* Instead of using spinlock. */
+ ptr = policy_manager_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ policy_manager_list = new_entry;
+ error = 0;
+out: ;
+
+ up(&lock);
+
+ if (!error)
+ tmy_update_counter(TMY_UPDATE_MANAGER);
+ return error;
+}
+
+/* Update manager list. */
+static int tmy_add_manager_policy(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ int is_delete = 0;
+
+ if (!tmy_is_root())
+ return -EPERM;
+ if (tmy_strstarts(&data, TMY_DELETE))
+ is_delete = 1;
+ return tmy_add_manager_entry(data, is_delete);
+}
+
+/* Read manager list.*/
+static int tmy_read_manager_policy(struct io_buffer *head)
+{
+ struct policy_manager_entry *ptr = head->read_var2;
+ if (head->read_eof)
+ return 0;
+ if (!tmy_is_root())
+ return -EPERM;
+ if (!ptr)
+ ptr = policy_manager_list;
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted &&
+ tmy_io_printf(head, "%s\n", ptr->manager->name))
+ break;
+ ptr = ptr->next;
+ }
+ if (!ptr)
+ head->read_eof = 1;
+ return 0;
+}
+
+/**
+ * tmy_is_policy_manager - check whether modifying policy is permitted.
+ *
+ * Returns nonzero if modifying policy is permitted.
+ * Returns zero otherwise.
+ *
+ * Only programs or domains registers as manager are permitted to modify
+ * policy via /proc/tomoyo/ interface.
+ * This function checks whether the current process is a policy manager.
+ * But policy manager is not the only processes that can modify policy;
+ * other process are permitted to add policy
+ * if their domains are assigned a profile for learning mode.
+ */
+static int tmy_is_policy_manager(void)
+{
+ struct policy_manager_entry *ptr;
+ const char *exe;
+ const struct path_info *domainname =
+ TMY_SECURITY->domain->domainname;
+
+ /* Everyone can modify policy before /sbin/init starts. */
+ if (!sbin_init_started)
+ return 1;
+
+ for (ptr = policy_manager_list; ptr; ptr = ptr->next) {
+ if (!ptr->is_deleted &&
+ ptr->is_domain &&
+ !tmy_pathcmp(domainname, ptr->manager))
+ return 1;
+ }
+
+ exe = tmy_get_exe();
+ if (!exe)
+ return 0;
+
+ for (ptr = policy_manager_list; ptr; ptr = ptr->next) {
+ if (!ptr->is_deleted &&
+ !ptr->is_domain &&
+ !strcmp(exe, ptr->manager->name))
+ break;
+ }
+
+ if (!ptr) { /* Reduce error messages. */
+ static pid_t last_pid;
+ const pid_t pid = current->pid;
+ if (last_pid != pid) {
+ printk(KERN_INFO
+ "%s is not permitted to update policies.\n",
+ exe);
+ last_pid = pid;
+ }
+ }
+
+ tmy_free(exe);
+ return ptr ? 1 : 0;
+}
+
+/************************* DOMAIN POLICY HANDLER *************************/
+
+/* Update domain policy. */
+static int tmy_add_domain_policy(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ char *cp;
+ const struct condition_list *cond = NULL;
+ struct domain_info *domain = head->write_var1;
+ int is_delete = 0;
+ int is_select = 0;
+ int is_undelete = 0;
+ unsigned int profile;
+
+ if (!tmy_is_root())
+ return -EPERM;
+
+ if (tmy_strstarts(&data, TMY_DELETE))
+ is_delete = 1;
+ else if (tmy_strstarts(&data, TMY_SELECT))
+ is_select = 1;
+ else if (tmy_strstarts(&data, TMY_UNDELETE))
+ is_undelete = 1;
+
+ tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+
+ if (tmy_is_domain_def(data)) {
+ if (is_delete) {
+ tmy_delete_domain(data);
+ domain = NULL;
+ } else if (is_select)
+ domain = tmy_find_domain(data);
+ else if (is_undelete)
+ domain = tmy_undelete_domain(data);
+ else
+ domain = tmy_new_domain(data, 0);
+ head->write_var1 = domain;
+ return 0;
+ }
+
+ if (!domain)
+ return -EINVAL;
+
+ if (sscanf(data, TMY_USE_PROFILE "%u", &profile) == 1 &&
+ profile < TMY_MAX_PROFILES) {
+ if (profile_ptr[profile] || !sbin_init_started)
+ domain->profile = (u8) profile;
+ return 0;
+ }
+ cp = tmy_find_condition_part(data);
+ if (cp) {
+ cond = tmy_assign_condition(cp);
+ if (!cond)
+ return -EINVAL;
+ }
+ if (tmy_strstarts(&data, TMY_ALLOW_NETWORK))
+ return tmy_add_network_policy(data, domain, cond, is_delete);
+ else if (tmy_strstarts(&data, TMY_ALLOW_ARGV0))
+ return tmy_add_argv0_policy(data, domain, cond, is_delete);
+ else if (tmy_strstarts(&data, TMY_ALLOW_SIGNAL))
+ return tmy_add_signal_policy(data, domain, cond, is_delete);
+ else
+ return tmy_add_file_policy(data, domain, cond, is_delete);
+
+ return -EINVAL;
+}
+
+/* Dump file's open ACL. */
+static inline int print_file_rwx_acl(struct io_buffer *head,
+ struct acl_info *ptr)
+{
+ struct file_acl *ptr2 = (struct file_acl *) ptr;
+ const unsigned char b = ptr2->u_is_group;
+
+ if (tmy_io_printf(head, "%d %s%s",
+ ptr2->perm, b ? "@" : "",
+ b ? ptr2->u.group->group_name->name :
+ ptr2->u.filename->name))
+ return -ENOMEM;
+ return 0;
+}
+
+/* Dump argv[0]'s ACL. */
+static inline int print_argv0_acl(struct io_buffer *head,
+ struct acl_info *ptr)
+{
+ struct argv0_acl *ptr2 = (struct argv0_acl *) ptr;
+
+ if (tmy_io_printf(head, TMY_ALLOW_ARGV0 "%s %s",
+ ptr2->filename->name, ptr2->argv0->name))
+ return -ENOMEM;
+ return 0;
+}
+
+/* Dump network's ACL. */
+static inline int print_network_acl(struct io_buffer *head,
+ struct acl_info *ptr)
+{
+ struct net_acl *ptr2 = (struct net_acl *) ptr;
+ u8 record_type = ptr2->record_type;
+ u32 min_address;
+ u32 max_address;
+ char buf[64];
+ const u16 *min_address_ptr;
+ const u16 *max_address_ptr;
+ u16 min_port;
+ u16 max_port;
+
+ if (tmy_io_printf(head, TMY_ALLOW_NETWORK "%s ",
+ tmy_network2keyword(ptr2->operation_type)))
+ goto out;
+ if (record_type != TMY_TYPE_ADDRESS_GROUP)
+ goto non_address_group;
+
+ if (tmy_io_printf(head, "@%s", ptr2->u.group->group_name->name))
+ goto out;
+ goto print_port;
+
+non_address_group: ;
+ if (record_type != TMY_TYPE_IPv4)
+ goto ipv6_address;
+
+ min_address = ptr2->u.ipv4.min;
+ max_address = ptr2->u.ipv4.max;
+ if (tmy_io_printf(head, NIPQUAD_FMT, HIPQUAD(min_address)))
+ goto out;
+ if (min_address != max_address &&
+ tmy_io_printf(head, "-" NIPQUAD_FMT, HIPQUAD(max_address)))
+ goto out;
+ goto print_port;
+
+ipv6_address: ;
+ min_address_ptr = ptr2->u.ipv6.min;
+ max_address_ptr = ptr2->u.ipv6.max;
+ tmy_print_ipv6(buf, sizeof(buf), min_address_ptr);
+ if (tmy_io_printf(head, "%s", buf))
+ goto out;
+ if (memcmp(min_address_ptr, max_address_ptr, 16)) {
+ tmy_print_ipv6(buf, sizeof(buf), max_address_ptr);
+ if (tmy_io_printf(head, "-%s", buf))
+ goto out;
+ }
+
+print_port: ;
+ min_port = ptr2->min_port;
+ max_port = ptr2->max_port;
+ if (tmy_io_printf(head, " %u", min_port))
+ goto out;
+ if (min_port != max_port && tmy_io_printf(head, "-%u", max_port))
+ goto out;
+ return 0;
+out: ;
+ return -ENOMEM;
+}
+
+/* Dump signal's ACL. */
+static inline int print_signal_acl(struct io_buffer *head,
+ struct acl_info *ptr)
+{
+ struct signal_acl *ptr2 = (struct signal_acl *) ptr;
+
+ if (tmy_io_printf(head, TMY_ALLOW_SIGNAL "%u %s",
+ ptr2->sig, ptr2->domainname->name))
+ return -ENOMEM;
+ return 0;
+}
+
+/* Dump file's non-open ACL. */
+static inline int print_file_other_acl(struct io_buffer *head,
+ struct acl_info *ptr)
+{
+ const u8 acl_type = ptr->type;
+ const char *keyword = tmy_acltype2keyword(acl_type);
+
+ if (!keyword)
+ return 0; /* This must not happen. */
+
+ if (tmy_acltype2paths(acl_type) == 2) {
+ struct double_acl *ptr2 = (struct double_acl *) ptr;
+ const u8 b1 = ptr2->u1_is_group;
+ const u8 b2 = ptr2->u2_is_group;
+
+ if (tmy_io_printf(head,
+ "allow_%s %s%s %s%s",
+ keyword,
+ b1 ? "@" : "",
+ b1 ? ptr2->u1.group1->group_name->name :
+ ptr2->u1.filename1->name,
+ b2 ? "@" : "",
+ b2 ? ptr2->u2.group2->group_name->name :
+ ptr2->u2.filename2->name))
+ return -ENOMEM;
+ } else {
+ struct single_acl *ptr2 = (struct single_acl *) ptr;
+ const u8 b = ptr2->u_is_group;
+
+ if (tmy_io_printf(head,
+ "allow_%s %s%s",
+ keyword,
+ b ? "@" : "",
+ b ? ptr2->u.group->group_name->name :
+ ptr2->u.filename->name))
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/* Read domain policy. */
+static int tmy_read_domain_policy(struct io_buffer *head)
+{
+ struct domain_info *domain = head->read_var1;
+
+ if (head->read_eof)
+ return 0;
+ switch (head->read_step) {
+ case 0: break; /* Resume from printing domainname. */
+ case 1: goto step1; /* Resume from printing profile number. */
+ case 2: goto step2; /* Resume from printing ACL entries. */
+ case 3: goto step3; /* Resume from printing trailing '\n'. */
+ default: return -EINVAL;
+ }
+
+ if (!tmy_is_root())
+ return -EPERM;
+
+ for (domain = &KERNEL_DOMAIN; domain; domain = domain->next) {
+ struct acl_info *ptr;
+ if (domain->is_deleted)
+ continue;
+ head->read_var1 = domain;
+ head->read_var2 = NULL;
+ head->read_step = 1;
+
+step1: ;
+ if (tmy_io_printf(head,
+ "%s\n" TMY_USE_PROFILE "%u\n%s\n",
+ domain->domainname->name,
+ domain->profile,
+ domain->quota_warned ?
+ "quota_exceeded\n" : ""))
+ break;
+ head->read_var2 = domain->first_acl_ptr;
+ head->read_step = 2;
+
+step2: ;
+ for (ptr = head->read_var2; ptr; ptr = ptr->next) {
+ const u8 acl_type = ptr->type;
+ const int pos = head->read_avail;
+ head->read_var2 = ptr;
+ if (ptr->is_deleted)
+ continue;
+ if (acl_type == TMY_TYPE_FILE_ACL) {
+ if (print_file_rwx_acl(head, ptr))
+ goto print_acl_rollback;
+ } else if (acl_type == TMY_TYPE_ARGV0_ACL) {
+ if (print_argv0_acl(head, ptr))
+ goto print_acl_rollback;
+ } else if (acl_type == TMY_TYPE_IP_NETWORK_ACL) {
+ if (print_network_acl(head, ptr))
+ goto print_acl_rollback;
+ } else if (acl_type == TMY_TYPE_SIGNAL_ACL) {
+ if (print_signal_acl(head, ptr))
+ goto print_acl_rollback;
+ } else
+ if (print_file_other_acl(head, ptr))
+ goto print_acl_rollback;
+ if (tmy_dump_condition(head, ptr->cond)) {
+print_acl_rollback: ;
+ head->read_avail = pos;
+ break;
+ }
+ }
+ if (ptr)
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 3;
+
+step3: ;
+ if (tmy_io_printf(head, "\n"))
+ break;
+ }
+
+ if (!domain)
+ head->read_eof = 1;
+ return 0;
+}
+
+/* Read domainname and its profile values. */
+static int tmy_read_domain_profile(struct io_buffer *head)
+{
+ struct domain_info *domain;
+
+ if (head->read_eof)
+ return 0;
+
+ if (head->read_step == 0) {
+ head->read_var1 = &KERNEL_DOMAIN;
+ head->read_step = 1;
+ }
+ if (!tmy_is_root())
+ return -EPERM;
+
+ for (domain = head->read_var1; domain; domain = domain->next) {
+ if (domain->is_deleted)
+ continue;
+ head->read_var1 = domain;
+ if (tmy_io_printf(head, "%u %s\n",
+ domain->profile, domain->domainname->name))
+ break;
+ }
+ if (!domain)
+ head->read_eof = 1;
+
+ return 0;
+}
+
+/* Set PID to report. Non manager process is permitted to call this function. */
+static int tmy_write_pid(struct io_buffer *head)
+{
+ head->read_step = (int) simple_strtoul(head->write_buf, NULL, 10);
+ head->read_eof = 0;
+ return 0;
+}
+
+/* Read domainname and its profile values. */
+static int tmy_read_pid(struct io_buffer *head)
+{
+ const int pid = head->read_step;
+ struct task_struct *p;
+ struct domain_info *domain = NULL;
+
+ /* If PID is not set via tmy_write_pid(), do nothing. */
+ if (head->read_avail || head->read_eof)
+ return 0;
+
+ /***** CRITICAL SECTION START *****/
+ read_lock(&tasklist_lock);
+ p = find_task_by_pid(pid);
+ if (p) {
+ /* "struct task_struct"->security is not NULL. */
+ domain = ((struct tmy_security *) p->security)->domain;
+ }
+ read_unlock(&tasklist_lock);
+ /***** CRITICAL SECTION END *****/
+
+ if (domain)
+ tmy_io_printf(head, "%d %u %s",
+ pid, domain->profile, domain->domainname->name);
+ head->read_eof = 1;
+
+ return 0;
+}
+
+/* Update profile values for domains. */
+static int tmy_update_domain_profile(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ char *cp = strchr(data, ' ');
+ struct domain_info *domain;
+ unsigned int profile;
+
+ if (!tmy_is_root())
+ return -EPERM;
+ if (!cp)
+ return -EINVAL;
+
+ *cp = '\0';
+ domain = tmy_find_domain(cp + 1);
+ profile = simple_strtoul(data, NULL, 10);
+ if (domain && profile < TMY_MAX_PROFILES &&
+ (profile_ptr[profile] || !sbin_init_started))
+ domain->profile = (u8) profile;
+ tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+
+ return 0;
+}
+
+/************************* SYSTEM POLICY HANDLER *************************/
+
+/* Update system policy. */
+static int tmy_add_system_policy(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ int is_delete = 0;
+
+ if (!tmy_is_root())
+ return -EPERM;
+
+ tmy_update_counter(TMY_UPDATE_SYSTEMPOLICY);
+
+ if (tmy_strstarts(&data, TMY_DELETE))
+ is_delete = 1;
+ if (tmy_strstarts(&data, TMY_ALLOW_MOUNT))
+ return tmy_add_mount_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_DENY_UNMOUNT))
+ return tmy_add_no_umount_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_ALLOW_PIVOT_ROOT))
+ return tmy_add_pivot_root_policy(data, is_delete);
+
+ return -EINVAL;
+}
+
+/* Read system policy. */
+static int tmy_read_system_policy(struct io_buffer *head)
+{
+ if (head->read_eof)
+ return 0;
+
+ switch (head->read_step) {
+ case 0:
+ if (!tmy_is_root())
+ return -EPERM;
+ head->read_var2 = NULL; head->read_step = 1;
+ case 1:
+ if (tmy_read_mount_policy(head))
+ break;
+ head->read_var2 = NULL; head->read_step = 2;
+ case 2:
+ if (tmy_read_no_umount_policy(head))
+ break;
+ head->read_var2 = NULL; head->read_step = 3;
+ case 3:
+ if (tmy_read_pivot_root_policy(head))
+ break;
+ head->read_eof = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/************************* EXCEPTION POLICY HANDLER *************************/
+/* Update exception policy. */
+static int tmy_add_exception_policy(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ int is_delete = 0;
+
+ if (!tmy_is_root())
+ return -EPERM;
+
+ tmy_update_counter(TMY_UPDATE_EXCEPTIONPOLICY);
+
+ if (tmy_strstarts(&data, TMY_DELETE))
+ is_delete = 1;
+ if (tmy_strstarts(&data, TMY_KEEP_DOMAIN))
+ return tmy_add_domain_keeper_policy(data, 0, is_delete);
+ if (tmy_strstarts(&data, TMY_NO_KEEP_DOMAIN))
+ return tmy_add_domain_keeper_policy(data, 1, is_delete);
+ if (tmy_strstarts(&data, TMY_INITIALIZE_DOMAIN))
+ return tmy_add_domain_initializer_policy(data, 0, is_delete);
+ if (tmy_strstarts(&data, TMY_NO_INITIALIZE_DOMAIN))
+ return tmy_add_domain_initializer_policy(data, 1, is_delete);
+ if (tmy_strstarts(&data, TMY_ALIAS))
+ return tmy_add_alias_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_AGGREGATOR))
+ return tmy_add_aggregator_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_ALLOW_READ))
+ return tmy_add_globally_readable_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_FILE_PATTERN))
+ return tmy_add_pattern_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_PATH_GROUP))
+ return tmy_add_group_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_DENY_REWRITE))
+ return tmy_add_no_rewrite_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_ADDRESS_GROUP))
+ return tmy_add_addr_group_policy(data, is_delete);
+
+ return -EINVAL;
+}
+
+/* Read exception policy. */
+static int tmy_read_exception_policy(struct io_buffer *head)
+{
+ if (head->read_eof)
+ return 0;
+
+ switch (head->read_step) {
+ case 0:
+ if (!tmy_is_root())
+ return -EPERM;
+ head->read_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:
+ if (tmy_read_domain_initializer_policy(head))
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 4;
+ case 4:
+ if (tmy_read_alias_policy(head))
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 5;
+ case 5:
+ if (tmy_read_aggregator_policy(head))
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 6;
+ case 6:
+ if (tmy_read_pattern_policy(head))
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 7;
+ case 7:
+ if (tmy_read_no_rewrite_policy(head))
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 8;
+ case 8:
+ if (tmy_read_group_policy(head))
+ break;
+ head->read_var1 = NULL;
+ head->read_var2 = NULL;
+ head->read_step = 9;
+ case 9:
+ if (tmy_read_addr_group_policy(head))
+ break;
+ head->read_eof = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/************************* POLICY LOADER *************************/
+
+/**
+ * tmy_load_policy - load policy and activate access control.
+ * @filename: program to be executed.
+ *
+ * This function calls /sbin/tomoyo-init using call_usermodehelper()
+ * to load policy
+ * if "execution of /sbin/init is requested" and "/sbin/tomoyo-init exists".
+ */
+void tmy_load_policy(const char *filename)
+{
+ if (sbin_init_started)
+ return;
+ if (strcmp(filename, "/sbin/init") != 0)
+ return;
+
+ /*
+ * Don't activate MAC if the path '/sbin/tomoyo-init' doesn't exist.
+ * If initrd.img includes /sbin/init but real-root-dev has not
+ * mounted on / yet, activating MAC will block the system
+ * since policies are not loaded yet.
+ * So let do_execve() call this function everytime.
+ */
+ if (!profile_loaded) {
+ const char *tmy_loader = "/sbin/tomoyo-init";
+ struct nameidata nd;
+ char *argv[2];
+ char *envp[3];
+
+ if (path_lookup(tmy_loader, LOOKUP_FOLLOW, &nd)) {
+ printk("TOMOYO: Not activating Mandatory Access Control"
+ " now since %s doesn't exist.\n", tmy_loader);
+ return;
+ }
+ path_release(&nd);
+ argv[0] = (char *) tmy_loader;
+ argv[1] = NULL;
+ envp[0] = "HOME=/";
+ envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+ envp[2] = NULL;
+ call_usermodehelper(argv[0], argv, envp, 1);
+ }
+
+ printk(KERN_INFO "TOMOYO: %s 2007/08/21\n", TOMOYO_VERSION_CODE);
+
+ if (!profile_loaded)
+ panic("TOMOYO: No profiles loaded. "
+ "Run policy loader using 'init=' option.\n");
+
+ printk(KERN_INFO "TOMOYO: Mandatory Access Control activated.\n");
+ sbin_init_started = 1;
+
+ { /* Check all profiles currently assigned to domains are defined. */
+ struct domain_info *domain;
+ for (domain = &KERNEL_DOMAIN; domain; domain = domain->next) {
+ const u8 profile = domain->profile;
+ if (profile_ptr[profile])
+ continue;
+ panic("TOMOYO: Profile %u (used by '%s') "
+ "not defined.\n",
+ profile,
+ domain->domainname->name);
+ }
+ }
+}
+
+
+/************************* MAC Decision Delayer *************************/
+
+static DECLARE_WAIT_QUEUE_HEAD(query_wait);
+
+static spinlock_t query_lock = SPIN_LOCK_UNLOCKED;
+
+struct query_entry {
+ struct list_head list;
+ char *query;
+ int query_len;
+ unsigned int serial;
+ int timer;
+ int answer;
+};
+
+static LIST_HEAD(query_list);
+static atomic_t queryd_watcher = ATOMIC_INIT(0);
+
+/**
+ * tmy_supervisor - ask for supervisors decision.
+ * @fmt: format strings for printf().
+ *
+ * Returns zero if administrator allowed.
+ * Returns nonzero otherwise.
+ *
+ * This is one of TOMOYO Linux's feature.
+ * Normally, access requests that violates policy is rejected immediately.
+ * But this behavior is inconvenient when developing policy.
+ * TOMOYO Linux allows administrators handle access requests that violated
+ * policy in enforce mode to adjust policy.
+ *
+ * This function blocks a process who is requesting access that violated policy
+ * and tell it via /proc/tomoyo/query and wait for supervisor's decision.
+ */
+int tmy_supervisor(const char *fmt, ...)
+{
+ va_list args;
+ int error = -EPERM;
+ int pos;
+ int len;
+ static unsigned int serial;
+ struct query_entry *query_entry;
+
+ if (!tmy_flags(TMY_ALLOW_ENFORCE_GRACE))
+ return -EPERM;
+ if (!atomic_read(&queryd_watcher))
+ return -EPERM;
+
+ va_start(args, fmt);
+ len = vsnprintf((char *) &pos, sizeof(pos) - 1, fmt, args) + 32;
+ va_end(args);
+
+ query_entry = tmy_alloc(sizeof(*query_entry));
+ if (!query_entry)
+ goto out;
+ query_entry->query = tmy_alloc(len);
+ if (!query_entry->query)
+ goto out;
+
+ INIT_LIST_HEAD(&query_entry->list);
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&query_lock);
+ query_entry->serial = serial++;
+ spin_unlock(&query_lock);
+ /***** CRITICAL SECTION END *****/
+
+ pos = snprintf(query_entry->query, len - 1,
+ "Q%u\n", query_entry->serial);
+ va_start(args, fmt);
+ vsnprintf(query_entry->query + pos, len - 1 - pos, fmt, args);
+ query_entry->query_len = strlen(query_entry->query) + 1;
+ va_end(args);
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&query_lock);
+ list_add_tail(&query_entry->list, &query_list);
+ spin_unlock(&query_lock);
+ /***** CRITICAL SECTION END *****/
+
+ tmy_update_counter(TMY_UPDATE_QUERY);
+
+ /* Give 10 seconds for supervisor's opinion. */
+ for (query_entry->timer = 0;
+ atomic_read(&queryd_watcher) &&
+ tmy_flags(TMY_ALLOW_ENFORCE_GRACE) &&
+ query_entry->timer < 100;
+ query_entry->timer++) {
+ wake_up(&query_wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(HZ / 10);
+ if (query_entry->answer)
+ break;
+ }
+
+ tmy_update_counter(TMY_UPDATE_QUERY);
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&query_lock);
+ list_del(&query_entry->list);
+ spin_unlock(&query_lock);
+ /***** CRITICAL SECTION END *****/
+
+ switch (query_entry->answer) {
+ case 1:
+ /* Granted by administrator. */
+ error = 0;
+ break;
+ case 0:
+ /* Timed out. */
+ break;
+ default:
+ /* Rejected by administrator. */
+ break;
+ }
+
+out: ;
+ if (query_entry)
+ tmy_free(query_entry->query);
+ tmy_free(query_entry);
+ return error;
+}
+
+/* Check for pending access requests. */
+static int tmy_poll_query(struct file *file, poll_table *wait)
+{
+ int found;
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&query_lock);
+ found = !list_empty(&query_list);
+ spin_unlock(&query_lock);
+ /***** CRITICAL SECTION END *****/
+
+ if (found)
+ return POLLIN | POLLRDNORM;
+ poll_wait(file, &query_wait, wait);
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&query_lock);
+ found = !list_empty(&query_list);
+ spin_unlock(&query_lock);
+ /***** CRITICAL SECTION END *****/
+
+ if (found)
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+/* Read pending access requests. */
+static int tmy_read_query(struct io_buffer *head)
+{
+ struct list_head *tmp;
+ int pos = 0;
+ int len = 0;
+ char *buf;
+
+ if (head->read_avail)
+ return 0;
+ if (head->read_buf) {
+ tmy_free(head->read_buf);
+ head->read_buf = NULL;
+ head->readbuf_size = 0;
+ }
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&query_lock);
+ list_for_each(tmp, &query_list) {
+ struct query_entry *ptr
+ = list_entry(tmp, struct query_entry, list);
+ if (pos++ == head->read_step) {
+ len = ptr->query_len;
+ break;
+ }
+ }
+ spin_unlock(&query_lock);
+ /***** CRITICAL SECTION END *****/
+
+ if (!len) {
+ head->read_step = 0;
+ return 0;
+ }
+ buf = tmy_alloc(len);
+ if (buf) {
+ pos = 0;
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&query_lock);
+ list_for_each(tmp, &query_list) {
+ struct query_entry *ptr
+ = list_entry(tmp, struct query_entry, list);
+ if (pos++ == head->read_step) {
+ /* Some query can be skiipped because
+ * query_list can change, but I don't care.
+ */
+ if (len == ptr->query_len)
+ memmove(buf, ptr->query, len);
+ break;
+ }
+ }
+ spin_unlock(&query_lock);
+ /***** CRITICAL SECTION END *****/
+
+ if (buf[0]) {
+ head->readbuf_size = len;
+ head->read_avail = len;
+ head->read_buf = buf;
+ head->read_step++;
+ } else
+ tmy_free(buf);
+ }
+
+ return 0;
+}
+
+/* Reply to pending access requests. */
+static int tmy_write_answer(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ struct list_head *tmp;
+ unsigned int serial;
+ unsigned int answer;
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&query_lock);
+ list_for_each(tmp, &query_list) {
+ struct query_entry *ptr
+ = list_entry(tmp, struct query_entry, list);
+ ptr->timer = 0;
+ }
+ spin_unlock(&query_lock);
+ /***** CRITICAL SECTION END *****/
+
+ if (sscanf(data, "A%u=%u", &serial, &answer) != 2)
+ return -EINVAL;
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&query_lock);
+ list_for_each(tmp, &query_list) {
+ struct query_entry *ptr
+ = list_entry(tmp, struct query_entry, list);
+ if (ptr->serial != serial)
+ continue;
+ if (!ptr->answer)
+ ptr->answer = answer;
+ break;
+ }
+ spin_unlock(&query_lock);
+ /***** CRITICAL SECTION END *****/
+
+ return 0;
+}
+
+/************************* /proc INTERFACE HANDLER *************************/
+
+/* Policy updates counter. */
+static unsigned int updates_counter[TMY_MAX_UPDATES_COUNTER];
+static spinlock_t updates_counter_lock = SPIN_LOCK_UNLOCKED;
+
+/**
+ * tmy_update_counter - notify userland that policy is changed.
+ * @index: index to update counter.
+ *
+ * This is for userland process who is monitoring policy changes.
+ */
+void tmy_update_counter(const unsigned char index)
+{
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&updates_counter_lock);
+ if (index < TMY_MAX_UPDATES_COUNTER)
+ updates_counter[index]++;
+ spin_unlock(&updates_counter_lock);
+ /***** CRITICAL SECTION END *****/
+}
+
+/* Read policy update counter. */
+static int tmy_read_updates_counter(struct io_buffer *head)
+{
+ unsigned int counter[TMY_MAX_UPDATES_COUNTER];
+ if (head->read_eof)
+ return 0;
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&updates_counter_lock);
+ memmove(counter, updates_counter, sizeof(updates_counter));
+ memset(updates_counter, 0, sizeof(updates_counter));
+ spin_unlock(&updates_counter_lock);
+ /***** CRITICAL SECTION END *****/
+
+ tmy_io_printf(head,
+ "/proc/tomoyo/domain_policy: %10u\n"
+ "/proc/tomoyo/system_policy: %10u\n"
+ "/proc/tomoyo/exception_policy: %10u\n"
+ "/proc/tomoyo/profile: %10u\n"
+ "/proc/tomoyo/query: %10u\n"
+ "/proc/tomoyo/manager: %10u\n",
+ counter[TMY_UPDATE_DOMAINPOLICY],
+ counter[TMY_UPDATE_SYSTEMPOLICY],
+ counter[TMY_UPDATE_EXCEPTIONPOLICY],
+ counter[TMY_UPDATE_PROFILE],
+ counter[TMY_UPDATE_QUERY],
+ counter[TMY_UPDATE_MANAGER]);
+ head->read_eof = 1;
+
+ return 0;
+}
+
+/* Read how much memory is used. */
+static int tmy_read_memory_counter(struct io_buffer *head)
+{
+ int shared;
+ int private;
+ int dynamic;
+
+ if (head->read_eof)
+ return 0;
+ shared = tmy_get_memory_used_for_save_name();
+ private = tmy_get_memory_used_for_elements();
+ dynamic = tmy_get_memory_used_for_dynamic();
+ if (tmy_io_printf(head,
+ "Shared: %10u\n"
+ "Private: %10u\n"
+ "Dynamic: %10u\n"
+ "Total: %10u\n",
+ shared,
+ private,
+ dynamic,
+ shared + private + dynamic) == 0)
+ head->read_eof = 1;
+ return 0;
+}
+
+/* Read TOMOYO Linux's version. */
+static int tmy_read_version(struct io_buffer *head)
+{
+ if (!head->read_eof) {
+ tmy_io_printf(head, TOMOYO_VERSION_CODE "\n");
+ head->read_eof = 1;
+ }
+ return 0;
+}
+
+/* Read current process's domainname. */
+static int tmy_read_self_domain(struct io_buffer *head)
+{
+ if (!head->read_eof) {
+ tmy_io_printf(head,
+ "%s",
+ TMY_SECURITY->domain->domainname->name);
+ head->read_eof = 1;
+ }
+
+ return 0;
+}
+
+/* This is /proc/tomoyo/ interface. */
+static int tmy_open_control(const int type, struct file *file)
+{
+ struct io_buffer *head = tmy_alloc(sizeof(*head));
+
+ if (!head)
+ return -ENOMEM;
+ init_MUTEX(&head->read_sem);
+ init_MUTEX(&head->write_sem);
+
+ switch (type) {
+ case TMY_POLICY_DOMAINPOLICY:
+ head->write = tmy_add_domain_policy;
+ head->read = tmy_read_domain_policy;
+ break;
+ case TMY_POLICY_SYSTEMPOLICY:
+ head->write = tmy_add_system_policy;
+ head->read = tmy_read_system_policy;
+ break;
+ case TMY_POLICY_EXCEPTIONPOLICY:
+ head->write = tmy_add_exception_policy;
+ head->read = tmy_read_exception_policy;
+ break;
+ case TMY_POLICY_DOMAIN_STATUS:
+ head->write = tmy_update_domain_profile;
+ head->read = tmy_read_domain_profile;
+ break;
+ case TMY_INFO_PROCESS_STATUS:
+ head->write = tmy_write_pid;
+ head->read = tmy_read_pid;
+ break;
+ case TMY_INFO_SELFDOMAIN:
+ head->read = tmy_read_self_domain;
+ break;
+ case TMY_INFO_MEMINFO:
+ head->read = tmy_read_memory_counter;
+ head->readbuf_size = 128;
+ break;
+ case TMY_PROFILE:
+ head->write = tmy_set_profile;
+ head->read = tmy_read_profile;
+ break;
+ case TMY_POLICY_QUERY:
+ head->poll = tmy_poll_query;
+ head->write = tmy_write_answer;
+ head->read = tmy_read_query;
+ break;
+ case TMY_POLICY_MANAGER:
+ head->write = tmy_add_manager_policy;
+ head->read = tmy_read_manager_policy;
+ break;
+ case TMY_INFO_UPDATESCOUNTER:
+ head->read = tmy_read_updates_counter;
+ break;
+ case TMY_VERSION:
+ head->read = tmy_read_version;
+ break;
+ }
+
+ if (type != TMY_POLICY_QUERY) {
+ if (!head->readbuf_size)
+ head->readbuf_size = PAGE_SIZE * 2;
+ head->read_buf = tmy_alloc(head->readbuf_size);
+ if (!head->read_buf) {
+ tmy_free(head);
+ return -ENOMEM;
+ }
+ }
+
+ if (head->write) {
+ head->writebuf_size = PAGE_SIZE * 2;
+ head->write_buf = tmy_alloc(head->writebuf_size);
+ if (!head->write_buf) {
+ tmy_free(head->read_buf);
+ tmy_free(head);
+ return -ENOMEM;
+ }
+ }
+
+ file->private_data = head;
+
+ if (type == TMY_INFO_SELFDOMAIN)
+ tmy_read_control(file, NULL, 0);
+ else if (head->write == tmy_write_answer)
+ atomic_inc(&queryd_watcher);
+
+ return 0;
+}
+
+/* Copy read data to userland buffer. */
+static int tmy_copy_to_user(struct io_buffer *head, char __user *buffer,
+ int buffer_len)
+{
+ int len = head->read_avail;
+ char *cp = head->read_buf;
+
+ if (len > buffer_len)
+ len = buffer_len;
+ if (len) {
+ if (copy_to_user(buffer, cp, len))
+ return -EFAULT;
+ head->read_avail -= len;
+ memmove(cp, cp + len, head->read_avail);
+ }
+
+ return len;
+}
+
+/* Check for pending requests. */
+static int tmy_poll_control(struct file *file, poll_table *wait)
+{
+ struct io_buffer *head = (struct io_buffer *) file->private_data;
+ if (!head->poll)
+ return -ENOSYS;
+ return head->poll(file, wait);
+}
+
+/* Read policy. */
+static int tmy_read_control(struct file *file, char __user *buffer,
+ const int buffer_len)
+{
+ int len = 0;
+ struct io_buffer *head = (struct io_buffer *) file->private_data;
+
+ if (!head->read)
+ return -ENOSYS;
+ if (!access_ok(VERIFY_WRITE, buffer, buffer_len))
+ return -EFAULT;
+ if (down_interruptible(&head->read_sem))
+ return -EINTR;
+ len = head->read(head);
+ if (len >= 0)
+ len = tmy_copy_to_user(head, buffer, buffer_len);
+ up(&head->read_sem);
+
+ return len;
+}
+
+/* Update policy. */
+static int tmy_write_control(struct file *file, const char __user *buffer,
+ const int buffer_len)
+{
+ struct io_buffer *head = (struct io_buffer *) file->private_data;
+ int error = buffer_len;
+ int avail_len = buffer_len;
+ char *cp0 = head->write_buf;
+
+ if (!head->write)
+ return -ENOSYS;
+ if (!access_ok(VERIFY_READ, buffer, buffer_len))
+ return -EFAULT;
+ if (!tmy_is_root())
+ return -EPERM;
+ if (head->write != tmy_write_pid && !tmy_is_policy_manager())
+ /* Forbid updating policies for non manager programs. */
+ return -EPERM;
+
+ if (down_interruptible(&head->write_sem))
+ return -EINTR;
+ while (avail_len > 0) {
+ char c;
+ if (head->write_avail >= head->writebuf_size - 1) {
+ error = -ENOMEM;
+ break;
+ } else if (get_user(c, buffer)) {
+ error = -EFAULT;
+ break;
+ }
+ buffer++;
+ avail_len--;
+ cp0[head->write_avail++] = c;
+ if (c != '\n')
+ continue;
+ cp0[head->write_avail - 1] = '\0';
+ head->write_avail = 0;
+ tmy_normalize_line(cp0);
+ head->write(head);
+ }
+ up(&head->write_sem);
+
+ return error;
+}
+
+/* Close /proc/tomoyo/ interface. */
+static int tmy_close_control(struct file *file)
+{
+ struct io_buffer *head = file->private_data;
+
+ if (head->write == tmy_write_answer)
+ atomic_dec(&queryd_watcher);
+
+ tmy_free(head->read_buf);
+ head->read_buf = NULL;
+ tmy_free(head->write_buf);
+ head->write_buf = NULL;
+ tmy_free(head);
+ head = NULL;
+ file->private_data = NULL;
+ return 0;
+}
+
+/* open() operation for /proc/tomoyo/ interface. */
+static int tmy_open(struct inode *inode, struct file *file)
+{
+ return tmy_open_control(((u8 *) PDE(inode)->data) - ((u8 *) NULL),
+ file);
+}
+
+/* close() operation for /proc/tomoyo/ interface. */
+static int tmy_release(struct inode *inode, struct file *file)
+{
+ return tmy_close_control(file);
+}
+
+/* poll() operation for /proc/tomoyo/ interface. */
+static unsigned int tmy_poll(struct file *file, poll_table *wait)
+{
+ return tmy_poll_control(file, wait);
+}
+
+/* read() operation for /proc/tomoyo/ interface. */
+static ssize_t tmy_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ return tmy_read_control(file, buf, count);
+}
+
+/* write() operation for /proc/tomoyo/ interface. */
+static ssize_t tmy_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ return tmy_write_control(file, buf, count);
+}
+
+static struct file_operations tmy_operations = {
+ .open = tmy_open,
+ .release = tmy_release,
+ .poll = tmy_poll,
+ .read = tmy_read,
+ .write = tmy_write
+};
+
+/* Associate /proc/tomoyo/ interface with key. */
+static void tmy_create_entry(const char *name,
+ const mode_t mode,
+ struct proc_dir_entry *parent,
+ const int key)
+{
+ struct proc_dir_entry *entry = create_proc_entry(name, mode, parent);
+
+ if (entry) {
+ entry->proc_fops = &tmy_operations;
+ entry->data = ((u8 *) NULL) + key;
+ }
+}
+
+/**
+ * tmy_proc_init - initialize /proc/tomoyo/ interface.
+ */
+void tmy_proc_init(void)
+{
+ static int initialized;
+ struct proc_dir_entry *tmy_dir;
+
+ if (initialized)
+ return;
+ initialized = 1;
+ tmy_dir = proc_mkdir("tomoyo", NULL);
+ tmy_realpath_init();
+ tmy_find_domain(""); /* Set domainname of KERNEL domain. */
+ tmy_create_entry("query", 0600, tmy_dir,
+ TMY_POLICY_QUERY);
+ tmy_create_entry("domain_policy", 0600, tmy_dir,
+ TMY_POLICY_DOMAINPOLICY);
+ tmy_create_entry("system_policy", 0600, tmy_dir,
+ TMY_POLICY_SYSTEMPOLICY);
+ tmy_create_entry("exception_policy", 0600, tmy_dir,
+ TMY_POLICY_EXCEPTIONPOLICY);
+ tmy_create_entry(".domain_status", 0600, tmy_dir,
+ TMY_POLICY_DOMAIN_STATUS);
+ tmy_create_entry(".process_status", 0400, tmy_dir,
+ TMY_INFO_PROCESS_STATUS);
+ tmy_create_entry("self_domain", 0400, tmy_dir,
+ TMY_INFO_SELFDOMAIN);
+ tmy_create_entry("meminfo", 0400, tmy_dir,
+ TMY_INFO_MEMINFO);
+ tmy_create_entry("profile", 0600, tmy_dir,
+ TMY_PROFILE);
+ tmy_create_entry("manager", 0600, tmy_dir,
+ TMY_POLICY_MANAGER);
+ tmy_create_entry(".updates_counter", 0400, tmy_dir,
+ TMY_INFO_UPDATESCOUNTER);
+ tmy_create_entry("version", 0400, tmy_dir,
+ TMY_VERSION);
+}
Domain transition functions for TOMOYO Linux.
Every process belongs to a domain in TOMOYO Linux.
Domain transition occurs when execve(2) is called
and the domain is expressed as 'process invocation history',
such as '<kernel> /sbin/init /etc/init.d/rc'.
Domain information is stored in task_struct->security.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/domain.c | 1291 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1291 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/domain.c 2007-08-24 15:51:36.000000000 +0900
@@ -0,0 +1,1291 @@
+/*
+ * security/tomoyo/domain.c
+ *
+ * Domain transition functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+#include <linux/highmem.h>
+#include <linux/binfmts.h>
+
+/************************* VARIABLES *************************/
+
+/* Lock for appending domain's ACL. */
+DECLARE_MUTEX(domain_acl_lock);
+
+/* Domain creation lock. */
+static DECLARE_MUTEX(new_domain_assign_lock);
+
+/***** The structure for program files to force domain reconstruction. *****/
+
+struct domain_initializer_entry {
+ struct domain_initializer_entry *next;
+ const struct path_info *domainname; /* This may be NULL */
+ const struct path_info *program;
+ u8 is_deleted;
+ u8 is_not;
+ u8 is_last_name;
+};
+
+/***** The structure for domains to not to transit domains. *****/
+
+struct domain_keeper_entry {
+ struct domain_keeper_entry *next;
+ const struct path_info *domainname;
+ const struct path_info *program; /* This may be NULL */
+ u8 is_deleted;
+ u8 is_not;
+ u8 is_last_name;
+};
+
+/***** The structure for program files that should be aggregated. *****/
+
+struct aggregator_entry {
+ struct aggregator_entry *next;
+ const struct path_info *original_name;
+ const struct path_info *aggregated_name;
+ int is_deleted;
+};
+
+/***** The structure for program files that should be aliased. *****/
+
+struct alias_entry {
+ struct alias_entry *next;
+ const struct path_info *original_name;
+ const struct path_info *aliased_name;
+ int is_deleted;
+};
+
+/************************* UTILITY FUNCTIONS *************************/
+
+/**
+ * tmy_is_domain_def - check if the line is likely a domain definition.
+ * @buffer: the line to check.
+ *
+ * Returns nonzero if @buffer is likely a domain definition.
+ * Returns zero otherwise.
+ *
+ * For complete validation check, use tmy_is_correct_domain().
+ */
+int tmy_is_domain_def(const unsigned char *buffer)
+{
+ return strncmp(buffer, TMY_ROOT_NAME, TMY_ROOT_NAME_LEN) == 0;
+}
+
+/**
+ * tmy_add_acl - add an entry to a domain.
+ * @ptr: pointer to the last "struct acl_info" of @domain. May be NULL.
+ * @domain: pointer to "struct domain_info".
+ * @new_ptr: pointer to "struct acl_info" to add.
+ *
+ * Returns zero.
+ *
+ * To be honest, I can calculate @ptr from @domain.
+ * But since the caller knows @ptr, I use it.
+ */
+int tmy_add_acl(struct acl_info *ptr,
+ struct domain_info *domain,
+ struct acl_info *new_ptr)
+{
+ mb(); /* Instead of using spinlock. */
+
+ if (!ptr)
+ domain->first_acl_ptr = (struct acl_info *) new_ptr;
+ else
+ ptr->next = (struct acl_info *) new_ptr;
+
+ tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+ return 0;
+}
+
+/**
+ * tmy_del_acl - remove an entry from a domain
+ * @ptr: pointer to "struct acl_info" to remove.
+ *
+ * Returns zero.
+ *
+ * TOMOYO Linux doesn't free memory used by policy because policies are not
+ * so frequently changed after entring into enforcing mode.
+ * This makes the code free of read-lock.
+ * The caller uses "down(&domain_acl_lock);" as write-lock.
+ */
+int tmy_del_acl(struct acl_info *ptr)
+{
+ ptr->is_deleted = 1;
+ tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+ return 0;
+}
+
+/**
+ * tmy_too_many_acl - check quota for a domain.
+ * @domain: pointer to "struct domain_info" to examine.
+ *
+ * Returns nonzero if quota exceeded.
+ * Returns zero otherwise.
+ *
+ * This is a safeguard for "learning mode", for appending entries
+ * without limit dulls the system response and consumes much memory.
+ */
+int tmy_too_many_acl(struct domain_info * const domain)
+{
+ unsigned int count = 0;
+ struct acl_info *ptr;
+
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ if (!ptr->is_deleted)
+ count++;
+ }
+ /* If there are so many entries, don't append if accept mode. */
+ if (count < tmy_flags(TMY_MAX_ACCEPT_ENTRY))
+ return 0;
+
+ if (!domain->quota_warned) {
+ printk(KERN_INFO
+ "TOMOYO-WARNING: Domain '%s' has so many ACLs "
+ "to hold. Stopped learning mode.\n",
+ domain->domainname->name);
+ domain->quota_warned = 1;
+ }
+
+ return 1;
+}
+
+
+/************************ DOMAIN INITIALIZER HANDLER ************************/
+
+static struct domain_initializer_entry *domain_initializer_list;
+
+/* Update domain initializer list. */
+static int tmy_add_domain_initializer_entry(const char *domainname,
+ const char *program,
+ const int is_not,
+ const int is_delete)
+{
+ struct domain_initializer_entry *new_entry;
+ struct domain_initializer_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved_program;
+ const struct path_info *saved_domainname = NULL;
+ int error = -ENOMEM;
+ int is_last_name = 0;
+
+ if (!tmy_correct_path(program, 1, -1, -1, __FUNCTION__))
+ return -EINVAL; /* No patterns allowed. */
+
+ if (domainname) {
+ if (!tmy_is_domain_def(domainname) &&
+ tmy_correct_path(domainname, 1, -1, -1, __FUNCTION__))
+ is_last_name = 1;
+
+ else if (!tmy_is_correct_domain(domainname, __FUNCTION__))
+ return -EINVAL;
+
+ saved_domainname = tmy_save_name(domainname);
+ if (!saved_domainname)
+ return -ENOMEM;
+ }
+
+ saved_program = tmy_save_name(program);
+ if (!saved_program)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = domain_initializer_list; ptr; ptr = ptr->next) {
+ if (ptr->is_not == is_not &&
+ ptr->domainname == saved_domainname &&
+ ptr->program == saved_program) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->domainname = saved_domainname;
+ new_entry->program = saved_program;
+ new_entry->is_not = is_not;
+ new_entry->is_last_name = is_last_name;
+
+ mb(); /* Instead of using spinlock. */
+ ptr = domain_initializer_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ domain_initializer_list = new_entry;
+
+ error = 0;
+out: ;
+
+ up(&lock);
+ return error;
+}
+
+/**
+ * tmy_read_domain_initializer_policy - read domain initializer policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_domain_initializer_policy(struct io_buffer *head)
+{
+ struct domain_initializer_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = domain_initializer_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted) {
+ const char *s1 = ptr->is_not ? "no_" : "";
+ const char *s2 = TMY_INITIALIZE_DOMAIN;
+ const char *s3 = ptr->program->name;
+
+ if (ptr->domainname) {
+ if (tmy_io_printf(head, "%s%s%s from %s\n",
+ s1, s2, s3,
+ ptr->domainname->name))
+ break;
+ } else {
+ if (tmy_io_printf(head, "%s%s%s\n",
+ s1, s2, s3))
+ break;
+ }
+ }
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/**
+ * tmy_add_domain_initializer_policy - add domain initializer policy
+ * @data: a line to parse.
+ * @is_not: is this overriding?
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes a domain initializer entry.
+ */
+int tmy_add_domain_initializer_policy(char *data,
+ const int is_not,
+ const int is_delete)
+{
+ char *cp = strstr(data, " from ");
+
+ if (cp) {
+ *cp = '\0';
+ return tmy_add_domain_initializer_entry(cp + 6, data, is_not,
+ is_delete);
+ }
+
+ return tmy_add_domain_initializer_entry(NULL, data, is_not, is_delete);
+}
+
+/* Should I transit to a domain under "<kernel>" domain? */
+static int tmy_is_domain_initializer(const struct path_info *domainname,
+ const struct path_info *program,
+ const struct path_info *last_name)
+{
+ struct domain_initializer_entry *ptr;
+ int flag = 0;
+
+ for (ptr = domain_initializer_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+
+ if (ptr->domainname) {
+ if (!ptr->is_last_name) {
+ if (ptr->domainname != domainname)
+ continue;
+ } else {
+ if (tmy_pathcmp(ptr->domainname, last_name))
+ continue;
+ }
+ }
+
+ if (tmy_pathcmp(ptr->program, program))
+ continue;
+ if (ptr->is_not)
+ return 0;
+
+ flag = 1;
+ }
+
+ return flag;
+}
+
+/************************* DOMAIN KEEPER HANDLER *************************/
+
+static struct domain_keeper_entry *domain_keeper_list;
+
+/* Update domain keeper list. */
+static int tmy_add_domain_keeper_entry(const char *domainname,
+ const char *program,
+ const int is_not,
+ const int 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 DECLARE_MUTEX(lock);
+ int error = -ENOMEM;
+ int is_last_name = 0;
+
+ if (!tmy_is_domain_def(domainname) &&
+ tmy_correct_path(domainname, 1, -1, -1, __FUNCTION__))
+ is_last_name = 1;
+
+ else if (!tmy_is_correct_domain(domainname, __FUNCTION__))
+ return -EINVAL;
+
+ if (program) {
+ if (!tmy_correct_path(program, 1, -1, -1, __FUNCTION__))
+ return -EINVAL;
+
+ saved_program = tmy_save_name(program);
+ if (!saved_program)
+ return -ENOMEM;
+ }
+
+ saved_domainname = tmy_save_name(domainname);
+ if (!saved_domainname)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = domain_keeper_list; ptr; ptr = ptr->next) {
+ if (ptr->is_not == is_not &&
+ ptr->domainname == saved_domainname &&
+ ptr->program == saved_program) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->domainname = saved_domainname;
+ new_entry->program = saved_program;
+ new_entry->is_not = is_not;
+ new_entry->is_last_name = is_last_name;
+
+ mb(); /* Instead of using spinlock. */
+ ptr = domain_keeper_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ domain_keeper_list = new_entry;
+ error = 0;
+
+out: ;
+
+ up(&lock);
+ return error;
+}
+
+/**
+ * tmy_add_domain_keeper_policy - add domain keeper policy.
+ * @data: a line to parse.
+ * @is_not: is this overriding?
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes a domain keeper entry.
+ *
+ */
+int tmy_add_domain_keeper_policy(char *data,
+ const int is_not,
+ const int is_delete)
+{
+ char *cp = strstr(data, " from ");
+
+ if (cp) {
+ *cp = '\0';
+ return tmy_add_domain_keeper_entry(cp + 6, data,
+ is_not, is_delete);
+ }
+
+ return tmy_add_domain_keeper_entry(data, NULL, is_not, is_delete);
+}
+
+/**
+ * tmy_read_domain_keeper_policy - read domain keeper policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_domain_keeper_policy(struct io_buffer *head)
+{
+ struct domain_keeper_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = domain_keeper_list;
+
+ for (; ptr; ptr = ptr->next) {
+ head->read_var2 = ptr;
+ if (ptr->is_deleted)
+ continue;
+
+ if (ptr->program) {
+ if (tmy_io_printf(head,
+ "%s" TMY_KEEP_DOMAIN
+ "%s from %s\n",
+ ptr->is_not ? "no_" : "",
+ ptr->program->name,
+ ptr->domainname->name))
+ break;
+
+ } else {
+ if (tmy_io_printf(head,
+ "%s" TMY_KEEP_DOMAIN
+ "%s\n",
+ ptr->is_not ? "no_" : "",
+ ptr->domainname->name))
+ break;
+
+ }
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/* Should I remain in current domain? */
+static int tmy_is_domain_keeper(const struct path_info *domainname,
+ const struct path_info *program,
+ const struct path_info *last_name)
+{
+ struct domain_keeper_entry *ptr;
+ int flag = 0;
+
+ for (ptr = domain_keeper_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+
+ if (!ptr->is_last_name) {
+ if (ptr->domainname != domainname)
+ continue;
+ } else {
+ if (tmy_pathcmp(ptr->domainname, last_name))
+ continue;
+ }
+
+ if (ptr->program && tmy_pathcmp(ptr->program, program))
+ continue;
+ if (ptr->is_not)
+ return 0;
+ flag = 1;
+ }
+
+ return flag;
+}
+
+/********************* SYMBOLIC LINKED PROGRAM HANDLER *********************/
+
+static struct alias_entry *alias_list;
+
+/* Update alias list. */
+static int tmy_add_alias_entry(const char *original_name,
+ const char *aliased_name,
+ const int is_delete)
+{
+ struct alias_entry *new_entry;
+ struct alias_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved_original_name;
+ const struct path_info *saved_aliased_name;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(original_name, 1, -1, -1, __FUNCTION__) ||
+ !tmy_correct_path(aliased_name, 1, -1, -1, __FUNCTION__))
+ return -EINVAL; /* No patterns allowed. */
+
+ saved_original_name = tmy_save_name(original_name);
+ saved_aliased_name = tmy_save_name(aliased_name);
+ if (!saved_original_name || !saved_aliased_name)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = alias_list; ptr; ptr = ptr->next) {
+ if (ptr->original_name == saved_original_name &&
+ ptr->aliased_name == saved_aliased_name) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->original_name = saved_original_name;
+ new_entry->aliased_name = saved_aliased_name;
+
+ mb(); /* Instead of using spinlock. */
+ ptr = alias_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ alias_list = new_entry;
+
+ error = 0;
+out: ;
+
+ up(&lock);
+
+ return error;
+}
+
+/**
+ * tmy_read_alias_policy - read alias policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_alias_policy(struct io_buffer *head)
+{
+ struct alias_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = alias_list;
+
+ for (; ptr; ptr = ptr->next) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted &&
+ tmy_io_printf(head,
+ TMY_ALIAS "%s %s\n",
+ ptr->original_name->name,
+ ptr->aliased_name->name))
+ break;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/**
+ * tmy_add_alias_policy - add alias policy.
+ * @data: a line to parse.
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes an alias entry.
+ */
+int tmy_add_alias_policy(char *data, const int is_delete)
+{
+ char *cp = strchr(data, ' ');
+
+ if (!cp)
+ return -EINVAL;
+ *cp++ = '\0';
+
+ return tmy_add_alias_entry(data, cp, is_delete);
+}
+
+/************************ DOMAIN AGGREGATOR HANDLER ************************/
+
+static struct aggregator_entry *aggregator_list;
+
+/* Update aggregator list. */
+static int tmy_add_aggregator_entry(const char *original_name,
+ const char *aggregated_name,
+ const int is_delete)
+{
+ struct aggregator_entry *new_entry;
+ struct aggregator_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved_original_name;
+ const struct path_info *saved_aggregated_name;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(original_name, 1, 0, -1, __FUNCTION__) ||
+ !tmy_correct_path(aggregated_name, 1, -1, -1, __FUNCTION__))
+ return -EINVAL;
+
+ saved_original_name = tmy_save_name(original_name);
+ saved_aggregated_name = tmy_save_name(aggregated_name);
+ if (!saved_original_name || saved_aggregated_name)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = aggregator_list; ptr; ptr = ptr->next) {
+ if (ptr->original_name == saved_original_name &&
+ ptr->aggregated_name == saved_aggregated_name) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->original_name = saved_original_name;
+ new_entry->aggregated_name = saved_aggregated_name;
+
+ mb(); /* Instead of using spinlock. */
+ ptr = aggregator_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ aggregator_list = new_entry;
+
+ error = 0;
+out: ;
+
+ up(&lock);
+
+ return error;
+}
+
+/**
+ * tmy_read_aggregator_policy - read aggregator policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_aggregator_policy(struct io_buffer *head)
+{
+ struct aggregator_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = aggregator_list;
+
+ for (; ptr; ptr = ptr->next) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted &&
+ tmy_io_printf(head,
+ TMY_AGGREGATOR "%s %s\n",
+ ptr->original_name->name,
+ ptr->aggregated_name->name))
+ break;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/**
+ * tmy_add_aggregator_policy - add aggregator policy.
+ * @data: a line to parse.
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes an aggregator entry.
+ */
+int tmy_add_aggregator_policy(char *data, const int is_delete)
+{
+ char *cp = strchr(data, ' ');
+
+ if (!cp)
+ return -EINVAL;
+ *cp++ = '\0';
+
+ return tmy_add_aggregator_entry(data, cp, is_delete);
+}
+
+/************************* DOMAIN DELETION HANDLER *************************/
+
+/**
+ * tmy_delete_domain - delete a domain.
+ * @domainname0: domainname to delete.
+ *
+ * Returns zero.
+ *
+ * This function deletes domains.
+ * The behavior of deleting domain is like deleting files on Linux's
+ * filesystem. A process transits to different domain upon do_execve(),
+ * and the process can refer the deleted domains after the domain is deleted,
+ * like a process opens a file and the process can read()/write() the deleted
+ * file after the file is deleted.
+ * This avoids processes from crashing due to referring non-existent domains.
+ * Administrator manually terminates processes thet are referring deleted
+ * domains after deleting domains.
+ * Also, undeleting domains is supported. See tmy_undelete_domain().
+ */
+int tmy_delete_domain(char *domainname0)
+{
+ struct domain_info *domain;
+ struct path_info domainname;
+
+ domainname.name = domainname0;
+ tmy_fill_path_info(&domainname);
+
+ down(&new_domain_assign_lock);
+
+ /* Is there an active domain? */ /* Never delete KERNEL_DOMAIN */
+ for (domain = KERNEL_DOMAIN.next; domain; domain = domain->next) {
+ if (domain->is_deleted ||
+ tmy_pathcmp(domain->domainname, &domainname))
+ continue;
+ break;
+ }
+ if (domain) {
+ struct domain_info *domain2;
+
+ /* Mark already deleted domains as non undeletable. */
+ for (domain2 = KERNEL_DOMAIN.next;
+ domain2;
+ domain2 = domain2->next) {
+ if (!domain2->is_deleted ||
+ tmy_pathcmp(domain2->domainname, &domainname))
+ continue;
+ domain2->is_deleted = 255;
+ }
+
+ /* Delete and mark active domain as undeletable. */
+ domain->is_deleted = 1;
+ }
+
+ up(&new_domain_assign_lock);
+
+ return 0;
+}
+
+/**
+ * tmy_undelete_domain - undelete a domain.
+ * @domainname0: domainname to undelete.
+ *
+ * Returns pointer to undeleted "struct domain_info" on success.
+ * Returns NULL on failure.
+ *
+ * This function undeletes domains.
+ * Not only the domain previously deleted by tmy_delete_domain()
+ * but also all domains deleted by tmy_delete_domain() are undeletable.
+ * If there is no deleted domain named @domainname0 or
+ * a not-yet-deleted domain named @domainname0 exists, undelete fails.
+ * Otherwise, previously deleted domain named @domainname0 is undeleted.
+ */
+struct domain_info *tmy_undelete_domain(const char *domainname0)
+{
+ struct domain_info *domain;
+ struct domain_info *candidate_domain = NULL;
+ struct path_info domainname;
+
+ domainname.name = domainname0;
+ tmy_fill_path_info(&domainname);
+
+ down(&new_domain_assign_lock);
+
+ for (domain = KERNEL_DOMAIN.next; domain; domain = domain->next) {
+ if (tmy_pathcmp(&domainname, domain->domainname))
+ continue;
+
+ if (!domain->is_deleted) {
+ /* This domain is active. I can't undelete. */
+ candidate_domain = NULL;
+ break;
+ }
+
+ /* Is this domain undeletable? */
+ if (domain->is_deleted == 1)
+ candidate_domain = domain;
+ }
+ if (candidate_domain)
+ candidate_domain->is_deleted = 0;
+
+ up(&new_domain_assign_lock);
+
+ return candidate_domain;
+}
+
+/************************ DOMAIN TRANSITION HANDLER ************************/
+
+/**
+ * tmy_find_domain - find a domain with given domainname.
+ * @domainname0: the domainname to find.
+ *
+ * Returns pointer to "struct domain_info" on success.
+ * Returns NULL on failure.
+ *
+ * This function does not create a new domain
+ * if a domain named @domainname0 does not exist.
+ */
+struct domain_info *tmy_find_domain(const char *domainname0)
+{
+ struct domain_info *domain;
+ static int first = 1;
+ struct path_info domainname;
+
+ domainname.name = domainname0;
+ tmy_fill_path_info(&domainname);
+
+ if (first) {
+ KERNEL_DOMAIN.domainname = tmy_save_name(TMY_ROOT_NAME);
+ first = 0;
+ }
+
+ for (domain = &KERNEL_DOMAIN; domain; domain = domain->next) {
+ if (!domain->is_deleted &&
+ !tmy_pathcmp(&domainname, domain->domainname))
+ return domain;
+ }
+
+ return NULL;
+}
+
+/**
+ * tmy_new_domain - find or assign a domain with given domainname.
+ * @domainname: the domainname to find.
+ * @profile: profile number to assign if newly created.
+ *
+ * Returns pointer to "struct domain_info" on success.
+ * Returns NULL on failure.
+ *
+ * This function creates a new domain if a domain named @domainname0
+ * does not exist.
+ */
+struct domain_info *tmy_new_domain(const char *domainname,
+ const u8 profile)
+{
+ struct domain_info *domain = NULL;
+ const struct path_info *saved_domainname;
+
+ down(&new_domain_assign_lock);
+
+ domain = tmy_find_domain(domainname);
+ if (domain)
+ goto out;
+
+ if (!tmy_is_correct_domain(domainname, __FUNCTION__))
+ goto out;
+
+ saved_domainname = tmy_save_name(domainname);
+ if (!saved_domainname)
+ goto out;
+
+ /* Can I reuse memory of deleted domain? */
+ for (domain = KERNEL_DOMAIN.next; domain; domain = domain->next) {
+ struct task_struct *p;
+ struct acl_info *ptr;
+ int flag;
+
+ if (!domain->is_deleted ||
+ domain->domainname != saved_domainname)
+ continue;
+ flag = 0;
+
+ /***** CRITICAL SECTION START *****/
+ read_lock(&tasklist_lock);
+ for_each_process(p) {
+ /* "struct task_struct"->security is not NULL. */
+ if (((struct tmy_security *) p->security)->domain
+ == domain) {
+ flag = 1;
+ break;
+ }
+ }
+ read_unlock(&tasklist_lock);
+ /***** CRITICAL SECTION END *****/
+
+ /* Somebody is still referring this deleted domain. */
+ if (flag)
+ continue;
+
+ /* OK. Let's reuse memory for this deleted domain. */
+
+ /* Delete all entries in this deleted domain. */
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next)
+ ptr->is_deleted = 1;
+
+ domain->profile = profile;
+ domain->quota_warned = 0;
+
+ mb(); /* Instead of using spinlock. */
+ /* Undelete this deleted domain. */
+ domain->is_deleted = 0;
+ goto out;
+ }
+
+ /* No memory reusable. Create using new memory. */
+ domain = tmy_alloc_element(sizeof(*domain));
+ if (domain) {
+ struct domain_info *ptr = &KERNEL_DOMAIN;
+
+ domain->domainname = saved_domainname;
+ domain->profile = profile;
+
+ mb(); /* Instead of using spinlock. */
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = domain;
+ }
+out: ;
+ up(&new_domain_assign_lock);
+
+ return domain;
+}
+
+/* Convert non ASCII printable characters to ASCII printable characters. */
+static int tmy_escape(char *dest, const char *src, int dest_len)
+{
+ while (*src) {
+ const unsigned char c = * (const unsigned char *) src;
+
+ if (c == '\\') {
+ dest_len -= 2;
+ if (dest_len <= 0)
+ goto out;
+ *dest++ = '\\';
+ *dest++ = '\\';
+ } else if (c > ' ' && c < 127) {
+ if (--dest_len <= 0)
+ goto out;
+ *dest++ = c;
+ } else {
+ dest_len -= 4;
+ if (dest_len <= 0)
+ goto out;
+ *dest++ = '\\';
+ *dest++ = (c >> 6) + '0';
+ *dest++ = ((c >> 3) & 7) + '0';
+ *dest++ = (c & 7) + '0';
+ }
+ src++;
+ }
+
+ if (--dest_len <= 0)
+ goto out;
+ *dest = '\0';
+
+ return 0;
+out: ;
+ return -ENOMEM;
+}
+
+/* Get argv[0] of "struct linux_binprm". */
+static char *tmy_get_argv0(struct linux_binprm *bprm)
+{
+ char *arg_ptr;
+ int arg_len = 0;
+ unsigned long pos = bprm->p;
+ int i = pos / PAGE_SIZE;
+ int offset = pos % PAGE_SIZE;
+
+ if (bprm->argc <= 0)
+ return NULL;
+
+ arg_ptr = tmy_alloc(PAGE_SIZE);
+
+ if (!arg_ptr)
+ goto out;
+
+ while (1) {
+ struct page *page;
+ const char *kaddr;
+ char *tmp_arg;
+
+#ifdef CONFIG_MMU
+ if (get_user_pages(current, bprm->mm, pos,
+ 1, 0, 1, &page, NULL) <= 0)
+ goto out;
+#else
+ page = bprm->page[i];
+#endif
+ kaddr = kmap(page);
+ if (!kaddr) { /* Mapping failed. */
+#ifdef CONFIG_MMU
+ put_page(page);
+#endif
+ goto out;
+ }
+
+ memmove(arg_ptr + arg_len, kaddr + offset, PAGE_SIZE - offset);
+ kunmap(page);
+
+#ifdef CONFIG_MMU
+ put_page(page);
+ pos += PAGE_SIZE - offset;
+#endif
+
+ arg_len += PAGE_SIZE - offset;
+
+ if (memchr(arg_ptr, '\0', arg_len))
+ break;
+
+ tmp_arg = tmy_alloc(arg_len + PAGE_SIZE);
+ if (!tmp_arg)
+ goto out;
+
+ memmove(tmp_arg, arg_ptr, arg_len);
+ tmy_free(arg_ptr);
+ arg_ptr = tmp_arg;
+ i++;
+ offset = 0;
+ }
+ return arg_ptr;
+out: ;
+ tmy_free(arg_ptr);
+
+ return NULL;
+}
+
+/**
+ * tmy_find_next_domain - find a domain to transit to if do_execve() succeeds.
+ * @bprm: pointer to "struct linux_binprm".
+ * @next_domain: pointer to pointer to "struct domain_info".
+ *
+ * Returns zero if success. @next_domain receives new domain to transit to.
+ * Returns nonzero on failure.
+ *
+ * This function handles TOMOYO Linux's domain transition.
+ * New domains are automatically created unless the domain the caller process
+ * belongs to is assigned a profile for enforcing mode.
+ */
+int tmy_find_next_domain(struct linux_binprm *bprm,
+ struct domain_info **next_domain)
+{
+ /* This function assumes that the size of buffer returned */
+ /* by tmy_realpath() = TMY_MAX_PATHNAME_LEN. */
+ struct domain_info *old_domain = TMY_SECURITY->domain;
+ struct domain_info *domain = NULL;
+ const char *old_domain_name = old_domain->domainname->name;
+ const char *original_name = bprm->filename;
+ struct file *filp = bprm->file;
+ char *new_domain_name = NULL;
+ char *real_program_name = NULL;
+ char *symlink_program_name = NULL;
+ const int is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+ int retval;
+ struct path_info r;
+ struct path_info s;
+ struct path_info l;
+
+ /*
+ * Built-in initializers.
+ * This is needed because policies are not loaded
+ * until starting /sbin/init .
+ */
+ static int first = 1;
+ if (first) {
+ tmy_add_domain_initializer_entry(NULL, "/sbin/hotplug", 0, 0);
+ tmy_add_domain_initializer_entry(NULL, "/sbin/modprobe", 0, 0);
+ tmy_add_domain_initializer_entry(NULL, "/sbin/udevd", 0, 0);
+ first = 0;
+ }
+
+ /* Get realpath of program. */
+ /* I hope tmy_realpath() won't fail with -ENOMEM. */
+ retval = -ENOENT;
+ real_program_name = tmy_realpath(original_name);
+
+ if (!real_program_name)
+ goto out;
+
+ /* Get realpath of symbolic link. */
+ symlink_program_name = tmy_realpath_nofollow(original_name);
+ if (!symlink_program_name)
+ goto out;
+
+ r.name = real_program_name;
+ tmy_fill_path_info(&r);
+ s.name = symlink_program_name;
+ tmy_fill_path_info(&s);
+ l.name = strrchr(old_domain_name, ' ');
+
+ if (l.name)
+ l.name++;
+ else
+ l.name = old_domain_name;
+ tmy_fill_path_info(&l);
+
+ /* Check 'alias' directive. */
+ if (tmy_pathcmp(&r, &s)) {
+ struct alias_entry *ptr;
+
+ /* Is this program allowed to be called via symbolic links? */
+ for (ptr = alias_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted ||
+ tmy_pathcmp(&r, ptr->original_name) ||
+ tmy_pathcmp(&s, ptr->aliased_name))
+ continue;
+ memset(real_program_name, 0, TMY_MAX_PATHNAME_LEN);
+ strncpy(real_program_name,
+ ptr->aliased_name->name,
+ TMY_MAX_PATHNAME_LEN - 1);
+ tmy_fill_path_info(&r);
+ break;
+ }
+ }
+
+ /* Compare basename of real_program_name and argv[0] */
+ if (bprm->argc > 0 && tmy_flags(TMY_MAC_FOR_ARGV0)) {
+
+ char *org_argv0 = tmy_get_argv0(bprm);
+
+ retval = -ENOMEM;
+ if (org_argv0) {
+
+ const int len = strlen(org_argv0);
+ char *printable_argv0 = tmy_alloc(len * 4 + 8);
+
+ if (printable_argv0 &&
+ !tmy_escape(printable_argv0, org_argv0,
+ len * 4 + 8)) {
+ const char *base_argv0;
+ const char *base_filename;
+
+ base_argv0 = strrchr(printable_argv0, '/');
+ if (!base_argv0)
+ base_argv0 = printable_argv0;
+ else
+ base_argv0++;
+
+ base_filename = strrchr(real_program_name, '/');
+ if (!base_filename)
+ base_filename = real_program_name;
+ else
+ base_filename++;
+
+ if (strcmp(base_argv0, base_filename))
+ retval = tmy_argv0_perm(&r, base_argv0);
+ else
+ retval = 0;
+ }
+
+ tmy_free(printable_argv0);
+ tmy_free(org_argv0);
+ }
+
+ if (retval)
+ goto out;
+
+ }
+
+
+ /* Check 'aggregator' directive. */
+ {
+ struct aggregator_entry *ptr;
+
+ /* Is this program allowed to be aggregated? */
+ for (ptr = aggregator_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted ||
+ !tmy_path_match(&r, ptr->original_name))
+ continue;
+ memset(real_program_name, 0, TMY_MAX_PATHNAME_LEN);
+ strncpy(real_program_name,
+ ptr->aggregated_name->name,
+ TMY_MAX_PATHNAME_LEN - 1);
+ tmy_fill_path_info(&r);
+ break;
+ }
+ }
+
+ /* Check execute permission. */
+ retval = tmy_exec_perm(&r, filp);
+ if (retval < 0)
+ goto out;
+
+ /* Allocate memory for calcurating domain name. */
+ retval = -ENOMEM;
+ new_domain_name = tmy_alloc(TMY_MAX_PATHNAME_LEN + 16);
+ if (!new_domain_name)
+ goto out;
+
+ if (tmy_is_domain_initializer(old_domain->domainname, &r, &l))
+ /* Transit to the child of KERNEL_DOMAIN domain. */
+ snprintf(new_domain_name, TMY_MAX_PATHNAME_LEN + 1,
+ TMY_ROOT_NAME " " "%s", real_program_name);
+ else if (old_domain == &KERNEL_DOMAIN && !sbin_init_started)
+ /*
+ * Needn't to transit from kernel domain
+ * before starting /sbin/init .
+ * But transit from kernel domain if executing initializers,
+ * for they might start before /sbin/init .
+ */
+ domain = old_domain;
+ else if (tmy_is_domain_keeper(old_domain->domainname, &r, &l))
+ /* Keep current domain. */
+ domain = old_domain;
+ else
+ /* Normal domain transition. */
+ snprintf(new_domain_name,
+ TMY_MAX_PATHNAME_LEN + 1,
+ "%s %s",
+ old_domain_name,
+ real_program_name);
+
+ if (domain || strlen(new_domain_name) >= TMY_MAX_PATHNAME_LEN)
+ goto ok;
+
+ if (is_enforce) {
+ domain = tmy_find_domain(new_domain_name);
+ if (!domain &&
+ tmy_supervisor("#Need to create domain\n%s\n",
+ new_domain_name) == 0) {
+ const u8 profile = TMY_SECURITY->domain->profile;
+ domain = tmy_new_domain(new_domain_name, profile);
+ }
+ } else {
+ const u8 profile = TMY_SECURITY->domain->profile;
+ domain = tmy_new_domain(new_domain_name, profile);
+ }
+
+ok: ;
+
+ if (!domain) {
+ printk(KERN_INFO "TOMOYO-ERROR: Domain '%s' not defined.\n",
+ new_domain_name);
+ if (is_enforce)
+ retval = -EPERM;
+ } else
+ retval = 0;
+out: ;
+
+ tmy_free(new_domain_name);
+ tmy_free(real_program_name);
+ tmy_free(symlink_program_name);
+ *next_domain = domain ? domain : old_domain;
+
+ return retval;
+}
On Fri, 24 Aug 2007, Kentaro Takeda wrote:
> Kconfig and Makefile for TOMOYO Linux.
> TOMOYO Linux is placed in security/tomoyo .
> Signed-off-by: Kentaro Takeda <[email protected]>
> Signed-off-by: Tetsuo Handa <[email protected]>
> ---
> security/Kconfig | 1 +
> security/Makefile | 1 +
> security/tomoyo/Kconfig | 18 ++++++++++++++++++
> security/tomoyo/Makefile | 3 +++
> 4 files changed, 23 insertions(+)
Just a trivial minor nitpick - IMHO this breaks bisectability. It might be
better to add the Kconfig/Makefile patch at the end of the whole series,
so that bisect doesn't end up in the tree in which Makefile references
non-exsting files/directories.
--
Jiri Kosina
This patch makes access logs sent to auditing subsystem.
TOMOYO Linux uses two channels for auditing.
One is 'AUDIT_TMY_GRANTED', used for auditing accesses which are
granted in the TOMOYO Linux policy.
The other is 'AUDIT_TMY_REJECTED', used for auditing accesses which
are not granted in the TOMOYO Linux policy.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
include/linux/audit.h | 3 ++
security/tomoyo/audit.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 71 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/audit.c 2007-08-24 15:51:36.000000000 +0900
@@ -0,0 +1,68 @@
+/*
+ * security/tomoyo/audit.c
+ *
+ * Audit functions for TOMOYO Linux
+ */
+
+#include "tomoyo.h"
+#include <linux/audit.h>
+
+/**
+ * tmy_init_audit_log - allocate and initialize audit buffer.
+ * @len: pointer to length of requested size.
+ *
+ * Returns pointer to audit buffer on success. @len received allocated size.
+ * Returns NULL on failure.
+ *
+ * @len must not be a NULL.
+ */
+char *tmy_init_audit_log(int *len)
+{
+ char *buf;
+ struct task_struct *task = current;
+ const char *domainname = TMY_SECURITY->domain->domainname->name;
+
+ *len += strlen(domainname) + 256;
+ buf = tmy_alloc(*len);
+
+ if (!buf)
+ return NULL;
+
+ snprintf(buf, (*len) - 1,
+ "pid=%d uid=%d gid=%d euid=%d egid=%d "
+ "suid=%d sgid=%d fsuid=%d fsgid=%d : %s : ",
+ task->pid, task->uid, task->gid, task->euid, task->egid,
+ task->suid, task->sgid, task->fsuid, task->fsgid, domainname);
+
+ return buf;
+}
+
+/**
+ * tmy_write_audit_log - write audit log.
+ * @buf: pointer to access log contents.
+ * @is_granted: is the access request granted?
+ * @is_enforce: is the access requested in enforcing mode?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * Write audit log.
+ * Caller must allocate @buf with tmy_init_audit_log().
+ */
+int tmy_write_audit_log(char *buf, const int is_granted, const int is_enforce)
+{
+ struct audit_buffer *ab;
+ int type = is_granted ? AUDIT_TMY_GRANTED : AUDIT_TMY_REJECTED;
+
+ ab = audit_log_start(current->audit_context, GFP_KERNEL, type);
+ if (ab) {
+ const char *msg
+ = is_granted ? "granted" : is_enforce ?
+ "error" : "warning";
+ audit_log_format(ab, "TOMOYO %s: %s", msg, buf);
+ audit_log_end(ab);
+ }
+
+ tmy_free(buf);
+ return ab ? 0 : -ENOMEM;
+}
--- linux-2.6.orig/include/linux/audit.h 2007-08-23 21:25:55.000000000 +0900
+++ linux-2.6/include/linux/audit.h 2007-08-24 15:51:36.000000000 +0900
@@ -120,6 +120,9 @@
#define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */
+#define AUDIT_TMY_GRANTED 2001 /* TOMOYO Linux audit granted */
+#define AUDIT_TMY_REJECTED 2002 /* TOMOYO Linux audit rejected */
+
/* Rule flags */
#define AUDIT_FILTER_USER 0x00 /* Apply rule to user-generated messages */
#define AUDIT_FILTER_TASK 0x01 /* Apply rule at task creation (not syscall) */
File access control functions for TOMOYO Linux.
TOMOYO Linux checks permission in
open/creat/unlink/truncate/ftruncate/mknod/mkdir/
rmdir/symlink/link/rename/uselib/sysctl .
Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/file.c | 1565 +++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1565 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/file.c 2007-08-24 15:51:36.000000000 +0900
@@ -0,0 +1,1565 @@
+/*
+ * security/tomoyo/file.c
+ *
+ * File access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
+
+/************************* VARIABLES *************************/
+
+/***** The structure for globally readable files. *****/
+
+struct globally_readable_file_entry {
+ struct globally_readable_file_entry *next;
+ const struct path_info *filename;
+ int is_deleted;
+};
+
+/***** The structure for filename patterns. *****/
+
+struct pattern_entry {
+ struct pattern_entry *next;
+ const struct path_info *pattern;
+ int is_deleted;
+};
+
+/***** The structure for non-rewritable-by-default file patterns. *****/
+
+struct no_rewrite_entry {
+ struct no_rewrite_entry *next;
+ const struct path_info *pattern;
+ int is_deleted;
+};
+
+/***** The structure for detailed write operations. *****/
+
+static struct {
+ const char *keyword;
+ const int paths;
+} acl_type_array[] = {
+ { "create", 1 }, /* TMY_TYPE_CREATE_ACL */
+ { "unlink", 1 }, /* TMY_TYPE_UNLINK_ACL */
+ { "mkdir", 1 }, /* TMY_TYPE_MKDIR_ACL */
+ { "rmdir", 1 }, /* TMY_TYPE_RMDIR_ACL */
+ { "mkfifo", 1 }, /* TMY_TYPE_MKFIFO_ACL */
+ { "mksock", 1 }, /* TMY_TYPE_MKSOCK_ACL */
+ { "mkblock", 1 }, /* TMY_TYPE_MKBLOCK_ACL */
+ { "mkchar", 1 }, /* TMY_TYPE_MKCHAR_ACL */
+ { "truncate", 1 }, /* TMY_TYPE_TRUNCATE_ACL */
+ { "symlink", 1 }, /* TMY_TYPE_SYMLINK_ACL */
+ { "link", 2 }, /* TMY_TYPE_LINK_ACL */
+ { "rename", 2 }, /* TMY_TYPE_RENAME_ACL */
+ { "rewrite", 1 }, /* TMY_TYPE_REWRITE_ACL */
+ { NULL, 0 }
+};
+
+/************************* UTILITY FUNCTIONS *************************/
+
+/**
+ * tmy_acltype2keyword - get keyword from access control index.
+ * @acl_type: index number.
+ *
+ * Returns keyword that corresponds with @acl_type .
+ */
+const char *tmy_acltype2keyword(const unsigned int acl_type)
+{
+ return (acl_type < ARRAY_SIZE(acl_type_array))
+ ? acl_type_array[acl_type].keyword : NULL;
+}
+
+/**
+ * tmy_acltype2paths - get number of arguments from access control index.
+ * @acl_type: index number.
+ *
+ * Returns number of arguments that corresponds with @acl_type .
+ */
+int tmy_acltype2paths(const unsigned int acl_type)
+{
+ return (acl_type < ARRAY_SIZE(acl_type_array))
+ ? acl_type_array[acl_type].paths : 0;
+}
+
+static int tmy_strendswith(const char *name, const char *tail)
+{
+ int len;
+
+ if (!name || !tail)
+ return 0;
+
+ len = strlen(name) - strlen(tail);
+ return len >= 0 && strcmp(name + len, tail) == 0;
+}
+
+static struct path_info *tmy_get_path(struct dentry *dentry,
+ struct vfsmount *mnt)
+{
+ /* sizeof(struct path_info_with_data) <= PAGE_SIZE */
+ struct path_info_with_data {
+ /* Keep this first, this pointer is passed to tmy_free(). */
+ struct path_info head;
+ char bariier1[16];
+ char body[TMY_MAX_PATHNAME_LEN];
+ char barrier2[16];
+ } *buf = tmy_alloc(sizeof(*buf));
+
+ if (buf) {
+ int error = tmy_realpath_dentry2(dentry,
+ mnt,
+ buf->body,
+ sizeof(buf->body) - 1);
+
+ if (error == 0) {
+ buf->head.name = buf->body;
+ tmy_fill_path_info(&buf->head);
+ return &buf->head;
+ }
+
+ tmy_free(buf);
+ buf = NULL;
+ printk(KERN_INFO "tmy_realpath_dentry = %d\n", error);
+ }
+
+ return NULL;
+}
+
+/************************* PROTOTYPES *************************/
+
+static int tmy_add_double_write_acl(const u8 type,
+ const char *filename1,
+ const char *filename2,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const u8 is_add);
+static int tmy_add_single_write_acl(const u8 type,
+ const char *filename,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const u8 is_add);
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_file_log(const struct path_info *filename,
+ const u8 perm,
+ const int is_granted,
+ const int is_enforce)
+{
+ char *buf;
+ int len;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ len = filename->total_len + 8;
+ buf = tmy_init_audit_log(&len);
+
+ if (!buf)
+ return -ENOMEM;
+
+ snprintf(buf + strlen(buf),
+ len - strlen(buf) - 1,
+ "%d %s",
+ perm,
+ filename->name);
+
+ return tmy_write_audit_log(buf, is_granted, is_enforce);
+}
+
+static int tmy_audit_write_log(const char *operation,
+ const struct path_info *filename1,
+ const struct path_info *filename2,
+ const int is_granted,
+ const int is_enforce)
+{
+ char *buf;
+ int len;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ len = strlen(operation) +
+ filename1->total_len +
+ (filename2 ? filename2->total_len : 0)
+ + 16;
+
+ buf = tmy_init_audit_log(&len);
+ if (!buf)
+ return -ENOMEM;
+
+ snprintf(buf + strlen(buf), len - strlen(buf) - 1,
+ "allow_%s %s %s",
+ operation, filename1->name, filename2 ? filename2->name : "");
+
+ return tmy_write_audit_log(buf, is_granted, is_enforce);
+}
+
+/********************** GLOBALLY READABLE FILE HANDLER **********************/
+
+static struct globally_readable_file_entry *globally_readable_list;
+
+static int tmy_add_globally_readable_entry(const char *filename,
+ const int is_delete)
+{
+ struct globally_readable_file_entry *new_entry;
+ struct globally_readable_file_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(filename, 1, -1, -1, __FUNCTION__))
+ return -EINVAL; /* No patterns allowed. */
+ saved = tmy_save_name(filename);
+ if (!saved)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = globally_readable_list; ptr; ptr = ptr->next) {
+ if (ptr->filename == saved) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->filename = saved;
+ mb(); /* Instead of using spinlock. */
+ ptr = globally_readable_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ globally_readable_list = new_entry;
+
+ error = 0;
+
+out: ;
+ up(&lock);
+
+ return error;
+}
+
+static int tmy_globally_readable(const struct path_info *filename)
+{
+ struct globally_readable_file_entry *ptr;
+
+ for (ptr = globally_readable_list; ptr; ptr = ptr->next) {
+ if (!ptr->is_deleted &&
+ !tmy_pathcmp(filename, ptr->filename))
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * tmy_add_globally_readable_policy - add or delete globally readable policy.
+ * @filename: pointer to filename to add ore remove.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_globally_readable_policy(char *filename, const int is_delete)
+{
+ return tmy_add_globally_readable_entry(filename, is_delete);
+}
+
+/**
+ * tmy_read_globally_readable_policy - read globally readable policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_globally_readable_policy(struct io_buffer *head)
+{
+ struct globally_readable_file_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = globally_readable_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted &&
+ tmy_io_printf(head, TMY_ALLOW_READ "%s\n",
+ ptr->filename->name))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/************************* FILE GROUP HANDLER *************************/
+
+static struct group_entry *group_list;
+
+static int tmy_add_group_entry(const char *group_name,
+ const char *member_name,
+ const int is_delete)
+{
+ static DECLARE_MUTEX(lock);
+ struct group_entry *new_group;
+ struct group_entry *group;
+ struct group_member *new_member;
+ struct group_member *member;
+ const struct path_info *saved_group;
+ const struct path_info *saved_member;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(group_name, 0, 0, 0, __FUNCTION__) ||
+ !group_name[0] ||
+ !tmy_correct_path(member_name, 0, 0, 0, __FUNCTION__) ||
+ !member_name[0])
+ return -EINVAL;
+
+ saved_group = tmy_save_name(group_name);
+ saved_member = tmy_save_name(member_name);
+
+ if (!saved_group || !saved_member)
+ return -ENOMEM;
+
+ down(&lock);
+ for (group = group_list; group; group = group->next) {
+ if (saved_group != group->group_name)
+ continue;
+
+ for (member = group->first_member;
+ member;
+ member = member->next) {
+ if (member->member_name == saved_member) {
+ member->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+ break;
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ if (!group) {
+ new_group = tmy_alloc_element(sizeof(*new_group));
+ if (!new_group)
+ goto out;
+ new_group->group_name = saved_group;
+ mb(); /* Instead of using spinlock. */
+ group = group_list;
+
+ if (group) {
+ while (group->next)
+ group = group->next;
+ group->next = new_group;
+ } else
+ group_list = new_group;
+
+ group = new_group;
+ }
+
+ new_member = tmy_alloc_element(sizeof(*new_member));
+ if (!new_member)
+ goto out;
+
+ new_member->member_name = saved_member;
+ mb(); /* Instead of using spinlock. */
+ member = group->first_member;
+
+ if (member) {
+ while (member->next)
+ member = member->next;
+ member->next = new_member;
+ } else
+ group->first_member = new_member;
+
+ error = 0;
+out: ;
+ up(&lock);
+
+ return error;
+}
+
+/**
+ * tmy_add_group_policy - add or delete path group policy.
+ * @data: a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_group_policy(char *data, const int is_delete)
+{
+ char *cp = strchr(data, ' ');
+
+ if (!cp)
+ return -EINVAL;
+ *cp++ = '\0';
+ return tmy_add_group_entry(data, cp, is_delete);
+}
+
+static struct group_entry *tmy_new_path_group(const char *group_name)
+{
+ int i;
+ struct group_entry *group;
+
+ for (i = 0; i <= 1; i++) {
+ for (group = group_list; group; group = group->next) {
+ if (strcmp(group_name, group->group_name->name) == 0)
+ return group;
+ }
+
+ if (i == 0) {
+ /*
+ * Add a dummy entry to create new path group
+ * and delete that entry.
+ */
+ tmy_add_group_entry(group_name, "/", 0);
+ tmy_add_group_entry(group_name, "/", 1);
+ }
+ }
+
+ return NULL;
+}
+
+static int tmy_path_match_group(const struct path_info *pathname,
+ const struct group_entry *group,
+ const int may_use_pattern)
+{
+ struct group_member *member;
+
+ for (member = group->first_member; member; member = member->next) {
+ if (member->is_deleted)
+ continue;
+
+ if (!member->member_name->is_patterned) {
+ if (!tmy_pathcmp(pathname, member->member_name))
+ return 1;
+ } else if (may_use_pattern) {
+ if (tmy_path_match(pathname, member->member_name))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * tmy_read_group_policy - read path group policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_group_policy(struct io_buffer *head)
+{
+ struct group_entry *group = head->read_var1;
+ struct group_member *member = head->read_var2;
+
+ if (!group)
+ group = group_list;
+
+ while (group) {
+ head->read_var1 = group;
+ if (!member)
+ member = group->first_member;
+ while (member) {
+ head->read_var2 = member;
+ if (!member->is_deleted &&
+ tmy_io_printf(head,
+ TMY_PATH_GROUP "%s %s\n",
+ group->group_name->name,
+ member->member_name->name))
+ break;
+ member = member->next;
+ }
+
+ if (member)
+ break;
+
+ head->read_var2 = NULL;
+ group = group->next;
+ }
+
+ return group ? -ENOMEM : 0;
+}
+
+/************************* FILE PATTERN HANDLER *************************/
+
+static struct pattern_entry *pattern_list;
+
+static int tmy_add_pattern_entry(const char *pattern, const int is_delete)
+{
+ struct pattern_entry *new_entry;
+ struct pattern_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(pattern, 0, 1, 0, __FUNCTION__))
+ return -EINVAL;
+
+ saved = tmy_save_name(pattern);
+ if (!saved)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = pattern_list; ptr; ptr = ptr->next) {
+ if (saved == ptr->pattern) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+
+ if (!new_entry)
+ goto out;
+ new_entry->pattern = saved;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = pattern_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ pattern_list = new_entry;
+
+ error = 0;
+out: ;
+ up(&lock);
+ return error;
+}
+
+static const struct path_info *tmy_get_pattern(const struct path_info *filename)
+{
+ struct pattern_entry *ptr;
+ const struct path_info *pattern = NULL;
+
+ for (ptr = pattern_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+ if (!tmy_path_match(filename, ptr->pattern))
+ continue;
+
+ pattern = ptr->pattern;
+ if (!tmy_strendswith(pattern->name, "/\\*"))
+ break;
+ }
+
+ if (pattern)
+ filename = pattern;
+
+ return filename;
+}
+
+/**
+ * tmy_add_pattern_policy - add or delete file pattern policy.
+ * @pattern: pointer to file pattern entry.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_pattern_policy(char *pattern, const int is_delete)
+{
+ return tmy_add_pattern_entry(pattern, is_delete);
+}
+
+/**
+ * tmy_read_pattern_policy - read file pattern policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_pattern_policy(struct io_buffer *head)
+{
+ struct pattern_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = pattern_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted &&
+ tmy_io_printf(head, TMY_FILE_PATTERN "%s\n",
+ ptr->pattern->name))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/*********************** NON REWRITABLE FILE HANDLER ***********************/
+
+static struct no_rewrite_entry *no_rewrite_list;
+
+static int tmy_add_no_rewrite_entry(const char *pattern, const int is_delete)
+{
+ struct no_rewrite_entry *new_entry;
+ struct no_rewrite_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(pattern, 0, 0, 0, __FUNCTION__))
+ return -EINVAL;
+ saved = tmy_save_name(pattern);
+ if (!saved)
+ return -ENOMEM;
+
+ down(&lock);
+ for (ptr = no_rewrite_list; ptr; ptr = ptr->next) {
+ if (ptr->pattern == saved) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->pattern = saved;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = no_rewrite_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ no_rewrite_list = new_entry;
+
+ error = 0;
+out: ;
+ up(&lock);
+
+ return error;
+}
+
+static int tmy_is_no_rewrite_file(const struct path_info *filename)
+{
+ struct no_rewrite_entry *ptr;
+
+ for (ptr = no_rewrite_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+ if (!tmy_path_match(filename, ptr->pattern))
+ continue;
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * tmy_add_no_rewrite_policy - add or delete no-rewrite policy.
+ * @pattern: pointer to no-rewrite entry.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_no_rewrite_policy(char *pattern, const int is_delete)
+{
+ return tmy_add_no_rewrite_entry(pattern, is_delete);
+}
+
+/**
+ * tmy_read_no_rewrite_policy - read no-rewrite policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_no_rewrite_policy(struct io_buffer *head)
+{
+ struct no_rewrite_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = no_rewrite_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted &&
+ tmy_io_printf(head, TMY_DENY_REWRITE "%s\n",
+ ptr->pattern->name))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/************************* FILE ACL HANDLER *************************/
+
+/*
+ * @is_add: 1 add this entry if not quota exceeded
+ * -1 always add this entry
+ * 0 remove this entry
+ */
+static int tmy_add_file_acl(const char *filename,
+ u8 perm,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const u8 is_add)
+{
+ const struct path_info *saved;
+ struct acl_info *ptr;
+ int error = -ENOMEM;
+ u8 is_group = 0;
+
+ if (!domain)
+ return -EINVAL;
+ if (perm > 7 || !perm) {
+ printk(KERN_DEBUG "%s: Invalid permission '%d %s'\n",
+ __FUNCTION__, perm, filename);
+ return -EINVAL;
+ }
+ if (!tmy_correct_path(filename, 0, 0, 0, __FUNCTION__))
+ return -EINVAL;
+
+ if (filename[0] == '@') {
+ /* This cast is OK because I don't dereference. */
+ saved = (struct path_info *) tmy_new_path_group(filename + 1);
+ if (!saved)
+ return -ENOMEM;
+ is_group = 1;
+ } else {
+
+ saved = tmy_save_name(filename);
+ if (!saved)
+ return -ENOMEM;
+
+ if (!saved->is_dir) {
+ if (is_add && perm == 4 &&
+ tmy_globally_readable(saved))
+ return 0;
+ } else if ((perm & 2) == 0 && is_add)
+ /* Don't add if the directory doesn't have */
+ /* write permission. */
+ return 0;
+
+ }
+
+ down(&domain_acl_lock);
+
+ if (!is_add)
+ goto remove;
+
+ ptr = domain->first_acl_ptr;
+ if (!ptr)
+ goto first_entry;
+
+ while (1) {
+ struct file_acl *acl = (struct file_acl *) ptr;
+
+ if ((ptr->type == TMY_TYPE_FILE_ACL) &&
+ ptr->cond == cond &&
+ (acl->u.filename == saved)) {
+ if (ptr->is_deleted) {
+ acl->perm = 0;
+ mb(); /* Instead of using spinlock. */
+ ptr->is_deleted = 0;
+ }
+ /* Found. Just 'OR' the permission bits. */
+ acl->perm |= perm;
+ error = 0;
+ tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+ break;
+ }
+
+ if (ptr->next) {
+ ptr = ptr->next;
+ continue;
+ }
+
+first_entry: ;
+ if (is_add == 1 && tmy_too_many_acl(domain))
+ break;
+
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ break;
+
+ acl->head.type = TMY_TYPE_FILE_ACL;
+ acl->head.cond = cond;
+ acl->perm = perm;
+ acl->u_is_group = is_group;
+ acl->u.filename = saved;
+ error = tmy_add_acl(ptr, domain,
+ (struct acl_info *) acl);
+ break;
+ }
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct file_acl *acl = (struct file_acl *) ptr;
+
+ if (ptr->type != TMY_TYPE_FILE_ACL ||
+ ptr->cond != cond ||
+ ptr->is_deleted ||
+ acl->perm != perm ||
+ acl->u.filename != saved)
+ continue;
+
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ up(&domain_acl_lock);
+ return error;
+}
+
+static int tmy_file_acl(const struct path_info *filename, const u8 perm,
+ struct obj_info *obj)
+{
+ const struct domain_info *domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+ const int may_use_pat = ((perm & 1) == 0);
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+ if (!filename->is_dir) {
+ if (perm == 4 && tmy_globally_readable(filename))
+ return 0;
+ }
+
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct file_acl *acl = (struct file_acl *) ptr;
+
+ if (ptr->type != TMY_TYPE_FILE_ACL ||
+ ptr->is_deleted ||
+ (acl->perm & perm) != perm ||
+ tmy_check_condition(ptr->cond, obj))
+ continue;
+
+ if (acl->u_is_group) {
+ if (tmy_path_match_group(filename,
+ acl->u.group,
+ may_use_pat))
+ return 0;
+ } else {
+ if ((may_use_pat || !acl->u.filename->is_patterned) &&
+ tmy_path_match(filename, acl->u.filename))
+ return 0;
+ }
+ }
+
+ return -EPERM;
+}
+
+static int tmy_file_perm2(const struct path_info *filename,
+ const u8 perm,
+ struct obj_info *obj,
+ const char *operation)
+{
+ int error = 0;
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ const int is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+
+ if (!filename)
+ return 0;
+
+ error = tmy_file_acl(filename, perm, obj);
+
+ tmy_audit_file_log(filename, perm, !error, is_enforce);
+
+ if (!error)
+ return error;
+
+ if (is_enforce)
+ error = tmy_supervisor("%s\n%d %s\n",
+ domain->domainname->name,
+ perm, filename->name);
+
+ else if (tmy_accept(TMY_MAC_FOR_FILE)) {
+ /* Don't use patterns if execution bit is on. */
+ const struct path_info *patterned =
+ ((perm & 1) == 0) ?
+ tmy_get_pattern(filename) : filename;
+ tmy_add_file_acl(patterned->name, perm, domain, NULL, 1);
+ }
+
+ if (!is_enforce)
+ error = 0;
+
+ return error;
+}
+
+/**
+ * tmy_file_perm - check permission for sysctl(2) operation.
+ * @filename0: pointer to filename returned by sysctlpath_from_table().
+ * @perm: mode (read = 4, write = 2, read-write = 6).
+ * @operation: pointer to error message.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_file_perm(const char *filename0, const u8 perm, const char *operation)
+{
+ struct path_info filename;
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ filename.name = filename0;
+ tmy_fill_path_info(&filename);
+
+ return tmy_file_perm2(&filename, perm, NULL, operation);
+}
+
+/**
+ * tmy_add_file_policy - add or delete file policy.
+ * @data: a line to parse.
+ * @domain: pointer to "struct domain_info".
+ * @cond: pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_file_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const int is_delete)
+{
+ char *filename = strchr(data, ' ');
+ unsigned int perm;
+ u8 type;
+
+ if (!filename)
+ return -EINVAL;
+ *filename++ = '\0';
+
+ if (sscanf(data, "%u", &perm) == 1)
+ return tmy_add_file_acl(filename, (u8) perm, domain, cond,
+ is_delete ? 0 : -1);
+
+ if (strncmp(data, "allow_", 6))
+ goto out;
+
+ data += 6;
+
+ for (type = 0; acl_type_array[type].keyword; type++) {
+ if (strcmp(data, acl_type_array[type].keyword))
+ continue;
+
+ if (acl_type_array[type].paths == 2) {
+ char *filename2 = strchr(filename, ' ');
+
+ if (!filename2)
+ break;
+ *filename2++ = '\0';
+ return tmy_add_double_write_acl(type, filename,
+ filename2,
+ domain, cond,
+ is_delete ? 0 : -1);
+ } else
+ return tmy_add_single_write_acl(type, filename,
+ domain, cond,
+ is_delete ? 0 : -1);
+
+ break;
+ }
+out: ;
+ return -EINVAL;
+}
+
+/*
+ * @is_add: 1 add this entry if not quota exceeded
+ * -1 always add this entry
+ * 0 remove this entry
+ */
+static int tmy_add_single_write_acl(const u8 type,
+ const char *filename,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const u8 is_add)
+{
+ const struct path_info *saved;
+ struct acl_info *ptr;
+ int error = -ENOMEM;
+ u8 is_group = 0;
+
+ if (!domain)
+ return -EINVAL;
+ if (!tmy_correct_path(filename, 0, 0, 0, __FUNCTION__))
+ return -EINVAL;
+
+ if (filename[0] == '@') {
+ /* This cast is OK because I don't dereference. */
+ saved = (struct path_info *) tmy_new_path_group(filename + 1);
+ if (!saved)
+ return -ENOMEM;
+ is_group = 1;
+ } else {
+ saved = tmy_save_name(filename);
+ if (!saved)
+ return -ENOMEM;
+ }
+
+ down(&domain_acl_lock);
+ if (!is_add)
+ goto remove;
+
+ ptr = domain->first_acl_ptr;
+ if (!ptr)
+ goto first_entry;
+ while (1) {
+ struct single_acl *acl = (struct single_acl *) ptr;
+
+ if (ptr->type == type && ptr->cond == cond) {
+ if (acl->u.filename == saved) {
+ ptr->is_deleted = 0;
+ /* Found. Nothing to do. */
+ error = 0;
+ break;
+ }
+ }
+ if (ptr->next) {
+ ptr = ptr->next;
+ continue;
+ }
+
+first_entry: ;
+ if (is_add == 1 && tmy_too_many_acl(domain))
+ break;
+
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ break;
+
+ acl->head.type = type;
+ acl->head.cond = cond;
+ acl->u_is_group = is_group;
+ acl->u.filename = saved;
+ error = tmy_add_acl(ptr, domain,
+ (struct acl_info *) acl);
+ break;
+ }
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct single_acl *acl = (struct single_acl *) ptr;
+
+ if (ptr->type != type || ptr->is_deleted ||
+ ptr->cond != cond || acl->u.filename != saved)
+ continue;
+
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ up(&domain_acl_lock);
+
+ return error;
+}
+
+/*
+ * @is_add: 1 add this entry if not quota exceeded
+ * -1 always add this entry
+ * 0 remove this entry
+ */
+static int tmy_add_double_write_acl(const u8 type,
+ const char *filename1,
+ const char *filename2,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const u8 is_add)
+{
+ const struct path_info *saved1;
+ const struct path_info *saved2;
+ struct acl_info *ptr;
+ int error = -ENOMEM;
+ u8 is_group1 = 0;
+ u8 is_group2 = 0;
+
+ if (!domain)
+ return -EINVAL;
+ if (!tmy_correct_path(filename1, 0, 0, 0, __FUNCTION__) ||
+ !tmy_correct_path(filename2, 0, 0, 0, __FUNCTION__))
+ return -EINVAL;
+
+ if (filename1[0] == '@') {
+ /* This cast is OK because I don't dereference. */
+ saved1 = (struct path_info *) tmy_new_path_group(filename1 + 1);
+ if (!saved1)
+ return -ENOMEM;
+ is_group1 = 1;
+ } else {
+ saved1 = tmy_save_name(filename1);
+ if (!saved1)
+ return -ENOMEM;
+ }
+
+ if (filename2[0] == '@') {
+ /* This cast is OK because I don't dereference. */
+ saved2 = (struct path_info *) tmy_new_path_group(filename2 + 1);
+ if (!saved2)
+ return -ENOMEM;
+ is_group2 = 1;
+ } else {
+ saved2 = tmy_save_name(filename2);
+ if (!saved2)
+ return -ENOMEM;
+ }
+
+ down(&domain_acl_lock);
+
+ if (!is_add)
+ goto remove;
+
+ ptr = domain->first_acl_ptr;
+ if (!ptr)
+ goto first_entry;
+ while (1) {
+ struct double_acl *acl = (struct double_acl *) ptr;
+
+ if (ptr->type == type && ptr->cond == cond) {
+ if (acl->u1.filename1 == saved1 &&
+ acl->u2.filename2 == saved2) {
+ ptr->is_deleted = 0;
+ /* Found. Nothing to do. */
+ error = 0;
+ break;
+ }
+ }
+
+ if (ptr->next) {
+ ptr = ptr->next;
+ continue;
+ }
+
+first_entry: ;
+ if (is_add == 1 && tmy_too_many_acl(domain))
+ break;
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ break;
+
+ acl->head.type = type;
+ acl->head.cond = cond;
+ acl->u1_is_group = is_group1;
+ acl->u2_is_group = is_group2;
+ acl->u1.filename1 = saved1;
+ acl->u2.filename2 = saved2;
+ error = tmy_add_acl(ptr, domain,
+ (struct acl_info *) acl);
+ break;
+ }
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct double_acl *acl = (struct double_acl *) ptr;
+
+ if (ptr->type != type || ptr->is_deleted ||
+ ptr->cond != cond ||
+ acl->u1.filename1 != saved1 ||
+ acl->u2.filename2 != saved2)
+ continue;
+
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ up(&domain_acl_lock);
+ return error;
+}
+
+static int tmy_single_write_acl(const u8 type,
+ const struct path_info *filename,
+ struct obj_info *obj)
+{
+ const struct domain_info *domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct single_acl *acl = (struct single_acl *) ptr;
+
+ if (ptr->type != type || ptr->is_deleted ||
+ tmy_check_condition(ptr->cond, obj))
+ continue;
+
+ if (acl->u_is_group) {
+ if (!tmy_path_match_group(filename, acl->u.group, 1))
+ continue;
+ } else {
+ if (!tmy_path_match(filename, acl->u.filename))
+ continue;
+ }
+ return 0;
+ }
+
+ return -EPERM;
+}
+
+static int tmy_double_write_acl(const u8 type,
+ const struct path_info *filename1,
+ const struct path_info *filename2,
+ struct obj_info *obj)
+{
+ const struct domain_info *domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct double_acl *acl = (struct double_acl *) ptr;
+
+ if (ptr->type != type || ptr->is_deleted ||
+ tmy_check_condition(ptr->cond, obj))
+ continue;
+
+ if (acl->u1_is_group) {
+ if (!tmy_path_match_group(filename1,
+ acl->u1.group1, 1))
+ continue;
+ } else {
+ if (!tmy_path_match(filename1, acl->u1.filename1))
+ continue;
+ }
+
+ if (acl->u2_is_group) {
+ if (!tmy_path_match_group(filename2,
+ acl->u2.group2, 1))
+ continue;
+ } else {
+ if (!tmy_path_match(filename2, acl->u2.filename2))
+ continue;
+ }
+
+ return 0;
+ }
+
+ return -EPERM;
+}
+
+static int tmy_single_write_perm2(const unsigned int operation,
+ const struct path_info *filename,
+ struct obj_info *obj)
+{
+ int error;
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ const int is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ error = tmy_single_write_acl(operation, filename, obj);
+
+ tmy_audit_write_log(tmy_acltype2keyword(operation),
+ filename, NULL, !error, is_enforce);
+
+ if (!error)
+ goto ok;
+
+ if (is_enforce)
+ error = tmy_supervisor("%s\nallow_%s %s\n",
+ domain->domainname->name,
+ tmy_acltype2keyword(operation),
+ filename->name);
+
+ else if (tmy_accept(TMY_MAC_FOR_FILE))
+ tmy_add_single_write_acl(operation,
+ tmy_get_pattern(filename)->name,
+ domain, NULL, 1);
+
+ if (!is_enforce)
+ error = 0;
+
+ok: ;
+ if (!error && operation == TMY_TYPE_TRUNCATE_ACL &&
+ tmy_is_no_rewrite_file(filename))
+ error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
+ filename, obj);
+
+ return error;
+}
+
+/**
+ * tmy_exec_perm - check permission for execve(2) operation.
+ * @filename: pointer to filename to execute.
+ * @filp: pointer to "struct file".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_exec_perm(const struct path_info *filename, struct file *filp)
+{
+ struct obj_info obj;
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = filp->f_dentry;
+ obj.path1_vfsmnt = filp->f_vfsmnt;
+ return tmy_file_perm2(filename, 1, &obj, "do_execve");
+}
+
+/**
+ * tmy_open_perm - check permission for open(2) operation.
+ * @dentry: pointer to "struct dentry".
+ * @mnt: pointer to "struct vfsmount".
+ * @flag: open flags.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_open_perm(struct dentry *dentry,
+ struct vfsmount *mnt,
+ const int flag)
+{
+ struct obj_info obj;
+ const int acc_mode = ACC_MODE(flag);
+ int error = -ENOMEM;
+ struct path_info *buf;
+ const int is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+ if (acc_mode == 0)
+ return 0;
+ if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode))
+ /* I don't check directories here */
+ /* because mkdir() and rmdir() don't call me. */
+ return 0;
+
+ buf = tmy_get_path(dentry, mnt);
+
+ if (!buf)
+ goto out;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = dentry;
+ obj.path1_vfsmnt = mnt;
+
+ error = 0;
+ if ((acc_mode & MAY_WRITE) &&
+ ((flag & O_TRUNC) || !(flag & O_APPEND)) &&
+ tmy_is_no_rewrite_file(buf))
+ error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
+ buf, &obj);
+
+ if (error == 0)
+ error = tmy_file_perm2(buf, acc_mode, &obj, "open");
+
+ if (error == 0 && (flag & O_TRUNC))
+ error = tmy_single_write_perm2(TMY_TYPE_TRUNCATE_ACL,
+ buf, &obj);
+
+ tmy_free(buf);
+
+out: ;
+ if (!is_enforce)
+ error = 0;
+ return error;
+}
+
+/**
+ * tmy_single_write_perm - check permission for create(2) etc. operation.
+ * @operation: operation index number.
+ * @dentry: pointer to "struct dentry".
+ * @mnt: pointer to "struct vfsmount".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_single_write_perm(const unsigned int operation,
+ struct dentry *dentry,
+ struct vfsmount *mnt)
+{
+ struct obj_info obj;
+ int error = -ENOMEM;
+ struct path_info *buf;
+ const int is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ buf = tmy_get_path(dentry, mnt);
+
+ if (!buf)
+ goto out;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = dentry;
+ obj.path1_vfsmnt = mnt;
+
+ switch (operation) {
+ case TMY_TYPE_MKDIR_ACL:
+ case TMY_TYPE_RMDIR_ACL:
+ if (!buf->is_dir) {
+ strcat((char *) buf->name, "/");
+ tmy_fill_path_info(buf);
+ }
+ }
+ error = tmy_single_write_perm2(operation, buf, &obj);
+ tmy_free(buf);
+
+out: ;
+ if (!is_enforce)
+ error = 0;
+
+ return error;
+}
+
+/**
+ * tmy_rewrite_perm - check permission for truncate/overwrite operation.
+ * @filp: pointer to "struct file".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_rewrite_perm(struct file *filp)
+{
+ int error = -ENOMEM;
+ const int is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+ struct path_info *buf = tmy_get_path(filp->f_dentry, filp->f_vfsmnt);
+
+ if (!buf)
+ goto out;
+
+ if (tmy_is_no_rewrite_file(buf)) {
+ struct obj_info obj;
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = filp->f_dentry;
+ obj.path1_vfsmnt = filp->f_vfsmnt;
+ error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
+ buf, &obj);
+ } else
+ error = 0;
+
+ tmy_free(buf);
+
+out: ;
+ if (!is_enforce)
+ error = 0;
+ return error;
+}
+
+/**
+ * tmy_double_write_perm - check permission for link(2)/rename(2) operation.
+ * @operation: operation index number.
+ * @dentry1: pointer to "struct dentry".
+ * @mnt1: pointer to "struct vfsmount".
+ * @dentry2: pointer to "struct dentry".
+ * @mnt2: pointer to "struct vfsmount".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_double_write_perm(const unsigned int operation,
+ struct dentry *dentry1,
+ struct vfsmount *mnt1,
+ struct dentry *dentry2,
+ struct vfsmount *mnt2)
+{
+ struct obj_info obj;
+ int error = -ENOMEM;
+ struct path_info *buf1;
+ struct path_info *buf2;
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ const int is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+ buf1 = tmy_get_path(dentry1, mnt1);
+ buf2 = tmy_get_path(dentry2, mnt2);
+
+ if (!buf1 || !buf2)
+ goto out;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = dentry1;
+ obj.path1_vfsmnt = mnt1;
+ obj.path2_dentry = dentry2;
+ obj.path2_vfsmnt = mnt2;
+ if (operation == TMY_TYPE_RENAME_ACL) {
+ /* TMY_TYPE_LINK_ACL can't reach here for directory. */
+ if (dentry1->d_inode && S_ISDIR(dentry1->d_inode->i_mode)) {
+ if (!buf1->is_dir) {
+ strcat((char *) buf1->name, "/");
+ tmy_fill_path_info(buf1);
+ }
+ if (!buf2->is_dir) {
+ strcat((char *) buf2->name, "/");
+ tmy_fill_path_info(buf2);
+ }
+ }
+ }
+ error = tmy_double_write_acl(operation, buf1, buf2, &obj);
+
+ tmy_audit_write_log(tmy_acltype2keyword(operation),
+ buf1, buf2, !error, is_enforce);
+
+ if (!error)
+ goto out;
+
+ if (is_enforce)
+ error = tmy_supervisor("%s\nallow_%s %s %s\n",
+ domain->domainname->name,
+ tmy_acltype2keyword(operation),
+ buf1->name, buf2->name);
+ else if (tmy_accept(TMY_MAC_FOR_FILE))
+ tmy_add_double_write_acl(operation,
+ tmy_get_pattern(buf1)->name,
+ tmy_get_pattern(buf2)->name,
+ domain, NULL, 1);
+
+out: ;
+ tmy_free(buf1);
+ tmy_free(buf2);
+ if (!is_enforce)
+ error = 0;
+ return error;
+}
argv[0] check functions for TOMOYO Linux.
If the executed program name and argv[0] is different,
TOMOYO Linux checks permission.
Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/exec.c | 230 +++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 230 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/exec.c 2007-08-24 15:51:37.000000000 +0900
@@ -0,0 +1,230 @@
+/*
+ * security/tomoyo/exec.c
+ *
+ * Argv0 access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_argv0_log(const struct path_info *filename,
+ const char *argv0,
+ const int is_granted,
+ const int is_enforce)
+{
+ char *buf;
+ int len;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ len = filename->total_len + strlen(argv0) + 8;
+ buf = tmy_init_audit_log(&len);
+
+ if (!buf)
+ return -ENOMEM;
+
+ snprintf(buf + strlen(buf),
+ len - strlen(buf) - 1,
+ TMY_ALLOW_ARGV0 "%s %s",
+ filename->name,
+ argv0);
+
+ return tmy_write_audit_log(buf, is_granted, is_enforce);
+}
+
+/************************* ARGV0 MISMATCH HANDLER *************************/
+
+/*
+ * @is_add: 1 add this entry if not quota exceeded
+ * -1 always add this entry
+ * 0 remove this entry
+ */
+static int tmy_add_argv0_entry(const char *filename,
+ const char *argv0,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_add)
+{
+ struct acl_info *ptr;
+ const struct path_info *saved_filename;
+ const struct path_info *saved_argv0;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(filename, 1, 0, -1, __FUNCTION__) ||
+ !tmy_correct_path(argv0, -1, 0, -1, __FUNCTION__) ||
+ strchr(argv0, '/'))
+ return -EINVAL;
+
+ saved_filename = tmy_save_name(filename);
+ saved_argv0 = tmy_save_name(argv0);
+ if (!saved_filename || !saved_argv0)
+ return -ENOMEM;
+
+ down(&domain_acl_lock);
+
+ if (!is_add)
+ goto remove;
+
+ ptr = domain->first_acl_ptr;
+ if (!ptr)
+ goto first_entry;
+ while (1) {
+ struct argv0_acl *acl = (struct argv0_acl *) ptr;
+
+ if (ptr->type == TMY_TYPE_ARGV0_ACL && ptr->cond == cond &&
+ acl->filename == saved_filename &&
+ acl->argv0 == saved_argv0) {
+ ptr->is_deleted = 0;
+ /* Found. Nothing to do. */
+ error = 0;
+ break;
+ }
+
+ if (ptr->next) {
+ ptr = ptr->next;
+ continue;
+ }
+
+first_entry: ;
+ if (is_add == 1 && tmy_too_many_acl(domain))
+ break;
+
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ break;
+
+ acl->head.type = TMY_TYPE_ARGV0_ACL;
+ acl->head.cond = cond;
+ acl->filename = saved_filename;
+ acl->argv0 = saved_argv0;
+ error = tmy_add_acl(ptr, domain,
+ (struct acl_info *) acl);
+
+ break;
+ }
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct argv0_acl *acl = (struct argv0_acl *) ptr;
+
+ if (ptr->type != TMY_TYPE_ARGV0_ACL ||
+ ptr->cond != cond || ptr->is_deleted ||
+ acl->filename != saved_filename ||
+ acl->argv0 != saved_argv0)
+ continue;
+
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ up(&domain_acl_lock);
+
+ return error;
+}
+
+static int tmy_argv0_acl(const struct path_info *filename,
+ const char *argv0_)
+{
+ const struct domain_info *domain = TMY_SECURITY->domain;
+ int error = -EPERM;
+ struct acl_info *ptr;
+ struct path_info argv0;
+
+ if (!tmy_flags(TMY_MAC_FOR_ARGV0))
+ return 0;
+
+ argv0.name = argv0_;
+ tmy_fill_path_info(&argv0);
+
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct argv0_acl *acl = (struct argv0_acl *) ptr;
+
+ if (ptr->type == TMY_TYPE_ARGV0_ACL &&
+ ptr->is_deleted == 0 &&
+ tmy_check_condition(ptr->cond, NULL) == 0 &&
+ tmy_path_match(filename, acl->filename) &&
+ tmy_path_match(&argv0, acl->argv0)) {
+ error = 0;
+ break;
+ }
+ }
+
+ return error;
+}
+
+/**
+ * tmy_argv0_perm - check for argv[0] permission.
+ * @filename: pointer to filename.
+ * @argv0: pointer to basename of argv[0].
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_argv0_perm(const struct path_info *filename, const char *argv0)
+{
+ int error = 0;
+ const int is_enforce = tmy_enforce(TMY_MAC_FOR_ARGV0);
+
+ if (!tmy_flags(TMY_MAC_FOR_ARGV0))
+ return 0;
+ if (!filename || !argv0 || !*argv0)
+ return 0;
+
+ error = tmy_argv0_acl(filename, argv0);
+
+ tmy_audit_argv0_log(filename, argv0, !error, is_enforce);
+
+ if (error) {
+ struct domain_info * const domain = TMY_SECURITY->domain;
+
+ if (is_enforce)
+ error = tmy_supervisor("%s\n" TMY_ALLOW_ARGV0 "%s %s\n",
+ domain->domainname->name,
+ filename->name, argv0);
+
+ else if (tmy_accept(TMY_MAC_FOR_ARGV0))
+ tmy_add_argv0_entry(filename->name, argv0, domain,
+ NULL, 1);
+
+ if (!is_enforce)
+ error = 0;
+ }
+
+ return error;
+}
+
+/**
+ * tmy_add_argv0_policy - add or delete argv[0] policy.
+ * @data: a line to parse.
+ * @domain: pointer to "struct domain_info".
+ * @cond: pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_argv0_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const int is_delete)
+{
+ char *argv0 = strchr(data, ' ');
+
+ if (!argv0)
+ return -EINVAL;
+
+ *argv0++ = '\0';
+
+ return tmy_add_argv0_entry(data, argv0, domain, cond,
+ is_delete ? 0 : -1);
+}
Network access control functions for TOMOYO Linux.
TOMOYO Linux checks permission by the following four parameters.
* protocol type (TCP, UDP, RAW)
* access type (bind, listen, connect, accept)
* IP address (Both IPv4 and IPv6 are available)
* port number
In order to check 'TCP accept' and 'UDP accept'(recv),
LSM expansion patch ([TOMOYO /]) is needed.
Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/net.c | 983 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 983 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/net.c 2007-08-24 15:51:37.000000000 +0900
@@ -0,0 +1,983 @@
+/*
+ * security/tomoyo/net.c
+ *
+ * Network access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+#include <net/ip.h>
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_network_log(const int is_ipv6,
+ const char *operation,
+ const u32 *address,
+ const u16 port,
+ const int is_granted,
+ const int is_enforce)
+{
+ char *buf;
+ int len = 256;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ buf = tmy_init_audit_log(&len);
+ if (!buf)
+ return -ENOMEM;
+
+ snprintf(buf + strlen(buf), len - strlen(buf) - 1,
+ TMY_ALLOW_NETWORK "%s ", operation);
+
+ if (is_ipv6)
+ tmy_print_ipv6(buf + strlen(buf), len - strlen(buf),
+ (const u16 *) address);
+ else {
+ u32 ip = *address;
+ snprintf(buf + strlen(buf), len - strlen(buf) - 1,
+ NIPQUAD_FMT, NIPQUAD(ip));
+ }
+
+ snprintf(buf + strlen(buf), len - strlen(buf) - 1, " %u", port);
+
+ return tmy_write_audit_log(buf, is_granted, is_enforce);
+}
+
+/************************* ADDRESS GROUP HANDLER *************************/
+
+/* List of address group. */
+static struct addr_group_entry *group_list;
+
+static int tmy_add_addr_group_entry(const char *group_name,
+ const u8 is_ipv6,
+ const u16 *min_address,
+ const u16 *max_address,
+ const int is_delete)
+{
+ static DECLARE_MUTEX(lock);
+ struct addr_group_entry *new_group;
+ struct addr_group_entry *group;
+ struct addr_group_member *new_member;
+ struct addr_group_member *member;
+ const struct path_info *saved_group_name;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(group_name, 0, 0, 0, __FUNCTION__) ||
+ !group_name[0])
+ return -EINVAL;
+
+ saved_group_name = tmy_save_name(group_name);
+ if (!saved_group_name)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (group = group_list; group; group = group->next) {
+
+ if (saved_group_name != group->group_name)
+ continue;
+
+ for (member = group->first_member;
+ member;
+ member = member->next) {
+
+ if (member->is_ipv6 != is_ipv6)
+ continue;
+
+ if (is_ipv6) {
+ if (memcmp(member->min.ipv6, min_address, 16) ||
+ memcmp(member->max.ipv6, max_address, 16))
+ continue;
+ } else {
+ if (member->min.ipv4 != * (u32 *) min_address ||
+ member->max.ipv4 != * (u32 *) max_address)
+ continue;
+ }
+
+ member->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ break;
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ if (!group) {
+ new_group = tmy_alloc_element(sizeof(*new_group));
+ if (!new_group)
+ goto out;
+ new_group->group_name = saved_group_name;
+ mb(); /* Instead of using spinlock. */
+ group = group_list;
+ if (group) {
+ while (group->next)
+ group = group->next;
+ group->next = new_group;
+ } else
+ group_list = new_group;
+ group = new_group;
+ }
+
+ new_member = tmy_alloc_element(sizeof(*new_member));
+ if (!new_member)
+ goto out;
+
+ new_member->is_ipv6 = is_ipv6;
+
+ if (is_ipv6) {
+ memmove(new_member->min.ipv6, min_address, 16);
+ memmove(new_member->max.ipv6, max_address, 16);
+ } else {
+ new_member->min.ipv4 = * (u32 *) min_address;
+ new_member->max.ipv4 = * (u32 *) max_address;
+ }
+
+ mb(); /* Instead of using spinlock. */
+
+ member = group->first_member;
+ if (member) {
+ while (member->next)
+ member = member->next;
+ member->next = new_member;
+ } else
+ group->first_member = new_member;
+
+ error = 0;
+out: ;
+ up(&lock);
+
+ return error;
+}
+
+/**
+ * tmy_add_addr_group_policy - add or delete address group policy.
+ * @data: a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_addr_group_policy(char *data, const int is_delete)
+{
+ int count;
+ int is_ipv6;
+ u16 min_address[8];
+ u16 max_address[8];
+ unsigned int min[8];
+ unsigned int max[8];
+ char *cp = strchr(data, ' ');
+
+ if (!cp)
+ return -EINVAL;
+
+ *cp++ = '\0';
+ count = sscanf(cp,
+ NIP6_FMT "-" NIP6_FMT,
+ &min[0], &min[1], &min[2], &min[3],
+ &min[4], &min[5], &min[6], &min[7],
+ &max[0], &max[1], &max[2], &max[3],
+ &max[4], &max[5], &max[6], &max[7]);
+
+ if (count == 8 || count == 16) {
+
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ min_address[i] = htons((u16) min[i]);
+ max_address[i] = htons((u16) max[i]);
+ }
+ if (count == 8)
+ memmove(max_address, min_address, sizeof(min_address));
+ is_ipv6 = 1;
+
+ goto ok;
+
+ }
+
+ count = sscanf(cp,
+ NIPQUAD_FMT "-" NIPQUAD_FMT,
+ &min[0], &min[1],
+ &min[2], &min[3],
+ &max[0], &max[1],
+ &max[2], &max[3]);
+
+ if (count == 4 || count == 8) {
+ u32 ip = ((((u8) min[0]) << 24) +
+ (((u8) min[1]) << 16) +
+ (((u8) min[2]) << 8) +
+ (u8) min[3]);
+
+ * (u32 *) min_address = ip;
+
+ if (count == 8)
+ ip = ((((u8) max[0]) << 24) +
+ (((u8) max[1]) << 16) +
+ (((u8) max[2]) << 8) +
+ (u8) max[3]);
+
+ * (u32 *) max_address = ip;
+ is_ipv6 = 0;
+
+ goto ok;
+
+ }
+
+ return -EINVAL;
+
+ok: ;
+ return tmy_add_addr_group_entry(data, is_ipv6, min_address, max_address,
+ is_delete);
+}
+
+static struct addr_group_entry *tmy_new_addr_group(const char *name)
+{
+ int i;
+ struct addr_group_entry *group;
+
+ for (i = 0; i <= 1; i++) {
+ for (group = group_list; group; group = group->next) {
+
+ if (strcmp(name, group->group_name->name) == 0)
+ return group;
+
+ }
+
+ if (i == 0) {
+ /*
+ * Add a dummy entry to create new address group
+ * and delete that entry.
+ */
+ const u16 dum[2] = { 0, 0 };
+ tmy_add_addr_group_entry(name, 0, dum, dum, 0);
+ tmy_add_addr_group_entry(name, 0, dum, dum, 1);
+ }
+ }
+
+ return NULL;
+}
+
+static int tmy_address_match_group(const u8 is_ipv6,
+ const u32 *address,
+ const struct addr_group_entry *group)
+{
+ struct addr_group_member *member;
+ const u32 ip = ntohl(*address);
+
+ for (member = group->first_member; member; member = member->next) {
+
+ if (member->is_deleted)
+ continue;
+
+ if (member->is_ipv6) {
+
+ if (is_ipv6 &&
+ memcmp(member->min.ipv6, address, 16) <= 0 &&
+ memcmp(address, member->max.ipv6, 16) <= 0)
+ return 1;
+
+ } else {
+
+ if (!is_ipv6 &&
+ member->min.ipv4 <= ip &&
+ ip <= member->max.ipv4)
+ return 1;
+
+ }
+ }
+
+ return 0;
+}
+
+static int tmy_read_addr_group(struct io_buffer *head,
+ struct addr_group_entry *group,
+ struct addr_group_member *member)
+{
+ char buf[128];
+ if (!member)
+ return 0;
+
+ if (member->is_ipv6) {
+
+ const u16 *min_addr = member->min.ipv6;
+ const u16 *max_addr = member->max.ipv6;
+
+ tmy_print_ipv6(buf, sizeof(buf), min_addr);
+
+ if (memcmp(min_addr, max_addr, 16)) {
+ char *cp = strchr(buf, '\0');
+ int len = sizeof(buf) - strlen(buf);
+
+ *cp++ = '-';
+ tmy_print_ipv6(cp, len, max_addr);
+ }
+
+ } else {
+
+ const u32 min_addr = member->min.ipv4;
+ const u32 max_addr = member->max.ipv4;
+
+ memset(buf, 0, sizeof(buf));
+ snprintf(buf, sizeof(buf) - 1,
+ NIPQUAD_FMT, HIPQUAD(min_addr));
+
+ if (min_addr != max_addr) {
+ const int len = strlen(buf);
+
+ snprintf(buf + len, sizeof(buf) - 1 - len,
+ "-" NIPQUAD_FMT, HIPQUAD(max_addr));
+ }
+
+ }
+
+ return tmy_io_printf(head, TMY_ADDRESS_GROUP "%s %s\n",
+ group->group_name->name, buf);
+}
+
+/**
+ * tmy_read_addr_group_policy - read address group policy
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_addr_group_policy(struct io_buffer *head)
+{
+ struct addr_group_entry *group = head->read_var1;
+ struct addr_group_member *member = head->read_var2;
+
+ if (!group)
+ group = group_list;
+
+ while (group) {
+ head->read_var1 = group;
+ if (!member)
+ member = group->first_member;
+ while (member) {
+
+ head->read_var2 = member;
+ if (member->is_deleted) {
+ member = member->next;
+ continue;
+ }
+
+ if (tmy_read_addr_group(head, group, member))
+ break;
+
+ member = member->next;
+ }
+ if (member)
+ break;
+ head->read_var2 = NULL;
+ group = group->next;
+ }
+ return group ? -ENOMEM : 0;
+}
+
+/*********************** NETWORK NETWORK ACL HANDLER ***********************/
+
+/**
+ * tmy_print_ipv6 - print ipv6 address
+ * @buffer: pointer to buffer to save the result.
+ * @buffer_len: sizeof @buffer .
+ * @ip: pointer to an IPv6 address in network byte order.
+ *
+ * Returns @buffer .
+ */
+char *tmy_print_ipv6(char *buffer, const int buffer_len, const u16 *ip)
+{
+ memset(buffer, 0, buffer_len);
+ snprintf(buffer, buffer_len - 1, NIP6_FMT,
+ ntohs(ip[0]), ntohs(ip[1]), ntohs(ip[2]), ntohs(ip[3]),
+ ntohs(ip[4]), ntohs(ip[5]), ntohs(ip[6]), ntohs(ip[7]));
+ return buffer;
+}
+
+/**
+ * tmy_network2keyword - get keyword from access control index.
+ * @operation: index number.
+ *
+ * Returns keyword that corresponds with @operation .
+ */
+const char *tmy_network2keyword(const unsigned int operation)
+{
+ const char *keyword = "unknown";
+ switch (operation) {
+ case TMY_NETWORK_ACL_UDP_BIND:
+ keyword = "UDP bind";
+ break;
+ case TMY_NETWORK_ACL_UDP_CONNECT:
+ keyword = "UDP connect";
+ break;
+ case TMY_NETWORK_ACL_TCP_BIND:
+ keyword = "TCP bind";
+ break;
+ case TMY_NETWORK_ACL_TCP_LISTEN:
+ keyword = "TCP listen";
+ break;
+ case TMY_NETWORK_ACL_TCP_CONNECT:
+ keyword = "TCP connect";
+ break;
+ case TMY_NETWORK_ACL_TCP_ACCEPT:
+ keyword = "TCP accept";
+ break;
+ case TMY_NETWORK_ACL_RAW_BIND:
+ keyword = "RAW bind";
+ break;
+ case TMY_NETWORK_ACL_RAW_CONNECT:
+ keyword = "RAW connect";
+ break;
+ }
+ return keyword;
+}
+
+/* Compare IPv4/IPv6 address. */
+static int tmy_cmp_network_entry(const u8 record_type,
+ struct net_acl *acl,
+ const struct addr_group_entry *group,
+ const u32 min_ip,
+ const u32 max_ip,
+ const u32 *min_address,
+ const u32 *max_address)
+{
+ int found = 0;
+
+ switch (record_type) {
+
+ case TMY_TYPE_ADDRESS_GROUP:
+ if (acl->u.group == group)
+ found = 1;
+ break;
+
+ case TMY_TYPE_IPv4:
+ if (acl->u.ipv4.min == min_ip &&
+ max_ip == acl->u.ipv4.max)
+ found = 1;
+ break;
+
+ case TMY_TYPE_IPv6:
+ if (memcmp(acl->u.ipv6.min, min_address, 16) == 0 &&
+ memcmp(max_address, acl->u.ipv6.max, 16) == 0)
+ found = 1;
+ break;
+
+ }
+
+ return found;
+}
+
+/*
+ * @is_add: 1 add this entry if not quota exceeded
+ * -1 always add this entry
+ * 0 remove this entry
+ */
+static int tmy_add_network_entry(const u8 operation,
+ const u8 record_type,
+ const struct addr_group_entry *group,
+ const u32 *min_address,
+ const u32 *max_address,
+ const u16 min_port,
+ const u16 max_port,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_add)
+{
+ struct acl_info *ptr;
+ int error = -ENOMEM;
+ /* using host byte order to allow u32 comparison than memcmp().*/
+ const u32 min_ip = ntohl(*min_address);
+ const u32 max_ip = ntohl(*max_address);
+
+ if (!domain)
+ return -EINVAL;
+
+ down(&domain_acl_lock);
+
+ if (!is_add)
+ goto remove;
+
+ ptr = domain->first_acl_ptr;
+ if (!ptr)
+ goto first_entry;
+
+ while (1) {
+ struct net_acl *acl = (struct net_acl *) ptr;
+
+ if (ptr->type == TMY_TYPE_IP_NETWORK_ACL &&
+ ptr->cond == cond &&
+ acl->operation_type == operation &&
+ acl->record_type == record_type &&
+ acl->min_port == min_port &&
+ max_port == acl->max_port &&
+ tmy_cmp_network_entry(record_type, acl,
+ group, min_ip, max_ip,
+ min_address,
+ max_address)) {
+ ptr->is_deleted = 0;
+ error = 0;
+ break;
+ }
+
+ if (ptr->next) {
+ ptr = ptr->next;
+ continue;
+ }
+first_entry: ;
+ if (is_add == 1 && tmy_too_many_acl(domain))
+ break;
+
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ break;
+
+ acl->head.type = TMY_TYPE_IP_NETWORK_ACL;
+ acl->head.cond = cond;
+ acl->operation_type = operation;
+ acl->record_type = record_type;
+
+ if (record_type == TMY_TYPE_ADDRESS_GROUP)
+ acl->u.group = group;
+ else if (record_type == TMY_TYPE_IPv4) {
+ acl->u.ipv4.min = min_ip;
+ acl->u.ipv4.max = max_ip;
+ } else {
+ memmove(acl->u.ipv6.min, min_address, 16);
+ memmove(acl->u.ipv6.max, max_address, 16);
+ }
+
+ acl->min_port = min_port;
+ acl->max_port = max_port;
+ error = tmy_add_acl(ptr, domain,
+ (struct acl_info *) acl);
+ break;
+ }
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct net_acl *acl = (struct net_acl *) ptr;
+ if (ptr->type != TMY_TYPE_IP_NETWORK_ACL ||
+ ptr->cond != cond ||
+ ptr->is_deleted ||
+ acl->operation_type != operation ||
+ acl->record_type != record_type ||
+ acl->min_port != min_port ||
+ acl->max_port != max_port ||
+ !tmy_cmp_network_entry(record_type, acl,
+ group, min_ip, max_ip,
+ min_address,
+ max_address))
+ continue;
+
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ up(&domain_acl_lock);
+
+ return error;
+}
+
+/* Check network permission. */
+static int tmy_network_entry(const int is_ipv6,
+ const int operation,
+ const u32 *address,
+ const u16 port)
+{
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+ const char *keyword = tmy_network2keyword(operation);
+ const int is_enforce = tmy_enforce(TMY_MAC_FOR_NETWORK);
+ /* using host byte order to allow u32 comparison than memcmp().*/
+ const u32 ip = ntohl(*address);
+
+ if (!tmy_flags(TMY_MAC_FOR_NETWORK))
+ return 0;
+
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct net_acl *acl = (struct net_acl *) ptr;
+
+ if (ptr->type != TMY_TYPE_IP_NETWORK_ACL ||
+ ptr->is_deleted ||
+ acl->operation_type != operation ||
+ port < acl->min_port ||
+ acl->max_port < port ||
+ tmy_check_condition(ptr->cond, NULL))
+ continue;
+
+ if (acl->record_type == TMY_TYPE_ADDRESS_GROUP &&
+ tmy_address_match_group(is_ipv6, address, acl->u.group))
+ break;
+
+ else if (acl->record_type == TMY_TYPE_IPv4 && !is_ipv6 &&
+ (acl->u.ipv4.min <= ip && ip <= acl->u.ipv4.max))
+ break;
+
+ else if (acl->record_type == TMY_TYPE_IPv6 && is_ipv6 &&
+ memcmp(acl->u.ipv6.min, address, 16) <= 0 &&
+ memcmp(address, acl->u.ipv6.max, 16) <= 0)
+ break;
+
+ }
+
+ tmy_audit_network_log(is_ipv6, keyword, address,
+ port, ptr != 0, is_enforce);
+
+ if (ptr)
+ return 0;
+
+ if (is_enforce) {
+
+ if (is_ipv6) {
+
+ char buf[64];
+
+ tmy_print_ipv6(buf, sizeof(buf), (const u16 *) address);
+ return tmy_supervisor("%s\n" TMY_ALLOW_NETWORK
+ "%s %s %u\n",
+ domain->domainname->name, keyword,
+ buf, port);
+
+ }
+
+ return tmy_supervisor("%s\n" TMY_ALLOW_NETWORK
+ "%s " NIPQUAD_FMT " %u\n",
+ domain->domainname->name, keyword,
+ HIPQUAD(ip), port);
+
+ }
+
+ if (tmy_accept(TMY_MAC_FOR_NETWORK))
+ tmy_add_network_entry(operation,
+ is_ipv6 ? TMY_TYPE_IPv6 : TMY_TYPE_IPv4,
+ NULL, address, address,
+ port, port, domain, NULL, 1);
+
+ return 0;
+}
+
+/**
+ * tmy_add_signal_policy - add or delete signal policy.
+ * @data: a line to parse.
+ * @domain: pointer to "struct domain_info".
+ * @cond: pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_network_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const int is_delete)
+{
+ u8 sock_type;
+ u8 operation;
+ u8 record_type;
+ u16 min_address[8];
+ u16 max_address[8];
+ unsigned int min[8];
+ unsigned int max[8];
+ struct addr_group_entry *group = NULL;
+ u16 min_port;
+ u16 max_port;
+ int count;
+ char *cp1 = NULL;
+ char *cp2 = NULL;
+
+ cp1 = strchr(data, ' ');
+ if (!cp1)
+ goto out;
+ cp1++;
+
+ if (strncmp(data, "TCP ", 4) == 0)
+ sock_type = SOCK_STREAM;
+ else if (strncmp(data, "UDP ", 4) == 0)
+ sock_type = SOCK_DGRAM;
+ else if (strncmp(data, "RAW ", 4) == 0)
+ sock_type = SOCK_RAW;
+ else
+ goto out;
+
+ cp2 = strchr(cp1, ' ');
+ if (!cp2)
+ goto out;
+ cp2++;
+
+ if (strncmp(cp1, "bind ", 5) == 0) {
+ switch (sock_type) {
+ case SOCK_STREAM:
+ operation = TMY_NETWORK_ACL_TCP_BIND;
+ break;
+ case SOCK_DGRAM:
+ operation = TMY_NETWORK_ACL_UDP_BIND;
+ break;
+ default:
+ operation = TMY_NETWORK_ACL_RAW_BIND;
+ break;
+ }
+ } else if (strncmp(cp1, "connect ", 8) == 0) {
+ switch (sock_type) {
+ case SOCK_STREAM:
+ operation = TMY_NETWORK_ACL_TCP_CONNECT;
+ break;
+ case SOCK_DGRAM:
+ operation = TMY_NETWORK_ACL_UDP_CONNECT;
+ break;
+ default:
+ operation = TMY_NETWORK_ACL_RAW_CONNECT;
+ break;
+ }
+ } else if (sock_type == SOCK_STREAM &&
+ strncmp(cp1, "listen ", 7) == 0)
+ operation = TMY_NETWORK_ACL_TCP_LISTEN;
+
+ else if (sock_type == SOCK_STREAM &&
+ strncmp(cp1, "accept ", 7) == 0)
+ operation = TMY_NETWORK_ACL_TCP_ACCEPT;
+
+ else
+ goto out;
+
+ cp1 = strchr(cp2, ' ');
+ if (!cp1)
+ goto out;
+ *cp1++ = '\0';
+
+ count = sscanf(cp2,
+ NIP6_FMT "-" NIP6_FMT,
+ &min[0], &min[1], &min[2], &min[3],
+ &min[4], &min[5], &min[6], &min[7],
+ &max[0], &max[1], &max[2], &max[3],
+ &max[4], &max[5], &max[6], &max[7]);
+
+ if (count == 8 || count == 16) {
+
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ min_address[i] = htons((u16) min[i]);
+ max_address[i] = htons((u16) max[i]);
+ }
+
+ if (count == 8)
+ memmove(max_address, min_address, sizeof(min_address));
+ record_type = TMY_TYPE_IPv6;
+
+ goto ok;
+
+ }
+
+ count = sscanf(cp2,
+ NIPQUAD_FMT "-" NIPQUAD_FMT,
+ &min[0], &min[1], &min[2], &min[3],
+ &max[0], &max[1], &max[2], &max[3]);
+
+ if (count == 4 || count == 8) {
+
+ u32 ip = htonl((((u8) min[0]) << 24) +
+ (((u8) min[1]) << 16) +
+ (((u8) min[2]) << 8) +
+ (u8) min[3]);
+ * (u32 *) min_address = ip;
+
+ if (count == 8)
+ ip = htonl((((u8) max[0]) << 24) +
+ (((u8) max[1]) << 16) +
+ (((u8) max[2]) << 8) +
+ (u8) max[3]);
+ * (u32 *) max_address = ip;
+ record_type = TMY_TYPE_IPv4;
+
+ goto ok;
+
+ }
+
+ if (*cp2 == '@') {
+
+ group = tmy_new_addr_group(cp2 + 1);
+ if (!group)
+ return -ENOMEM;
+ record_type = TMY_TYPE_ADDRESS_GROUP;
+
+ goto ok;
+ }
+
+ goto out;
+
+ok: ;
+ if (strchr(cp1, ' '))
+ goto out;
+
+ count = sscanf(cp1, "%hu-%hu", &min_port, &max_port);
+ if (count != 1 && count != 2)
+ goto out;
+
+ if (count == 1)
+ max_port = min_port;
+
+ return tmy_add_network_entry(operation, record_type, group,
+ (u32 *) min_address,
+ (u32 *) max_address,
+ min_port, max_port, domain,
+ cond, is_delete ? 0 : -1);
+
+out: ;
+ return -EINVAL;
+}
+
+/**
+ * tmy_network_listen_acl - check permission for listen(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port: TCP or UDP's port number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_listen_acl(const int is_ipv6,
+ const u8 *address,
+ const u16 port)
+{
+ return tmy_network_entry(is_ipv6,
+ TMY_NETWORK_ACL_TCP_LISTEN,
+ (const u32 *) address,
+ ntohs(port));
+}
+
+/**
+ * tmy_network_connect_acl - check permission for connect(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @sock_type: socket type (TCP, UDP or IP).
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port: TCP or UDP's port number or IP's protocol number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_connect_acl(const int is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port)
+{
+ int type;
+
+ switch (sock_type) {
+ case SOCK_STREAM:
+ type = TMY_NETWORK_ACL_TCP_CONNECT;
+ break;
+ case SOCK_DGRAM:
+ type = TMY_NETWORK_ACL_UDP_CONNECT;
+ break;
+ default:
+ type = TMY_NETWORK_ACL_RAW_CONNECT;
+ break;
+ }
+
+ return tmy_network_entry(is_ipv6, type,
+ (const u32 *) address, ntohs(port));
+}
+
+/**
+ * tmy_network_bind_acl - check permission for bind(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @sock_type: socket type (TCP, UDP or IP).
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port: TCP or UDP's port number or IP's protocol number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_bind_acl(const int is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port)
+{
+ int type;
+
+ switch (sock_type) {
+ case SOCK_STREAM:
+ type = TMY_NETWORK_ACL_TCP_BIND;
+ break;
+ case SOCK_DGRAM:
+ type = TMY_NETWORK_ACL_UDP_BIND;
+ break;
+ default:
+ type = TMY_NETWORK_ACL_RAW_BIND;
+ break;
+ }
+
+ return tmy_network_entry(is_ipv6, type,
+ (const u32 *) address, ntohs(port));
+}
+
+/**
+ * tmy_network_sendmsg_acl - check permission for sendmsg(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @sock_type: socket type (UDP or IP).
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port: UDP's port number or IP's protocol number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_sendmsg_acl(const int is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port)
+{
+ int type;
+
+ if (sock_type == SOCK_DGRAM)
+ type = SOCK_DGRAM;
+ else
+ type = TMY_NETWORK_ACL_RAW_CONNECT;
+
+ return tmy_network_entry(is_ipv6, type,
+ (const u32 *) address, ntohs(port));
+}
+
+/**
+ * tmy_network_accept_acl - check permission for accept(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port: TCP client's port number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_accept_acl(const int is_ipv6, const u8 *address,
+ const u16 port)
+{
+ return tmy_network_entry(is_ipv6, TMY_NETWORK_ACL_TCP_ACCEPT,
+ (const u32 *) address, ntohs(port));
+}
+
+/**
+ * tmy_network_recvmsg_acl - check permission for recvmsg(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @sock_type: socket type (UDP or IP).
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port: UDP's port number or IP's protocol number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_recvmsg_acl(const int is_ipv6, const int sock_type,
+ const u8 *address, const u16 port)
+{
+ return tmy_network_entry(is_ipv6, sock_type == SOCK_DGRAM ?
+ TMY_NETWORK_ACL_UDP_CONNECT :
+ TMY_NETWORK_ACL_RAW_CONNECT,
+ (const u32 *) address, ntohs(port));
+}
Mount access control functions for TOMOYO Linux.
TOMOYO Linux checks permission according to
device name, mount point, filesystem type and optional flags.
TOMOYO Linux also checks permission in umount and pivot_root.
Each permission can be automatically accumulated into
the policy using 'learning mode'.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/mount.c | 1019 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1019 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/mount.c 2007-08-24 15:51:37.000000000 +0900
@@ -0,0 +1,1019 @@
+/*
+ * security/tomoyo/mount.c
+ *
+ * Mount access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+#include <linux/namei.h>
+#include <linux/mnt_namespace.h>
+
+/***** KEYWORDS for mount restrictions. *****/
+
+#define MOUNT_BIND_KEYWORD "--bind"
+#define MOUNT_MOVE_KEYWORD "--move"
+#define MOUNT_REMOUNT_KEYWORD "--remount"
+#define MOUNT_MAKE_UNBINDABLE_KEYWORD "--make-unbindable"
+#define MOUNT_MAKE_PRIVATE_KEYWORD "--make-private"
+#define MOUNT_MAKE_SLAVE_KEYWORD "--make-slave"
+#define MOUNT_MAKE_SHARED_KEYWORD "--make-shared"
+
+/***** The structure for mount restrictions. *****/
+
+struct mount_entry {
+ struct mount_entry *next; /* NULL if none. */
+ const struct path_info *dev_name;
+ const struct path_info *dir_name;
+ const struct path_info *fs_type;
+ unsigned int flags; /* Mount flags. */
+ int is_deleted;
+};
+
+struct no_umount_entry {
+ struct no_umount_entry *next; /* NULL if none. */
+ const struct path_info *dir;
+ int is_deleted;
+};
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_mount_log(const int is_granted,
+ const int is_enforce,
+ const char *fmt, ...)
+ __attribute__((format(printf, 3, 4)));
+
+static int tmy_audit_mount_log(const int is_granted,
+ const int is_enforce,
+ const char *fmt, ...)
+{
+ char *buf1;
+ char *buf2;
+ unsigned int len;
+ va_list args;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ buf1 = tmy_alloc(PAGE_SIZE);
+
+ if (!buf1)
+ return -ENOMEM;
+
+ va_start(args, fmt);
+ len = vsnprintf(buf1, PAGE_SIZE, fmt, args);
+ va_end(args);
+
+ if (len >= PAGE_SIZE) {
+ tmy_free(buf1);
+ return -ENOMEM;
+ }
+
+ buf2 = tmy_init_audit_log(&len);
+
+ if (!buf2) {
+ tmy_free(buf1);
+ return -ENOMEM;
+ }
+
+ snprintf(buf2 + strlen(buf2),
+ len - strlen(buf2),
+ "%s", buf1);
+
+ tmy_free(buf1);
+
+ return tmy_write_audit_log(buf2, is_granted, is_enforce);
+}
+
+/************************ MOUNT RESTRICTION HANDLER ************************/
+
+static void put_filesystem(struct file_system_type *fs)
+{
+ module_put(fs->owner);
+}
+
+static struct mount_entry *mount_list;
+
+/* Add or remove a mount entry. */
+static int tmy_add_mount_acl(const char *dev_name,
+ const char *dir_name,
+ const char *fs_type,
+ const unsigned int flags,
+ const int is_delete)
+{
+ struct mount_entry *new_entry;
+ struct mount_entry *ptr;
+ const struct path_info *fs;
+ const struct path_info *dev;
+ const struct path_info *dir;
+ static DECLARE_MUTEX(lock);
+ int error = -ENOMEM;
+
+ fs = tmy_save_name(fs_type);
+ if (!fs)
+ return -EINVAL;
+
+ if (!dev_name)
+ /* Map dev_name to "<NULL>" for if no dev_name given. */
+ dev_name = "<NULL>";
+ if (strcmp(fs->name, MOUNT_REMOUNT_KEYWORD) == 0)
+ /* Fix dev_name to "any" for remount permission. */
+ dev_name = "any";
+ if (strcmp(fs->name, MOUNT_MAKE_UNBINDABLE_KEYWORD) == 0 ||
+ strcmp(fs->name, MOUNT_MAKE_PRIVATE_KEYWORD) == 0 ||
+ strcmp(fs->name, MOUNT_MAKE_SLAVE_KEYWORD) == 0 ||
+ strcmp(fs->name, MOUNT_MAKE_SHARED_KEYWORD) == 0)
+ dev_name = "any";
+
+ if (!tmy_correct_path(dev_name, 0, 0, 0, __FUNCTION__) ||
+ !tmy_correct_path(dir_name, 1, 0, 1, __FUNCTION__))
+ return -EINVAL;
+
+ dev = tmy_save_name(dev_name);
+ if (!dev)
+ return -ENOMEM;
+ dir = tmy_save_name(dir_name);
+ if (!dir)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = mount_list; ptr; ptr = ptr->next) {
+ if (ptr->flags != flags ||
+ tmy_pathcmp(ptr->dev_name, dev) ||
+ tmy_pathcmp(ptr->dir_name, dir) ||
+ tmy_pathcmp(ptr->fs_type, fs))
+ continue;
+
+ if (is_delete) {
+ ptr->is_deleted = 1;
+ error = 0;
+ goto out;
+ } else {
+ if (ptr->is_deleted) {
+ ptr->flags = flags;
+ ptr->is_deleted = 0;
+ } else {
+ if (ptr->flags == flags) {
+ error = 0;
+ goto out; /* No changes. */
+ }
+ ptr->flags = flags;
+ }
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->dev_name = dev;
+ new_entry->dir_name = dir;
+ new_entry->fs_type = fs;
+ new_entry->flags = flags;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = mount_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ mount_list = new_entry;
+
+ error = 0;
+ ptr = new_entry;
+
+out: ;
+ up(&lock);
+
+ return error;
+}
+
+/* Print error message for mount request. */
+static inline int tmy_mount_perm_error(char *dev_name,
+ char *dir_name,
+ char *type,
+ unsigned long flags,
+ const int is_enforce)
+{
+ int error = -EPERM;
+ const char *realname1 = tmy_realpath(dev_name);
+ const char *realname2 = tmy_realpath(dir_name);
+ const char *exename = tmy_get_exe();
+
+ if (!strcmp(type, MOUNT_REMOUNT_KEYWORD)) {
+
+ tmy_audit_mount_log(0, is_enforce,
+ "mount -o remount %s 0x%lX",
+ realname2 ? realname2 : dir_name,
+ flags);
+ if (is_enforce &&
+ !tmy_supervisor("# %s is requesting\nmount -o remount %s\n",
+ exename, realname2 ? realname2 : dir_name))
+ error = 0;
+
+ } else if (!strcmp(type, MOUNT_BIND_KEYWORD) ||
+ !strcmp(type, MOUNT_MOVE_KEYWORD)) {
+
+ tmy_audit_mount_log(0, is_enforce, "mount %s %s %s 0x%lX",
+ type, realname1 ? realname1 : dev_name,
+ realname2 ? realname2 : dir_name, flags);
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\nmount %s %s %s 0x%lX\n",
+ exename, type,
+ realname1 ? realname1 : dev_name,
+ realname2 ? realname2 : dir_name,
+ flags) == 0)
+ error = 0;
+
+ } else if (!strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
+ !strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) ||
+ !strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) ||
+ !strcmp(type, MOUNT_MAKE_SHARED_KEYWORD)) {
+
+ tmy_audit_mount_log(0, is_enforce, "mount %s %s 0x%lX", type,
+ realname2 ? realname2 : dir_name, flags);
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\nmount %s %s 0x%lX",
+ exename, type,
+ realname2 ? realname2 : dir_name,
+ flags) == 0)
+ error = 0;
+
+ } else {
+
+ tmy_audit_mount_log(0, is_enforce, "mount -t %s %s %s 0x%lX",
+ type, realname1 ? realname1 : dev_name,
+ realname2 ? realname2 : dir_name, flags);
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\n"
+ "mount -t %s %s %s 0x%lX\n",
+ exename, type,
+ realname1 ? realname1 : dev_name,
+ realname2 ? realname2 : dir_name,
+ flags) == 0)
+ error = 0;
+
+ }
+
+ tmy_free(exename);
+ tmy_free(realname2);
+ tmy_free(realname1);
+ return error;
+}
+
+/**
+ * tmy_mount_perm - check for mount permission.
+ * @dev_name: pointer to device name. May be NULL.
+ * @dir_name: pointer to mount point.
+ * @type: pointer to filesystem. May be NULL.
+ * @flags: mount flags.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_mount_perm(char *dev_name,
+ char *dir_name,
+ char *type,
+ unsigned long flags)
+{
+ const int is_enforce = tmy_enforce(TMY_RESTRICT_MOUNT);
+ int error = -EPERM;
+
+ if (!tmy_flags(TMY_RESTRICT_MOUNT))
+ return 0;
+ if (!type)
+ type = "<NULL>";
+ if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
+ flags &= ~MS_MGC_MSK;
+
+ switch (flags & (MS_REMOUNT | MS_MOVE | MS_BIND)) {
+ case MS_REMOUNT:
+ case MS_MOVE:
+ case MS_BIND:
+ case 0:
+ break;
+ default:
+ tmy_audit_mount_log(0, 1, "%s%s%sare given "
+ "for single mount operation",
+ flags & MS_REMOUNT ? "remount " : "",
+ flags & MS_MOVE ? "move " : "",
+ flags & MS_BIND ? "bind " : "");
+ return -EINVAL;
+ }
+
+ switch (flags & (MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE | MS_SHARED)) {
+ case MS_UNBINDABLE:
+ case MS_PRIVATE:
+ case MS_SLAVE:
+ case MS_SHARED:
+ case 0:
+ break;
+ default:
+ tmy_audit_mount_log(0, 1, "%s%s%s%sare given "
+ "for single mount operation",
+ flags & MS_UNBINDABLE ? "unbindable " : "",
+ flags & MS_PRIVATE ? "private " : "",
+ flags & MS_SLAVE ? "slave " : "",
+ flags & MS_SHARED ? "shared " : "");
+ return -EINVAL;
+ }
+
+ if (flags & MS_REMOUNT)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_REMOUNT_KEYWORD,
+ flags & ~MS_REMOUNT);
+ else if (flags & MS_MOVE)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_MOVE_KEYWORD,
+ flags & ~MS_MOVE);
+ else if (flags & MS_BIND)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_BIND_KEYWORD,
+ flags & ~MS_BIND);
+ else if (flags & MS_UNBINDABLE)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_MAKE_UNBINDABLE_KEYWORD,
+ flags & ~MS_UNBINDABLE);
+ else if (flags & MS_PRIVATE)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_MAKE_PRIVATE_KEYWORD,
+ flags & ~MS_PRIVATE);
+ else if (flags & MS_SLAVE)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_MAKE_SLAVE_KEYWORD,
+ flags & ~MS_SLAVE);
+ else if (flags & MS_SHARED)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_MAKE_SHARED_KEYWORD,
+ flags & ~MS_SHARED);
+ else {
+ struct mount_entry *ptr;
+ struct file_system_type *fstype = NULL;
+ const char *requested_dir_name = NULL;
+ const char *requested_dev_name = NULL;
+ struct path_info rdev;
+ struct path_info rdir;
+ int need_dev = 0;
+
+ requested_dir_name = tmy_realpath(dir_name);
+ if (!requested_dir_name) {
+ error = -ENOENT;
+ goto cleanup;
+ }
+ rdir.name = requested_dir_name;
+ tmy_fill_path_info(&rdir);
+
+ /* Compare fs name. */
+ fstype = get_fs_type(type);
+ if (strcmp(type, MOUNT_REMOUNT_KEYWORD) == 0)
+ /* Needn't to resolve dev_name */;
+ else if (strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) == 0 ||
+ strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) == 0 ||
+ strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) == 0 ||
+ strcmp(type, MOUNT_MAKE_SHARED_KEYWORD) == 0)
+ /* Needn't to resolve dev_name */;
+ else if (strcmp(type, MOUNT_BIND_KEYWORD) == 0 ||
+ strcmp(type, MOUNT_MOVE_KEYWORD) == 0) {
+ requested_dev_name = tmy_realpath(dev_name);
+ if (!requested_dev_name) {
+ error = -ENOENT;
+ goto cleanup;
+ }
+ rdev.name = requested_dev_name;
+ tmy_fill_path_info(&rdev);
+ need_dev = -1;
+ } else if (fstype) {
+ if (fstype->fs_flags & FS_REQUIRES_DEV) {
+ requested_dev_name = tmy_realpath(dev_name);
+ if (!requested_dev_name) {
+ error = -ENOENT;
+ goto cleanup;
+ }
+ rdev.name = requested_dev_name;
+ tmy_fill_path_info(&rdev);
+ need_dev = 1;
+ }
+ } else {
+ error = -ENODEV;
+ goto cleanup;
+ }
+
+ for (ptr = mount_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+
+ /* Compare options */
+ if (ptr->flags != flags)
+ continue;
+
+ /* Compare fs name. */
+ if (strcmp(type, ptr->fs_type->name))
+ continue;
+
+ /* Compare mount point. */
+ if (tmy_path_match(&rdir, ptr->dir_name) == 0)
+ continue;
+
+ /* Compare device name. */
+ if (requested_dev_name &&
+ tmy_path_match(&rdev, ptr->dev_name) == 0)
+ continue;
+
+ /* OK. */
+ error = 0;
+
+ if (need_dev > 0)
+ tmy_audit_mount_log(1, is_enforce,
+ "mount -t %s %s %s 0x%lX",
+ type,
+ requested_dev_name,
+ requested_dir_name, flags);
+ else if (need_dev < 0)
+ tmy_audit_mount_log(1, is_enforce,
+ "mount %s %s %s 0x%lX",
+ type, requested_dev_name,
+ requested_dir_name, flags);
+ else if (!strcmp(type, MOUNT_REMOUNT_KEYWORD))
+ tmy_audit_mount_log(1, is_enforce,
+ "mount -o remount %s 0x%lX",
+ requested_dir_name, flags);
+ else if (!strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
+ !strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) ||
+ !strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) ||
+ !strcmp(type, MOUNT_MAKE_SHARED_KEYWORD))
+ tmy_audit_mount_log(1, is_enforce,
+ "mount %s %s 0x%lX",
+ type, requested_dir_name,
+ flags);
+ else
+ tmy_audit_mount_log(1, is_enforce,
+ "mount %s on %s 0x%lX",
+ type, requested_dir_name,
+ flags);
+
+ break;
+ }
+
+ if (error)
+ error = tmy_mount_perm_error(dev_name, dir_name,
+ type, flags, is_enforce);
+
+ if (error && tmy_accept(TMY_RESTRICT_MOUNT)) {
+ tmy_add_mount_acl(need_dev ?
+ requested_dev_name : dev_name,
+ requested_dir_name, type, flags, 0);
+ tmy_update_counter(TMY_UPDATE_SYSTEMPOLICY);
+ }
+
+cleanup:
+ tmy_free(requested_dev_name);
+ tmy_free(requested_dir_name);
+ if (fstype)
+ put_filesystem(fstype);
+
+ }
+
+ if (!is_enforce)
+ error = 0;
+ return error;
+
+}
+
+/**
+ * tmy_add_mount_policy - add or delete mount policy.
+ * @data: a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_mount_policy(char *data, const int is_delete)
+{
+ char *cp;
+ char *cp2;
+ const char *fs;
+ const char *dev;
+ const char *dir;
+ unsigned int flags = 0;
+
+ cp2 = data;
+ cp = strchr(cp2, ' ');
+ if (!cp)
+ return -EINVAL;
+ *cp = '\0';
+ dev = cp2;
+
+ cp2 = cp + 1;
+ cp = strchr(cp2, ' ');
+ if (!cp)
+ return -EINVAL;
+ *cp = '\0';
+ dir = cp2;
+
+ cp2 = cp + 1;
+ cp = strchr(cp2, ' ');
+ if (!cp)
+ return -EINVAL;
+ *cp = '\0';
+ fs = cp2;
+
+ flags = simple_strtoul(cp + 1, NULL, 0);
+ return tmy_add_mount_acl(dev, dir, fs, flags, is_delete);
+}
+
+/**
+ * tmy_read_mount_policy - read mount policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_mount_policy(struct io_buffer *head)
+{
+ struct mount_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = mount_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (ptr->is_deleted == 0 &&
+ tmy_io_printf(head, TMY_ALLOW_MOUNT "%s %s %s 0x%x\n",
+ ptr->dev_name->name, ptr->dir_name->name,
+ ptr->fs_type->name, ptr->flags))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+static int tmy_find_conceal(struct nameidata *nd,
+ struct vfsmount *vfsmnt,
+ struct dentry *dentry)
+{
+ int flag = 0;
+
+ if (IS_ROOT(dentry) || !d_unhashed(dentry)) {
+ while (1) {
+
+ if (nd->mnt->mnt_root == vfsmnt->mnt_root &&
+ nd->dentry == dentry) {
+ flag = 1;
+ break;
+ }
+
+ if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+
+ spin_lock(&vfsmount_lock);
+
+ if (vfsmnt->mnt_parent == vfsmnt) {
+ spin_unlock(&vfsmount_lock);
+ break;
+ }
+ dentry = vfsmnt->mnt_mountpoint;
+ vfsmnt = vfsmnt->mnt_parent;
+
+ spin_unlock(&vfsmount_lock);
+
+ continue;
+ }
+ dentry = dentry->d_parent;
+
+ }
+ }
+
+ return flag;
+}
+
+/**
+ * tmy_conceal_mount - check for conceal mount permission.
+ * @nd: pointer to "struct nameidata".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ *
+ * People seldom mount on directries that have submounts.
+ * For example, you don't mount on /usr/ directory if /usr/local/ directory
+ * is already mounted, do you?
+ * This function forbids such mount requests.
+ */
+int tmy_conceal_mount(struct nameidata *nd)
+{
+ int flag = 0;
+ struct mnt_namespace *namespace = current->nsproxy->mnt_ns;
+ int is_enforce;
+ char *dir;
+
+ if (!tmy_flags(TMY_DENY_CONCEAL_MOUNT))
+ return 0;
+
+ if (namespace) {
+ struct list_head *p;
+
+ list_for_each(p, &namespace->list) {
+ struct vfsmount *vfsmnt =
+ list_entry(p, struct vfsmount, mnt_list);
+ struct dentry *dentry = vfsmnt->mnt_root;
+
+ spin_lock(&dcache_lock);
+
+ flag = tmy_find_conceal(nd, vfsmnt, dentry);
+
+ spin_unlock(&dcache_lock);
+
+ if (flag)
+ break;
+ }
+ }
+
+ is_enforce = tmy_enforce(TMY_DENY_CONCEAL_MOUNT);
+ dir = tmy_realpath_dentry(nd->dentry, nd->mnt);
+
+ if (flag) {
+ int error = -EPERM;
+ if (dir) {
+ const char *exename = tmy_get_exe();
+
+ tmy_audit_mount_log(0, is_enforce, "mount %s", dir);
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\nmount on %s\n",
+ exename, dir) == 0)
+ error = 0;
+
+ tmy_free(exename);
+ }
+ tmy_free(dir);
+
+ if (is_enforce)
+ return error;
+ } else {
+ if (dir)
+ tmy_audit_mount_log(1, is_enforce, "mount %s", dir);
+ tmy_free(dir);
+ }
+
+ return 0;
+}
+
+/************************ UMOUNT RESTRICTION HANDLER ************************/
+
+static struct no_umount_entry *no_umount_list;
+
+/* Add or remove a no-unmount entry. */
+static int tmy_add_no_umount_acl(const char *dir, const int is_delete)
+{
+ struct no_umount_entry *new_entry;
+ struct no_umount_entry *ptr;
+ const struct path_info *saved_dir;
+ static DECLARE_MUTEX(lock);
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(dir, 1, 0, 1, __FUNCTION__))
+ return -EINVAL;
+ saved_dir = tmy_save_name(dir);
+ if (!saved_dir)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = no_umount_list; ptr; ptr = ptr->next) {
+ if (ptr->dir == saved_dir) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->dir = saved_dir;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = no_umount_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ no_umount_list = new_entry;
+
+ error = 0;
+out: ;
+
+ up(&lock);
+
+ return error;
+}
+
+/**
+ * tmy_umount_perm - check for no-unmount permission.
+ * @mnt: pointer to "struct vfsmount".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_umount_perm(struct vfsmount *mnt)
+{
+ int error = -EPERM;
+ const char *dir0;
+ const int is_enforce = tmy_enforce(TMY_RESTRICT_UMOUNT);
+
+ if (!tmy_flags(TMY_RESTRICT_UMOUNT))
+ return 0;
+
+ dir0 = tmy_realpath_dentry(mnt->mnt_root, mnt);
+
+ if (dir0) {
+ struct no_umount_entry *ptr;
+ struct path_info dir;
+
+ dir.name = dir0;
+ tmy_fill_path_info(&dir);
+
+ for (ptr = no_umount_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+ if (tmy_path_match(&dir, ptr->dir))
+ break;
+ }
+
+ if (ptr) {
+ const char *exename = tmy_get_exe();
+
+ tmy_audit_mount_log(0, is_enforce, "umount %s", dir0);
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\nunmount %s\n",
+ exename, dir0) == 0)
+ error = 0;
+
+ tmy_free(exename);
+ } else {
+ tmy_audit_mount_log(1, is_enforce, "umount %s", dir0);
+ error = 0;
+ }
+
+ tmy_free(dir0);
+ }
+
+ if (!is_enforce)
+ error = 0;
+ return error;
+}
+
+/**
+ * tmy_add_no_umount_policy - add or delete no-unmount policy.
+ * @data: a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_no_umount_policy(char *data, const int is_delete)
+{
+ return tmy_add_no_umount_acl(data, is_delete);
+}
+
+/**
+ * tmy_read_no_umount_policy - read no-unmount policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_no_umount_policy(struct io_buffer *head)
+{
+ struct no_umount_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = no_umount_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (ptr->is_deleted == 0 &&
+ tmy_io_printf(head, TMY_DENY_UNMOUNT "%s\n",
+ ptr->dir->name))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/***** The structure for pivot_root restrictions. *****/
+
+struct pivot_root_entry {
+ struct pivot_root_entry *next;
+ const struct path_info *old_root;
+ const struct path_info *new_root;
+ int is_deleted;
+};
+
+/********************** PIVOT_ROOT RESTRICTION HANDLER **********************/
+
+static struct pivot_root_entry *pivot_root_list;
+
+/* Add or remove a pivot_root entry. */
+static int tmy_add_pivot_root_acl(const char *old_root,
+ const char *new_root,
+ const int is_delete)
+{
+ struct pivot_root_entry *new_entry;
+ struct pivot_root_entry *ptr;
+ const struct path_info *saved_old_root;
+ const struct path_info *saved_new_root;
+ static DECLARE_MUTEX(lock);
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(old_root, 1, 0, 1, __FUNCTION__) ||
+ !tmy_correct_path(new_root, 1, 0, 1, __FUNCTION__))
+ return -EINVAL;
+
+ saved_old_root = tmy_save_name(old_root);
+ if (!saved_old_root)
+ return -ENOMEM;
+ saved_new_root = tmy_save_name(new_root);
+ if (!saved_new_root)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = pivot_root_list; ptr; ptr = ptr->next) {
+ if (ptr->old_root == saved_old_root &&
+ ptr->new_root == saved_new_root) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->old_root = saved_old_root;
+ new_entry->new_root = saved_new_root;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = pivot_root_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ pivot_root_list = new_entry;
+
+ error = 0;
+out: ;
+
+ up(&lock);
+
+ return error;
+}
+
+/**
+ * tmy_pivot_root_perm - check for pivot_root permission.
+ * @old_nd: pointer to "struct nameidata".
+ * @new_nd: pointer to "struct nameidata".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_pivot_root_perm(struct nameidata *old_nd, struct nameidata *new_nd)
+{
+ int error = -EPERM;
+ char *old_root;
+ char *new_root;
+ int is_enforce;
+ struct path_info old_root_dir;
+ struct path_info new_root_dir;
+
+ if (!tmy_flags(TMY_RESTRICT_PIVOT_ROOT))
+ return 0;
+
+ old_root = tmy_realpath_dentry(old_nd->dentry, old_nd->mnt);
+ new_root = tmy_realpath_dentry(new_nd->dentry, new_nd->mnt);
+
+ if (!old_root || !new_root)
+ goto out;
+
+ old_root_dir.name = old_root;
+ tmy_fill_path_info(&old_root_dir);
+ new_root_dir.name = new_root;
+ tmy_fill_path_info(&new_root_dir);
+
+ if (old_root_dir.is_dir && new_root_dir.is_dir) {
+ struct pivot_root_entry *ptr;
+
+ for (ptr = pivot_root_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+ if (tmy_path_match(&old_root_dir, ptr->old_root) &&
+ tmy_path_match(&new_root_dir, ptr->new_root)) {
+ error = 0;
+ break;
+ }
+ }
+ }
+
+out: ;
+
+ is_enforce = tmy_enforce(TMY_RESTRICT_PIVOT_ROOT);
+ tmy_audit_mount_log(!error, is_enforce, "pivot_root %s %s",
+ new_root, old_root);
+
+ if (error) {
+ const char *exename = tmy_get_exe();
+
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\npivot_root %s %s\n",
+ exename, new_root, old_root) == 0)
+ error = 0;
+
+ if (exename)
+ tmy_free(exename);
+
+ if (!is_enforce && tmy_accept(TMY_RESTRICT_PIVOT_ROOT)
+ && old_root && new_root) {
+ tmy_add_pivot_root_acl(old_root, new_root, 0);
+ tmy_update_counter(TMY_UPDATE_SYSTEMPOLICY);
+ }
+
+ if (!is_enforce)
+ error = 0;
+ }
+
+ tmy_free(old_root);
+ tmy_free(new_root);
+ return error;
+}
+
+/**
+ * tmy_add_pivot_root_policy - add or delete pivot_root policy.
+ * @data: a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_pivot_root_policy(char *data, const int is_delete)
+{
+ char *cp = strchr(data, ' ');
+
+ if (!cp)
+ return -EINVAL;
+ *cp++ = '\0';
+
+ return tmy_add_pivot_root_acl(cp, data, is_delete);
+}
+
+/**
+ * tmy_read_pivot_root_policy - read pivot_root policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_pivot_root_policy(struct io_buffer *head)
+{
+ struct pivot_root_entry *ptr = head->read_var2;
+ if (!ptr)
+ ptr = pivot_root_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (ptr->is_deleted == 0 &&
+ tmy_io_printf(head, TMY_ALLOW_PIVOT_ROOT "%s %s\n",
+ ptr->new_root->name, ptr->old_root->name))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
Signal control functions for TOMOYO Linux.
TOMOYO Linux checks sending signal by signal number and
the domain of target process.In order to check signal
permission, LSM expantion patch [TOMOYO /] is needed.
Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/signal.c | 238 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 238 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/signal.c 2007-08-24 15:51:38.000000000 +0900
@@ -0,0 +1,238 @@
+/*
+ * security/tomoyo/signal.c
+ *
+ * Signal access contol functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_signal_log(const int signal,
+ const struct path_info *dest_domain,
+ const int is_granted,
+ const int is_enforce)
+{
+ char *buf;
+ int len;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ len = dest_domain->total_len;
+ buf = tmy_init_audit_log(&len);
+
+ if (!buf)
+ return -ENOMEM;
+
+ snprintf(buf + strlen(buf),
+ len - strlen(buf) - 1,
+ "%s%d %s",
+ TMY_ALLOW_SIGNAL, signal, dest_domain->name);
+
+ return tmy_write_audit_log(buf, is_granted, is_enforce);
+}
+
+/************************* SIGNAL ACL HANDLER *************************/
+
+/*
+ * @is_add: 1 add this entry if not quota exceeded
+ * -1 always add this entry
+ * 0 remove this entry
+ */
+static int tmy_add_signal_entry(const u16 sig, const char *dest_pattern,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_add)
+{
+ struct acl_info *ptr;
+ const struct path_info *saved_dest_pattern;
+ int error = -ENOMEM;
+
+ if (!domain)
+ return -EINVAL;
+ if (!dest_pattern ||
+ !tmy_is_correct_domain(dest_pattern, __FUNCTION__))
+ return -EINVAL;
+
+ saved_dest_pattern = tmy_save_name(dest_pattern);
+ if (!saved_dest_pattern)
+ return -ENOMEM;
+
+ down(&domain_acl_lock);
+
+ if (!is_add)
+ goto remove;
+
+ ptr = domain->first_acl_ptr;
+ if (!ptr)
+ goto first_entry;
+
+ while (1) {
+ struct signal_acl *acl = (struct signal_acl *) ptr;
+
+ if (ptr->type == TMY_TYPE_SIGNAL_ACL && acl->sig == sig
+ && ptr->cond == cond
+ && !tmy_pathcmp(acl->domainname, saved_dest_pattern)) {
+ ptr->is_deleted = 0;
+ /* Found. Nothing to do. */
+ error = 0;
+ break;
+ }
+
+ if (ptr->next) {
+ ptr = ptr->next;
+ continue;
+ }
+
+first_entry: ;
+ if (is_add == 1 && tmy_too_many_acl(domain))
+ break;
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ break;
+
+ acl->head.type = TMY_TYPE_SIGNAL_ACL;
+ acl->head.cond = cond;
+ acl->sig = sig;
+ acl->domainname = saved_dest_pattern;
+ error = tmy_add_acl(ptr, domain, (struct acl_info *) acl);
+ break;
+ }
+
+ goto ok;
+
+remove: ;
+ error = -ENOENT;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct signal_acl *acl = (struct signal_acl *) ptr;
+ if (ptr->type != TMY_TYPE_SIGNAL_ACL || ptr->cond != cond ||
+ ptr->is_deleted || acl->sig != sig ||
+ tmy_pathcmp(acl->domainname, saved_dest_pattern))
+ continue;
+ error = tmy_del_acl(ptr);
+ break;
+ }
+
+ok: ;
+ up(&domain_acl_lock);
+
+ return error;
+}
+
+/**
+ * tmy_signal_acl - check permission for kill(2)/tkill(2)/tgkill(2).
+ * @sig: signal number.
+ * @pid: pid of destination process.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_signal_acl(const int sig, const int pid)
+{
+ struct domain_info *domain = TMY_SECURITY->domain;
+ struct domain_info *dest = NULL;
+ const char *dest_pattern;
+ struct acl_info *ptr;
+ const u16 hash = sig;
+ const int is_enforce = tmy_enforce(TMY_MAC_FOR_SIGNAL);
+
+ if (!tmy_flags(TMY_MAC_FOR_SIGNAL))
+ return 0;
+ if (!sig)
+ return 0; /* No check for NULL signal. */
+ if (current->pid == pid) {
+ tmy_audit_signal_log(sig, domain->domainname, 1, is_enforce);
+ return 0; /* No check for self. */
+ }
+
+ { /* Simplified checking. */
+ struct task_struct *p = NULL;
+ read_lock(&tasklist_lock);
+ if (pid > 0)
+ p = find_task_by_pid((pid_t) pid);
+ else if (pid == 0)
+ p = current;
+ else if (pid == -1)
+ dest = &KERNEL_DOMAIN;
+ else
+ p = find_task_by_pid((pid_t) -pid);
+ if (p)
+ /* "struct task_struct"->security is not NULL. */
+ dest = ((struct tmy_security *) p->security)->domain;
+ read_unlock(&tasklist_lock);
+ if (!dest)
+ return 0; /* I can't find destinatioin. */
+ }
+
+ if (domain == dest) {
+ tmy_audit_signal_log(sig, dest->domainname, 1, is_enforce);
+ return 0;
+ }
+
+ dest_pattern = dest->domainname->name;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct signal_acl *acl = (struct signal_acl *) ptr;
+
+ if (ptr->type == TMY_TYPE_SIGNAL_ACL && ptr->is_deleted == 0
+ && acl->sig == hash &&
+ tmy_check_condition(ptr->cond, NULL) == 0) {
+ const int len = acl->domainname->total_len;
+
+ if (strncmp(acl->domainname->name,
+ dest_pattern, len) == 0
+ && (dest_pattern[len] == ' ' ||
+ dest_pattern[len] == '\0'))
+ break;
+
+ }
+ }
+
+ if (ptr) {
+ tmy_audit_signal_log(sig, dest->domainname, 1, is_enforce);
+ return 0;
+ }
+
+ tmy_audit_signal_log(sig, dest->domainname, 0, is_enforce);
+ if (is_enforce)
+ return tmy_supervisor("%s\n" TMY_ALLOW_SIGNAL "%d %s\n",
+ domain->domainname->name,
+ sig, dest_pattern);
+ if (tmy_accept(TMY_MAC_FOR_SIGNAL))
+ tmy_add_signal_entry(sig, dest_pattern, domain, NULL, 1);
+
+ return 0;
+}
+
+/**
+ * tmy_add_signal_policy - add or delete signal policy.
+ * @data: a line to parse.
+ * @domain: pointer to "struct domain_info".
+ * @cond: pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_signal_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const int is_delete)
+{
+ int sig;
+ char *domainname = strchr(data, ' ');
+
+ if (sscanf(data, "%d", &sig) == 1 && domainname &&
+ tmy_is_domain_def(domainname + 1))
+ return tmy_add_signal_entry(sig, domainname + 1, domain,
+ cond, is_delete ? 0 : -1);
+
+ return -EINVAL;
+}
LSM wrapper functions for TOMOYO Linux access control.
If bind mounts are used, TOMOYO requires all permissions for
all possible pathnames (whereas AppArmor requires one of possible pathnames).
If "struct vfsmount" is passed to LSM hooks as AppArmor proposes,
this file will become more simpler and "namespace_sem" can remain "static".
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/tomoyo.c | 745 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 745 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/tomoyo.c 2007-08-24 15:51:38.000000000 +0900
@@ -0,0 +1,745 @@
+/*
+ * security/tomoyo/tomoyo.c
+ *
+ * LSM hooks for TOMOYO Linux.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/security.h>
+#include <linux/highmem.h>
+#include <linux/namei.h>
+#include <linux/mnt_namespace.h>
+#include <linux/sysctl.h>
+#include <linux/proc_fs.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+
+#include "tomoyo.h"
+#include "realpath.h"
+#define MAX_SOCK_ADDR 128 /* net/socket.c */
+
+/* The initial domain. */
+struct domain_info KERNEL_DOMAIN = { NULL, NULL, NULL, 0, 0, 0 };
+
+static struct kmem_cache *tmy_cachep;
+
+static int tmy_task_alloc_security(struct task_struct *p)
+{
+ struct tmy_security *ptr = kmem_cache_alloc(tmy_cachep, GFP_KERNEL);
+
+ if (!ptr)
+ return -ENOMEM;
+ memcpy(ptr, TMY_SECURITY, sizeof(*ptr));
+ p->security = ptr;
+ return 0;
+}
+
+static void tmy_task_free_security(struct task_struct *p)
+{
+ kmem_cache_free(tmy_cachep, p->security);
+}
+
+static int tmy_bprm_alloc_security(struct linux_binprm *bprm)
+{
+ TMY_SECURITY->prev_domain = TMY_SECURITY->domain;
+ return 0;
+}
+
+static int tmy_bprm_check_security(struct linux_binprm *bprm)
+{
+ struct domain_info *next_domain = NULL;
+ int retval = 0;
+
+ tmy_load_policy(bprm->filename);
+
+ /*
+ * TMY_CHECK_READ_FOR_OPEN_EXEC bit indicates whether this function is
+ * called by do_execve() or not.
+ * If called by do_execve(), I do domain transition.
+ */
+ if (!(TMY_SECURITY->flags
+ & TMY_CHECK_READ_FOR_OPEN_EXEC)) {
+ retval = tmy_find_next_domain(bprm, &next_domain);
+ if (retval == 0) {
+ TMY_SECURITY->domain = next_domain;
+ TMY_SECURITY->flags |=
+ TMY_CHECK_READ_FOR_OPEN_EXEC;
+ }
+ }
+
+ return retval;
+}
+
+static void tmy_bprm_post_apply_creds(struct linux_binprm *bprm)
+{
+ TMY_SECURITY->prev_domain = TMY_SECURITY->domain;
+}
+
+static void tmy_bprm_free_security(struct linux_binprm *bprm)
+{
+ TMY_SECURITY->domain = TMY_SECURITY->prev_domain;
+ TMY_SECURITY->flags &= ~TMY_CHECK_READ_FOR_OPEN_EXEC;
+}
+
+static int tmy_sysctl(struct ctl_table *table, int op)
+{
+ int error;
+ char *name;
+
+ if ((op & 6) == 0)
+ return 0;
+
+ name = sysctlpath_from_table(table);
+ if (!name)
+ return -ENOMEM;
+
+ error = tmy_file_perm(name, op & 6, "sysctl");
+ tmy_free(name);
+
+ return error;
+}
+
+static int tmy_inode_permission(struct inode *inode,
+ int mask,
+ struct nameidata *nd)
+{
+ int flag = 0;
+
+ if (S_ISDIR(inode->i_mode)) /* ignore because inode is directory */
+ return 0;
+ if (!nd || !nd->dentry || !nd->mnt)
+ return 0;
+ /*
+ * If called by other than do_execve(), I check for read permission of
+ * interpreter.
+ * Unlike DAC, I don't check for read permission of pathname passed to
+ * do_execve().
+ * TOMOYO Linux checks for program's execute permission and
+ * interpreter's read permission.
+ */
+ if ((mask == MAY_EXEC) &&
+ (TMY_SECURITY->flags & TMY_CHECK_READ_FOR_OPEN_EXEC))
+ mask = MAY_READ;
+ if ((mask == MAY_EXEC) || (mask == 0))
+ return 0;
+
+ if (mask == (MAY_READ | MAY_EXEC))
+ flag |= O_RDONLY + 1;
+ else {
+ if (mask & MAY_READ)
+ flag |= O_RDONLY + 1;
+ if (mask & MAY_WRITE)
+ flag |= O_WRONLY + 1;
+ if ((mask & MAY_APPEND))
+ flag |= O_APPEND;
+ }
+
+ return tmy_open_perm(nd->dentry, nd->mnt, flag);
+}
+
+static int tmy_do_single_write_perm(int operation, struct dentry *dentry)
+{
+ struct mnt_namespace *namespace = current->nsproxy->mnt_ns;
+ struct list_head *p;
+ int ret = 0;
+ int error = 0;
+ int index = 0;
+ int index2 = 0;
+
+ if (!dentry || !namespace)
+ return 0;
+
+start: ;
+ index2 = 0;
+
+ /* lock namespace */
+ down_read(&namespace_sem);
+
+ list_for_each(p, &namespace->list) {
+ struct vfsmount *mnt = list_entry(p, struct vfsmount, mnt_list);
+
+ if (mnt->mnt_root->d_sb != dentry->d_sb)
+ continue;
+ if (index2++ < index)
+ continue;
+
+ /* unlock namespace */
+ up_read(&namespace_sem);
+ error = tmy_single_write_perm(operation, dentry, mnt);
+
+ if (error != 0)
+ ret = error;
+
+ index++;
+
+ goto start;
+ }
+
+ /* unlock namespace */
+ up_read(&namespace_sem);
+
+ return ret;
+}
+
+static int tmy_inode_setattr(struct dentry *dentry, struct iattr *iattr)
+{
+ if (iattr->ia_valid & ATTR_SIZE)
+ return tmy_do_single_write_perm(TMY_TYPE_TRUNCATE_ACL, dentry);
+ return 0;
+}
+
+static int tmy_inode_create(struct inode *dir, struct dentry *dentry, int mode)
+{
+ return tmy_do_single_write_perm(TMY_TYPE_CREATE_ACL, dentry);
+}
+
+static int tmy_inode_unlink(struct inode *dir, struct dentry *dentry)
+{
+ return tmy_do_single_write_perm(TMY_TYPE_UNLINK_ACL, dentry);
+}
+
+static int tmy_inode_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+ return tmy_do_single_write_perm(TMY_TYPE_MKDIR_ACL, dentry);
+}
+
+static int tmy_inode_rmdir(struct inode *dir, struct dentry *dentry)
+{
+ return tmy_do_single_write_perm(TMY_TYPE_RMDIR_ACL, dentry);
+}
+
+static int tmy_inode_symlink(struct inode *dir,
+ struct dentry *dentry,
+ const char *old_name)
+{
+ return tmy_do_single_write_perm(TMY_TYPE_SYMLINK_ACL, dentry);
+}
+
+static int tmy_inode_mknod(struct inode *inode,
+ struct dentry *dentry,
+ int mode,
+ dev_t dev)
+{
+ if (S_ISCHR(mode))
+ return tmy_do_single_write_perm(TMY_TYPE_MKCHAR_ACL, dentry);
+ if (S_ISBLK(mode))
+ return tmy_do_single_write_perm(TMY_TYPE_MKBLOCK_ACL, dentry);
+ if (S_ISFIFO(mode))
+ return tmy_do_single_write_perm(TMY_TYPE_MKFIFO_ACL, dentry);
+ if (S_ISSOCK(mode))
+ return tmy_do_single_write_perm(TMY_TYPE_MKSOCK_ACL, dentry);
+
+ return 0;
+}
+
+static int tmy_do_double_write_perm(int operation,
+ struct dentry *old_dentry,
+ struct dentry *new_dentry)
+{
+ struct mnt_namespace *namespace = current->nsproxy->mnt_ns;
+ struct list_head *p;
+ int ret = 0;
+ int error = 0;
+ int index = 0;
+ int index2 = 0;
+
+ if (!old_dentry || !new_dentry)
+ return 0;
+start: ;
+ index2 = 0;
+
+ /* lock namespace */
+ down_read(&namespace_sem);
+
+ list_for_each(p, &namespace->list) {
+ struct vfsmount *mnt = list_entry(p, struct vfsmount, mnt_list);
+
+ if (mnt->mnt_root->d_sb != old_dentry->d_sb)
+ continue;
+ if (index2++ < index)
+ continue;
+ /* unlock namespace */
+ up_read(&namespace_sem);
+ error = tmy_double_write_perm(operation, old_dentry, mnt,
+ new_dentry, mnt);
+ if (error != 0)
+ ret = error;
+ index++;
+ goto start;
+ }
+
+ /* unlock namespace */
+ up_read(&namespace_sem);
+
+ return ret;
+}
+
+static int tmy_inode_link(struct dentry *old_dentry,
+ struct inode *inode,
+ struct dentry *new_dentry)
+{
+ return tmy_do_double_write_perm(TMY_TYPE_LINK_ACL,
+ old_dentry, new_dentry);
+}
+
+static int tmy_inode_rename(struct inode *old_inode,
+ struct dentry *old_dentry,
+ struct inode *new_inode,
+ struct dentry *new_dentry)
+{
+ return tmy_do_double_write_perm(TMY_TYPE_RENAME_ACL,
+ old_dentry,
+ new_dentry);
+}
+
+static int tmy_file_fcntl(struct file *file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ if (!(arg & O_APPEND))
+ return tmy_rewrite_perm(file);
+ return 0;
+}
+
+static int tmy_socket_listen(struct socket *sock, int backlog)
+{
+ char addr[MAX_SOCK_ADDR];
+ int addr_len;
+ int error = 0;
+
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if (sock->type != SOCK_STREAM)
+ return error;
+ if (sock->sk->sk_family != PF_INET && sock->sk->sk_family != PF_INET6)
+ return error;
+
+ if (sock->ops->getname(sock, (struct sockaddr *) addr, &addr_len, 0))
+ return -EPERM;
+
+ switch (((struct sockaddr *) addr)->sa_family) {
+ struct sockaddr_in6 *in6;
+ struct sockaddr_in *in;
+
+ case AF_INET6:
+ in6 = (struct sockaddr_in6 *) addr;
+ error = tmy_network_listen_acl(1, in6->sin6_addr.s6_addr,
+ in6->sin6_port);
+ break;
+ case AF_INET:
+ in = (struct sockaddr_in *) addr;
+ error = tmy_network_listen_acl(0, (u8 *) &in->sin_addr,
+ in->sin_port);
+ break;
+ }
+
+ return error;
+}
+
+static int tmy_socket_connect(struct socket *sock,
+ struct sockaddr *addr,
+ int addr_len0)
+{
+ unsigned int addr_len = (unsigned int) addr_len0;
+ int error = 0;
+ const unsigned int type = sock->type;
+
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_RAW)
+ return error;
+
+ switch (addr->sa_family) {
+ struct sockaddr_in6 *in6;
+ struct sockaddr_in *in;
+
+ case AF_INET6:
+ if (addr_len < SIN6_LEN_RFC2133)
+ break;
+
+ in6 = (struct sockaddr_in6 *) addr;
+ if (type != SOCK_RAW)
+ error = tmy_network_connect_acl(1, type,
+ in6->sin6_addr.s6_addr,
+ in6->sin6_port);
+ else {
+ const u16 port = htons(sock->sk->sk_protocol);
+
+ error = tmy_network_connect_acl(1, SOCK_RAW,
+ in6->sin6_addr.s6_addr,
+ port);
+ }
+ break;
+
+ case AF_INET:
+ if (addr_len < sizeof(struct sockaddr_in))
+ break;
+
+ in = (struct sockaddr_in *) addr;
+ if (type != SOCK_RAW)
+ error = tmy_network_connect_acl(0, type,
+ (u8 *) &in->sin_addr,
+ in->sin_port);
+ else {
+ const u16 port = htons(sock->sk->sk_protocol);
+
+ error = tmy_network_connect_acl(0, SOCK_RAW,
+ (u8 *) &in->sin_addr,
+ port);
+ }
+ break;
+ }
+
+ return error;
+}
+
+static int tmy_socket_bind(struct socket *sock,
+ struct sockaddr *addr,
+ int addr_len0)
+{
+ unsigned int addr_len = (unsigned int) addr_len0;
+ int error = 0;
+ const unsigned int type = sock->type;
+
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_RAW)
+ return error;
+
+ switch (addr->sa_family) {
+ struct sockaddr_in6 *in6;
+ struct sockaddr_in *in;
+
+ case AF_INET6:
+ if (addr_len < SIN6_LEN_RFC2133)
+ break;
+
+ in6 = ((struct sockaddr_in6 *) addr);
+ if (type != SOCK_RAW)
+ error = tmy_network_bind_acl(1, type,
+ in6->sin6_addr.s6_addr,
+ in6->sin6_port);
+ else {
+ const u16 port = htons(sock->sk->sk_protocol);
+
+ error = tmy_network_bind_acl(1, SOCK_RAW,
+ in6->sin6_addr.s6_addr,
+ port);
+ }
+ break;
+
+ case AF_INET:
+ if (addr_len < sizeof(struct sockaddr_in))
+ break;
+
+ in = (struct sockaddr_in *) addr;
+ if (type != SOCK_RAW)
+ error = tmy_network_bind_acl(0, type,
+ (u8 *) &in->sin_addr,
+ in->sin_port);
+ else {
+ const u16 port = htons(sock->sk->sk_protocol);
+
+ error = tmy_network_bind_acl(0, SOCK_RAW,
+ (u8 *) &in->sin_addr,
+ port);
+ }
+ break;
+ }
+
+ return error;
+}
+
+static int tmy_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size)
+{
+ int error = 0;
+ const int type = sock->type;
+ struct sockaddr *addr = (struct sockaddr *) msg->msg_name;
+ const unsigned int addr_len = msg->msg_namelen;
+
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if (!addr || (type != SOCK_DGRAM && type != SOCK_RAW))
+ return error;
+
+ switch (addr->sa_family) {
+ struct sockaddr_in6 *in6;
+ struct sockaddr_in *in;
+ u16 port;
+
+ case AF_INET6:
+ if (addr_len < SIN6_LEN_RFC2133)
+ break;
+
+ in6 = (struct sockaddr_in6 *) addr;
+ port = htons(sock->sk->sk_protocol);
+ error = tmy_network_sendmsg_acl(1, type, in6->sin6_addr.s6_addr,
+ type == SOCK_DGRAM ?
+ in6->sin6_port : port);
+ break;
+
+ case AF_INET:
+ if (addr_len < sizeof(struct sockaddr_in))
+ break;
+
+ in = (struct sockaddr_in *) addr;
+ port = htons(sock->sk->sk_protocol);
+ error = tmy_network_sendmsg_acl(0, type, (u8 *) &in->sin_addr,
+ type == SOCK_DGRAM ?
+ in->sin_port : port);
+ break;
+ }
+
+ return error;
+}
+
+static int tmy_socket_post_accept(struct socket *sock, struct socket *newsock)
+{
+ int error = 0;
+ int addr_len;
+ char addr[MAX_SOCK_ADDR];
+ struct sockaddr *sockaddr = (struct sockaddr *) addr;
+
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if ((newsock->sk->sk_family != PF_INET) &&
+ (newsock->sk->sk_family != PF_INET6))
+ return error;
+
+ if (newsock->ops->getname(newsock, sockaddr, &addr_len, 2) == 0) {
+ switch (sockaddr->sa_family) {
+ struct sockaddr_in6 *in6;
+ struct sockaddr_in *in;
+
+ case AF_INET6:
+ in6 = (struct sockaddr_in6 *) addr;
+ error = tmy_network_accept_acl(1,
+ in6->sin6_addr.s6_addr,
+ in6->sin6_port);
+ break;
+
+ case AF_INET:
+ in = (struct sockaddr_in *) addr;
+ error = tmy_network_accept_acl(0, (u8 *) &in->sin_addr,
+ in->sin_port);
+ break;
+ }
+ } else
+ error = -EPERM;
+
+ if (error)
+ return -ECONNABORTED;
+ return error;
+}
+
+static int tmy_post_recv_datagram(struct sock *sk,
+ struct sk_buff *skb,
+ unsigned int flags)
+{
+ int error = 0;
+ const unsigned int type = sk->sk_type;
+
+ /* skb_recv_datagram() didn't dequeue. */
+ if (!skb)
+ return 0;
+
+ /* skb_recv_datagram() can be called from interrupt context. */
+ if (in_interrupt())
+ return 0;
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if (type != SOCK_DGRAM && type != SOCK_RAW)
+ return 0;
+
+ switch (sk->sk_family) {
+ struct sockaddr_in6 sin6;
+ struct sockaddr_in sin;
+ u16 port;
+
+ case AF_INET6:
+
+ if (type == SOCK_DGRAM) {
+ /* UDP IPv6 */
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = udp_hdr(skb)->source;
+
+ if (skb->protocol == htons(ETH_P_IP))
+ ipv6_addr_set(&sin6.sin6_addr, 0, 0,
+ htonl(0xffff),
+ ip_hdr(skb)->saddr);
+ else
+ ipv6_addr_copy(&sin6.sin6_addr,
+ &ipv6_hdr(skb)->saddr);
+
+ port = sin6.sin6_port;
+ } else {
+ /* RAW IPv6 */
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = 0;
+ ipv6_addr_copy(&sin6.sin6_addr, &ipv6_hdr(skb)->saddr);
+
+ port = htons(sk->sk_protocol);
+ }
+
+ error = tmy_network_recvmsg_acl(1, type,
+ sin6.sin6_addr.s6_addr, port);
+
+ break;
+
+ case AF_INET:
+
+ if (type == SOCK_DGRAM) {
+ /* UDP IPv4 */
+ sin.sin_family = AF_INET;
+ sin.sin_port = udp_hdr(skb)->source;
+ sin.sin_addr.s_addr = ip_hdr(skb)->saddr;
+
+ port = sin.sin_port;
+ } else {
+ /* RAW IPv4 */
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = ip_hdr(skb)->saddr;
+ sin.sin_port = 0;
+
+ port = htons(sk->sk_protocol);
+ }
+
+ error = tmy_network_recvmsg_acl(0, type,
+ (u8 *) &sin.sin_addr, port);
+
+ break;
+
+ }
+
+ if (error)
+ error = -EAGAIN;
+ return error;
+}
+
+static int tmy_sb_mount(char *dev_name,
+ struct nameidata *nd,
+ char *type,
+ unsigned long flags,
+ void *data)
+{
+ char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ char *dir_name;
+ int error;
+
+ if (!buf)
+ return -ENOMEM;
+
+ dir_name = d_path(nd->dentry, nd->mnt, buf, PAGE_SIZE);
+
+ if (IS_ERR(dir_name))
+ error = PTR_ERR(dir_name);
+ else
+ error = tmy_mount_perm(dev_name, dir_name, type, flags);
+
+ if (!error && (flags & MS_REMOUNT) == 0)
+ error = tmy_conceal_mount(nd);
+
+ kfree(buf);
+ return error;
+}
+
+static int tmy_sb_umount(struct vfsmount *mnt, int flags)
+{
+ return tmy_umount_perm(mnt);
+}
+
+static int tmy_sb_pivotroot(struct nameidata *old_nd, struct nameidata *new_nd)
+{
+ return tmy_pivot_root_perm(old_nd, new_nd);
+}
+
+static int tmy_task_kill_unlocked(int pid, int sig)
+{
+ return tmy_signal_acl(sig, pid);
+}
+
+static int tmy_task_tkill_unlocked(int pid, int sig)
+{
+ return tmy_signal_acl(sig, pid);
+}
+
+static int tmy_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+ return tmy_signal_acl(sig, pid);
+}
+
+static struct security_operations tomoyo_security_ops = {
+ .task_alloc_security = tmy_task_alloc_security,
+ .task_free_security = tmy_task_free_security,
+ .bprm_alloc_security = tmy_bprm_alloc_security,
+ .bprm_check_security = tmy_bprm_check_security,
+ .bprm_post_apply_creds = tmy_bprm_post_apply_creds,
+ .bprm_free_security = tmy_bprm_free_security,
+ .sysctl = tmy_sysctl,
+ .inode_permission = tmy_inode_permission,
+ .inode_setattr = tmy_inode_setattr,
+ .inode_create = tmy_inode_create,
+ .inode_unlink = tmy_inode_unlink,
+ .inode_mkdir = tmy_inode_mkdir,
+ .inode_rmdir = tmy_inode_rmdir,
+ .inode_symlink = tmy_inode_symlink,
+ .inode_mknod = tmy_inode_mknod,
+ .inode_link = tmy_inode_link,
+ .inode_rename = tmy_inode_rename,
+ .file_fcntl = tmy_file_fcntl,
+ .socket_listen = tmy_socket_listen,
+ .socket_connect = tmy_socket_connect,
+ .socket_bind = tmy_socket_bind,
+ .socket_sendmsg = tmy_socket_sendmsg,
+ .sb_mount = tmy_sb_mount,
+ .sb_umount = tmy_sb_umount,
+ .sb_pivotroot = tmy_sb_pivotroot,
+#ifdef TMY_LSM_EXPANSION
+ .socket_post_accept = tmy_socket_post_accept,
+ .post_recv_datagram = tmy_post_recv_datagram,
+ .task_kill_unlocked = tmy_task_kill_unlocked,
+ .task_tkill_unlocked = tmy_task_tkill_unlocked,
+ .task_tgkill_unlocked = tmy_task_tgkill_unlocked,
+#endif
+};
+
+static int __init tmy_init(void)
+{
+
+ /* register ourselves with the security framework */
+ if (register_security(&tomoyo_security_ops))
+ panic("Failure registering TOMOYO Linux");
+
+ printk(KERN_INFO "TOMOYO Linux initialized\n");
+
+ tmy_proc_init();
+
+ tmy_cachep = kmem_cache_create("tomoyo_security",
+ sizeof(struct tmy_security),
+ 0, SLAB_PANIC, NULL);
+
+ init_task.security = kmem_cache_alloc(tmy_cachep, GFP_KERNEL);
+ ((struct tmy_security *) init_task.security)->domain = &KERNEL_DOMAIN;
+ ((struct tmy_security *) init_task.security)->prev_domain = NULL;
+ ((struct tmy_security *) init_task.security)->flags = 0;
+
+ return 0;
+}
+
+security_initcall(tmy_init);
This patch allows administrators use conditional permission.
TOMOYO Linux supports conditional permission based on
process's UID,GID etc. and/or requested pathname's UID/GID.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/condition.c | 680 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 680 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/condition.c 2007-08-24 15:51:39.000000000 +0900
@@ -0,0 +1,680 @@
+/*
+ * security/tomoyo/condition.c
+ *
+ * Functions to support conditional access control for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/**
+ * tmy_find_condition_part - check whether a line contains condition part.
+ * @data: a line to check.
+ *
+ * Returns pointer to condition part if found.
+ * Returns NULL if not found.
+ *
+ * Since the trailing spaces are removed by tmy_normalize_line(),
+ * the last "\040if\040" sequence corresponds to condition part.
+ */
+char *tmy_find_condition_part(char *data)
+{
+ char *cp = strstr(data, " if ");
+ if (cp) {
+ char *cp2;
+ while ((cp2 = strstr(cp + 3, " if ")) != NULL)
+ cp = cp2;
+ *cp++ = '\0';
+ }
+ return cp;
+}
+
+#define VALUE_TYPE_DECIMAL 1 /* 01 */
+#define VALUE_TYPE_OCTAL 2 /* 10 */
+#define VALUE_TYPE_HEXADECIMAL 3 /* 11 */
+
+static int tmy_parse_ulong(unsigned long *result, const char **str)
+{
+ const char *cp = *str;
+ char *ep;
+ int base = 10;
+ if (*cp == '0') {
+ char c = * (cp + 1);
+ if (c == 'x' || c == 'X') {
+ base = 16; cp += 2;
+ } else if (c >= '0' && c <= '7') {
+ base = 8; cp++;
+ }
+ }
+ *result = simple_strtoul(cp, &ep, base);
+ if (cp == ep) return 0; /* 00 */
+ *str = ep;
+ return (base == 16 ? VALUE_TYPE_HEXADECIMAL :
+ (base == 8 ? VALUE_TYPE_OCTAL : VALUE_TYPE_DECIMAL));
+}
+
+static void tmy_print_ulong(char *buffer, const int buffer_len,
+ const unsigned long value, const int type)
+{
+ if (type == VALUE_TYPE_DECIMAL)
+ snprintf(buffer, buffer_len, "%lu", value);
+ else if (type == VALUE_TYPE_OCTAL)
+ snprintf(buffer, buffer_len, "0%lo", value);
+ else
+ snprintf(buffer, buffer_len, "0x%lX", value);
+}
+
+/* List of conditins. */
+static struct condition_list {
+ struct condition_list *next;
+ int length;
+ /* "unsigned long condition[length]" comes here.*/
+} head;
+
+#define TASK_UID 0
+#define TASK_EUID 1
+#define TASK_SUID 2
+#define TASK_FSUID 3
+#define TASK_GID 4
+#define TASK_EGID 5
+#define TASK_SGID 6
+#define TASK_FSGID 7
+#define TASK_PID 8
+#define TASK_PPID 9
+#define PATH1_UID 10
+#define PATH1_GID 11
+#define PATH1_INO 12
+#define PATH1_PARENT_UID 13
+#define PATH1_PARENT_GID 14
+#define PATH1_PARENT_INO 15
+#define PATH2_PARENT_UID 16
+#define PATH2_PARENT_GID 17
+#define PATH2_PARENT_INO 18
+#define MAX_KEYWORD 19
+
+static struct {
+ const char *keyword;
+ const int keyword_len; /* strlen(keyword) */
+} cc_keyword[MAX_KEYWORD] = {
+ [TASK_UID] = { "task.uid", 8 },
+ [TASK_EUID] = { "task.euid", 9 },
+ [TASK_SUID] = { "task.suid", 9 },
+ [TASK_FSUID] = { "task.fsuid", 10 },
+ [TASK_GID] = { "task.gid", 8 },
+ [TASK_EGID] = { "task.egid", 9 },
+ [TASK_SGID] = { "task.sgid", 9 },
+ [TASK_FSGID] = { "task.fsgid", 10 },
+ [TASK_PID] = { "task.pid", 8 },
+ [TASK_PPID] = { "task.ppid", 9 },
+ [PATH1_UID] = { "path1.uid", 9 },
+ [PATH1_GID] = { "path1.gid", 9 },
+ [PATH1_INO] = { "path1.ino", 9 },
+ [PATH1_PARENT_UID] = { "path1.parent.uid", 16 },
+ [PATH1_PARENT_GID] = { "path1.parent.gid", 16 },
+ [PATH1_PARENT_INO] = { "path1.parent.ino", 16 },
+ [PATH2_PARENT_UID] = { "path2.parent.uid", 16 },
+ [PATH2_PARENT_GID] = { "path2.parent.gid", 16 },
+ [PATH2_PARENT_INO] = { "path2.parent.ino", 16 }
+};
+
+/**
+ * tmy_assign_condition - create condition part.
+ * @condition: pointer to condition part.
+ *
+ * Returns pointer to "struct condition_list" on success.
+ * Returns NULL on failure.
+ */
+const struct condition_list *tmy_assign_condition(const char *condition)
+{
+ const char *start;
+ struct condition_list *ptr;
+ struct condition_list *new_ptr;
+ unsigned long *ptr2;
+ int counter = 0;
+ int size;
+ int left;
+ int right;
+ unsigned long left_min = 0;
+ unsigned long left_max = 0;
+ unsigned long right_min = 0;
+ unsigned long right_max = 0;
+ if (strncmp(condition, "if ", 3))
+ return NULL;
+ condition += 3;
+ start = condition;
+ while (*condition) {
+ if (*condition == ' ')
+ condition++;
+ for (left = 0; left < MAX_KEYWORD; left++) {
+ if (strncmp(condition, cc_keyword[left].keyword,
+ cc_keyword[left].keyword_len))
+ continue;
+ condition += cc_keyword[left].keyword_len;
+ break;
+ }
+ if (left == MAX_KEYWORD) {
+ if (!tmy_parse_ulong(&left_min, &condition))
+ goto out;
+ counter++; /* body */
+ if (*condition != '-')
+ goto not_range1;
+ condition++;
+ if (!tmy_parse_ulong(&left_max, &condition)
+ || left_min > left_max)
+ goto out;
+ counter++; /* body */
+not_range1: ;
+ }
+ if (strncmp(condition, "!=", 2) == 0)
+ condition += 2;
+ else if (*condition == '=')
+ condition++;
+ else
+ goto out;
+ counter++; /* header */
+ for (right = 0; right < MAX_KEYWORD; right++) {
+ if (strncmp(condition, cc_keyword[right].keyword,
+ cc_keyword[right].keyword_len))
+ continue;
+ condition += cc_keyword[right].keyword_len;
+ break;
+ }
+ if (right == MAX_KEYWORD) {
+ if (!tmy_parse_ulong(&right_min, &condition))
+ goto out;
+ counter++; /* body */
+ if (*condition != '-')
+ goto not_range2;
+ condition++;
+ if (!tmy_parse_ulong(&right_max, &condition)
+ || right_min > right_max)
+ goto out;
+ counter++; /* body */
+not_range2: ;
+ }
+ }
+ size = sizeof(*new_ptr) + counter * sizeof(unsigned long);
+ new_ptr = tmy_alloc(size);
+ if (!new_ptr)
+ return NULL;
+ new_ptr->length = counter;
+ ptr2 = (unsigned long *) (((u8 *) new_ptr) + sizeof(*new_ptr));
+ condition = start;
+ while (*condition) {
+ unsigned int match = 0;
+ if (*condition == ' ')
+ condition++;
+ for (left = 0; left < MAX_KEYWORD; left++) {
+ if (strncmp(condition, cc_keyword[left].keyword,
+ cc_keyword[left].keyword_len))
+ continue;
+ condition += cc_keyword[left].keyword_len;
+ break;
+ }
+ if (left == MAX_KEYWORD) {
+ match |= tmy_parse_ulong(&left_min, &condition) << 2;
+ counter--; /* body */
+ if (*condition != '-')
+ goto not_range3;
+ condition++;
+ match |= tmy_parse_ulong(&left_max, &condition) << 4;
+ counter--; /* body */
+ left++;
+not_range3: ;
+ }
+ if (strncmp(condition, "!=", 2) == 0)
+ condition += 2;
+ else if (*condition == '=') {
+ match |= 1; condition++;
+ } else
+ goto out2;
+ counter--; /* header */
+ for (right = 0; right < MAX_KEYWORD; right++) {
+ if (strncmp(condition, cc_keyword[right].keyword,
+ cc_keyword[right].keyword_len))
+ continue;
+ condition += cc_keyword[right].keyword_len;
+ break;
+ }
+ if (right == MAX_KEYWORD) {
+ match |= tmy_parse_ulong(&right_min, &condition) << 6;
+ counter--; /* body */
+ if (*condition != '-')
+ goto not_range4;
+ condition++;
+ match |= tmy_parse_ulong(&right_max, &condition) << 8;
+ counter--; /* body */
+ right++;
+not_range4: ;
+ }
+ if (counter < 0)
+ goto out2;
+ *ptr2++ = (match << 16) | (left << 8) | right;
+ if (left >= MAX_KEYWORD)
+ *ptr2++ = left_min;
+ if (left == MAX_KEYWORD + 1)
+ *ptr2++ = left_max;
+ if (right >= MAX_KEYWORD)
+ *ptr2++ = right_min;
+ if (right == MAX_KEYWORD + 1)
+ *ptr2++ = right_max;
+ }
+ {
+ static DECLARE_MUTEX(lock);
+ struct condition_list *prev = NULL;
+ down(&lock);
+ for (ptr = &head; ptr; prev = ptr, ptr = ptr->next) {
+ /* Don't compare if size differs. */
+ if (ptr->length != new_ptr->length)
+ continue;
+ /*
+ * Compare ptr and new_ptr
+ * except ptr->next and new_ptr->next .
+ */
+ if (memcmp(((u8 *) ptr) + sizeof(ptr->next),
+ ((u8 *) new_ptr) + sizeof(new_ptr->next),
+ size - sizeof(ptr->next)))
+ continue;
+ /* Same entry found. Share this entry. */
+ tmy_free(new_ptr);
+ new_ptr = ptr;
+ goto ok;
+ }
+ /* Same entry not found. Save this entry. */
+ ptr = tmy_alloc_element(size);
+ if (ptr)
+ memmove(ptr, new_ptr, size);
+ tmy_free(new_ptr);
+ new_ptr = ptr;
+ /* Append to chain. */
+ prev->next = new_ptr;
+ok: ;
+ up(&lock);
+ }
+ return new_ptr;
+out2: ;
+ tmy_free(new_ptr);
+out: ;
+ return NULL;
+}
+
+/* Get inode's attribute information. */
+static void tmy_get_attributes(struct obj_info *obj)
+{
+ struct vfsmount *mnt;
+ struct dentry *dentry;
+ struct inode *inode;
+ struct kstat stat;
+
+ mnt = obj->path1_vfsmnt;
+ dentry = obj->path1_dentry;
+ inode = dentry->d_inode;
+ if (inode) {
+ if (!inode->i_op || vfs_getattr(mnt, dentry, &stat)) {
+ /* Nothing to do. */
+ } else {
+ obj->path1_stat.uid = stat.uid;
+ obj->path1_stat.gid = stat.gid;
+ obj->path1_stat.ino = stat.ino;
+ obj->path1_valid = 1;
+ }
+ }
+
+ dentry = dget_parent(obj->path1_dentry);
+ inode = dentry->d_inode;
+ if (inode) {
+ if (!inode->i_op || vfs_getattr(mnt, dentry, &stat)) {
+ /* Nothing to do. */
+ } else {
+ obj->path1_parent_stat.uid = stat.uid;
+ obj->path1_parent_stat.gid = stat.gid;
+ obj->path1_parent_stat.ino = stat.ino;
+ obj->path1_parent_valid = 1;
+ }
+ }
+ dput(dentry);
+
+ mnt = obj->path2_vfsmnt;
+ if (mnt) {
+ dentry = dget_parent(obj->path2_dentry);
+ inode = dentry->d_inode;
+ if (inode) {
+ if (!inode->i_op || vfs_getattr(mnt, dentry, &stat)) {
+ /* Nothing to do. */
+ } else {
+ obj->path2_parent_stat.uid = stat.uid;
+ obj->path2_parent_stat.gid = stat.gid;
+ obj->path2_parent_stat.ino = stat.ino;
+ obj->path2_parent_valid = 1;
+ }
+ }
+ dput(dentry);
+ }
+}
+
+/**
+ * tmy_check_condition - check condition part.
+ * @ptr: pointer to "struct condition_list".
+ * @obj: pointer to "struct obj_info". May be NULL.
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_check_condition(const struct condition_list *ptr, struct obj_info *obj)
+{
+ struct task_struct *task = current;
+ int i;
+ unsigned long left_min = 0;
+ unsigned long left_max = 0;
+ unsigned long right_min = 0;
+ unsigned long right_max = 0;
+ const unsigned long *ptr2;
+ if (!ptr)
+ return 0;
+ ptr2 = (unsigned long *) (((u8 *) ptr) + sizeof(*ptr));
+ for (i = 0; i < ptr->length; i++) {
+ const u8 match = ((*ptr2) >> 16) & 1;
+ const u8 left = (*ptr2) >> 8;
+ const u8 right = *ptr2;
+ ptr2++;
+ if ((left >= PATH1_UID && left < MAX_KEYWORD)
+ || (right >= PATH1_UID && right < MAX_KEYWORD)) {
+ if (!obj)
+ goto out;
+ if (!obj->validate_done) {
+ tmy_get_attributes(obj);
+ obj->validate_done = 1;
+ }
+ }
+ switch (left) {
+ case TASK_UID:
+ left_min = task->uid;
+ left_max = left_min;
+ break;
+ case TASK_EUID:
+ left_min = task->euid;
+ left_max = left_min;
+ break;
+ case TASK_SUID:
+ left_min = task->suid;
+ left_max = left_min;
+ break;
+ case TASK_FSUID:
+ left_min = task->fsuid;
+ left_max = left_min;
+ break;
+ case TASK_GID:
+ left_min = task->gid;
+ left_max = left_min;
+ break;
+ case TASK_EGID:
+ left_min = task->egid;
+ left_max = left_min;
+ break;
+ case TASK_SGID:
+ left_min = task->sgid;
+ left_max = left_min;
+ break;
+ case TASK_FSGID:
+ left_min = task->fsgid;
+ left_max = left_min;
+ break;
+ case TASK_PID:
+ left_min = task->pid;
+ left_max = left_min;
+ break;
+ case TASK_PPID:
+ left_min = sys_getppid();
+ left_max = left_min;
+ break;
+ case PATH1_UID:
+ if (!obj->path1_valid)
+ goto out;
+ left_min = obj->path1_stat.uid;
+ left_max = left_min;
+ break;
+ case PATH1_GID:
+ if (!obj->path1_valid)
+ goto out;
+ left_min = obj->path1_stat.gid;
+ left_max = left_min;
+ break;
+ case PATH1_INO:
+ if (!obj->path1_valid)
+ goto out;
+ left_min = obj->path1_stat.ino;
+ left_max = left_min;
+ break;
+ case PATH1_PARENT_UID:
+ if (!obj->path1_parent_valid)
+ goto out;
+ left_min = obj->path1_parent_stat.uid;
+ left_max = left_min;
+ break;
+ case PATH1_PARENT_GID:
+ if (!obj->path1_parent_valid)
+ goto out;
+ left_min = obj->path1_parent_stat.gid;
+ left_max = left_min;
+ break;
+ case PATH1_PARENT_INO:
+ if (!obj->path1_parent_valid)
+ goto out;
+ left_min = obj->path1_parent_stat.ino;
+ left_max = left_min;
+ break;
+ case PATH2_PARENT_UID:
+ if (!obj->path2_parent_valid)
+ goto out;
+ left_min = obj->path2_parent_stat.uid;
+ left_max = left_min;
+ break;
+ case PATH2_PARENT_GID:
+ if (!obj->path2_parent_valid)
+ goto out;
+ left_min = obj->path2_parent_stat.gid;
+ left_max = left_min;
+ break;
+ case PATH2_PARENT_INO:
+ if (!obj->path2_parent_valid)
+ goto out;
+ left_min = obj->path2_parent_stat.ino;
+ left_max = left_min;
+ break;
+ case MAX_KEYWORD:
+ left_min = *ptr2++;
+ left_max = left_min;
+ i++;
+ break;
+ case MAX_KEYWORD + 1:
+ left_min = *ptr2++;
+ left_max = *ptr2++;
+ i += 2;
+ break;
+ }
+ switch (right) {
+ case TASK_UID:
+ right_min = task->uid;
+ right_max = right_min;
+ break;
+ case TASK_EUID:
+ right_min = task->euid;
+ right_max = right_min;
+ break;
+ case TASK_SUID:
+ right_min = task->suid;
+ right_max = right_min;
+ break;
+ case TASK_FSUID:
+ right_min = task->fsuid;
+ right_max = right_min;
+ break;
+ case TASK_GID:
+ right_min = task->gid;
+ right_max = right_min;
+ break;
+ case TASK_EGID:
+ right_min = task->egid;
+ right_max = right_min;
+ break;
+ case TASK_SGID:
+ right_min = task->sgid;
+ right_max = right_min;
+ break;
+ case TASK_FSGID:
+ right_min = task->fsgid;
+ right_max = right_min;
+ break;
+ case TASK_PID:
+ right_min = task->pid;
+ right_max = right_min;
+ break;
+ case TASK_PPID:
+ right_min = sys_getppid();
+ right_max = right_min;
+ break;
+ case PATH1_UID:
+ if (!obj->path1_valid)
+ goto out;
+ right_min = obj->path1_stat.uid;
+ right_max = right_min;
+ break;
+ case PATH1_GID:
+ if (!obj->path1_valid)
+ goto out;
+ right_min = obj->path1_stat.gid;
+ right_max = right_min;
+ break;
+ case PATH1_INO:
+ if (!obj->path1_valid)
+ goto out;
+ right_min = obj->path1_stat.ino;
+ right_max = right_min;
+ break;
+ case PATH1_PARENT_UID:
+ if (!obj->path1_parent_valid)
+ goto out;
+ right_min = obj->path1_parent_stat.uid;
+ right_max = right_min;
+ break;
+ case PATH1_PARENT_GID:
+ if (!obj->path1_parent_valid)
+ goto out;
+ right_min = obj->path1_parent_stat.gid;
+ right_max = right_min;
+ break;
+ case PATH1_PARENT_INO:
+ if (!obj->path1_parent_valid)
+ goto out;
+ right_min = obj->path1_parent_stat.ino;
+ right_max = right_min;
+ break;
+ case PATH2_PARENT_UID:
+ if (!obj->path2_parent_valid)
+ goto out;
+ right_min = obj->path2_parent_stat.uid;
+ right_max = right_min;
+ break;
+ case PATH2_PARENT_GID:
+ if (!obj->path2_parent_valid)
+ goto out;
+ right_min = obj->path2_parent_stat.gid;
+ right_max = right_min;
+ break;
+ case PATH2_PARENT_INO:
+ if (!obj->path2_parent_valid)
+ goto out;
+ right_min = obj->path2_parent_stat.ino;
+ right_max = right_min;
+ break;
+ case MAX_KEYWORD:
+ right_min = *ptr2++;
+ right_max = right_min;
+ i++;
+ break;
+ case MAX_KEYWORD + 1:
+ right_min = *ptr2++;
+ right_max = *ptr2++;
+ i += 2;
+ break;
+ }
+ if (match) {
+ if (left_min <= right_max && left_max >= right_min)
+ continue;
+ } else {
+ if (left_min > right_max || left_max < right_min)
+ continue;
+ }
+out: ;
+ return -EPERM;
+ }
+ return 0;
+}
+
+/**
+ * tmy_dump_condition - dump condition part.
+ * @head: pointer to "struct io_buffer".
+ * @ptr: pointer to "struct condition_list". May be NULL.
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_dump_condition(struct io_buffer *head, const struct condition_list *ptr)
+{
+ int i;
+ const unsigned long *ptr2;
+ char buffer[32];
+ if (!ptr)
+ goto last;
+ ptr2 = (unsigned long *) (((u8 *) ptr) + sizeof(*ptr));
+ memset(buffer, 0, sizeof(buffer));
+ for (i = 0; i < ptr->length; i++) {
+ const u16 match = (*ptr2) >> 16;
+ const u8 left = (*ptr2) >> 8;
+ const u8 right = *ptr2;
+ ptr2++;
+ if (tmy_io_printf(head, "%s", i ? " " : " if "))
+ break;
+ if (left < MAX_KEYWORD) {
+ if (tmy_io_printf(head, "%s", cc_keyword[left].keyword))
+ break;
+ } else {
+ tmy_print_ulong(buffer, sizeof(buffer) - 1, *ptr2++,
+ (match >> 2) & 3);
+ if (tmy_io_printf(head, "%s", buffer))
+ break;
+ i++;
+ if (left == MAX_KEYWORD + 1) {
+ tmy_print_ulong(buffer, sizeof(buffer) - 1,
+ *ptr2++, (match >> 4) & 3);
+ if (tmy_io_printf(head, "-%s", buffer))
+ break;
+ i++;
+ }
+ }
+ if (tmy_io_printf(head, "%s", (match & 1) ? "=" : "!="))
+ break;
+ if (right < MAX_KEYWORD) {
+ if (tmy_io_printf(head, "%s",
+ cc_keyword[right].keyword))
+ break;
+ } else {
+ tmy_print_ulong(buffer, sizeof(buffer) - 1, *ptr2++,
+ (match >> 6) & 3);
+ if (tmy_io_printf(head, "%s", buffer))
+ break;
+ i++;
+ if (right == MAX_KEYWORD + 1) {
+ tmy_print_ulong(buffer, sizeof(buffer) - 1,
+ *ptr2++, (match >> 8) & 3);
+ if (tmy_io_printf(head, "-%s", buffer))
+ break;
+ i++;
+ }
+ }
+ }
+ if (i < ptr->length)
+ return -ENOMEM;
+last: ;
+ return tmy_io_printf(head, "\n") ? -ENOMEM : 0;
+}
LSM expansion for TOMOYO Linux.
LSM hooks for sending signal:
* task_kill_unlocked is added in sys_kill
* task_tkill_unlocked is added in sys_tkill
* task_tgkill_unlocked is added in sys_tgkill
LSM hooks for network accept and recv:
* socket_post_accept is modified to return int.
* post_recv_datagram is added in skb_recv_datagram.
You can try TOMOYO Linux without this patch, but in that case, you
can't use access control functionality for restricting signal
transmission and incoming network data.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
include/linux/security.h | 91 +++++++++++++++++++++++++++++++++++++++++++----
kernel/signal.c | 17 ++++++++
net/core/datagram.c | 7 +++
net/socket.c | 7 ++-
security/dummy.c | 32 ++++++++++++++--
5 files changed, 142 insertions(+), 12 deletions(-)
--- linux-2.6.orig/include/linux/security.h 2007-08-23 21:25:04.000000000 +0900
+++ linux-2.6/include/linux/security.h 2007-08-24 15:51:39.000000000 +0900
@@ -628,6 +628,22 @@ struct request_sock;
* @sig contains the signal value.
* @secid contains the sid of the process where the signal originated
* Return 0 if permission is granted.
+ * @task_kill_unlocked:
+ * Check permission before sending signal @sig to the process of @pid
+ * with sys_kill.
+ * @pid contains the pid of target process.
+ * @sig contains the signal value.
+ * @task_tkill_unlocked:
+ * Check permission before sending signal @sig to the process of @pid
+ * with sys_tkill.
+ * @pid contains the pid of target process.
+ * @sig contains the signal value.
+ * @task_tgkill_unlocked:
+ * Check permission before sending signal @sig to the process of @pid
+ * with sys_tgkill.
+ * @tgid contains the thread group id.
+ * @pid contains the pid of target process.
+ * @sig contains the signal value.
* @task_wait:
* Check permission before allowing a process to reap a child process @p
* and collect its status information.
@@ -749,8 +765,12 @@ struct request_sock;
* @socket_post_accept:
* This hook allows a security module to copy security
* information into the newly created socket's inode.
+ * This hook also allows a security module to filter connections
+ * from unwanted peers.
+ * The connection will be aborted if this hook returns nonzero.
* @sock contains the listening socket structure.
* @newsock contains the newly created server socket for connection.
+ * Return 0 if permission is granted.
* @socket_sendmsg:
* Check permission before transmitting a message to another socket.
* @sock contains the socket structure.
@@ -764,6 +784,11 @@ struct request_sock;
* @size contains the size of message structure.
* @flags contains the operational flags.
* Return 0 if permission is granted.
+ * @post_recv_datagram:
+ * Check permission after receiving a datagram.
+ * @sk contains the socket.
+ * @skb contains the socket buffer (may be NULL).
+ * @flags contains the operational flags.
* @socket_getsockname:
* Check permission before the local address (name) of the socket object
* @sock is retrieved.
@@ -1279,6 +1304,9 @@ struct security_operations {
int (*task_movememory) (struct task_struct * p);
int (*task_kill) (struct task_struct * p,
struct siginfo * info, int sig, u32 secid);
+ int (*task_kill_unlocked) (int pid, int sig);
+ int (*task_tkill_unlocked) (int pid, int sig);
+ int (*task_tgkill_unlocked) (int tgid, int pid, int sig);
int (*task_wait) (struct task_struct * p);
int (*task_prctl) (int option, unsigned long arg2,
unsigned long arg3, unsigned long arg4,
@@ -1346,12 +1374,16 @@ struct security_operations {
struct sockaddr * address, int addrlen);
int (*socket_listen) (struct socket * sock, int backlog);
int (*socket_accept) (struct socket * sock, struct socket * newsock);
- void (*socket_post_accept) (struct socket * sock,
- struct socket * newsock);
+#define TMY_LSM_EXPANSION
+ int (*socket_post_accept) (struct socket *sock,
+ struct socket *newsock);
int (*socket_sendmsg) (struct socket * sock,
struct msghdr * msg, int size);
int (*socket_recvmsg) (struct socket * sock,
struct msghdr * msg, int size, int flags);
+ int (*post_recv_datagram) (struct sock *sk,
+ struct sk_buff *skb,
+ unsigned int flags);
int (*socket_getsockname) (struct socket * sock);
int (*socket_getpeername) (struct socket * sock);
int (*socket_getsockopt) (struct socket * sock, int level, int optname);
@@ -1967,6 +1999,21 @@ static inline int security_task_kill (st
return security_ops->task_kill (p, info, sig, secid);
}
+static inline int security_task_kill_unlocked(int pid, int sig)
+{
+ return security_ops->task_kill_unlocked(pid, sig);
+}
+
+static inline int security_task_tkill_unlocked(int pid, int sig)
+{
+ return security_ops->task_tkill_unlocked(pid, sig);
+}
+
+static inline int security_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+ return security_ops->task_tgkill_unlocked(tgid, pid, sig);
+}
+
static inline int security_task_wait (struct task_struct *p)
{
return security_ops->task_wait (p);
@@ -2645,6 +2692,21 @@ static inline int security_task_kill (st
return 0;
}
+static inline int security_task_kill_unlocked(int pid, int sig)
+{
+ return 0;
+}
+
+static inline int security_task_tkill_unlocked(int pid, int sig)
+{
+ return 0;
+}
+
+static inline int security_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+ return 0;
+}
+
static inline int security_task_wait (struct task_struct *p)
{
return 0;
@@ -2870,10 +2932,10 @@ static inline int security_socket_accept
return security_ops->socket_accept(sock, newsock);
}
-static inline void security_socket_post_accept(struct socket * sock,
- struct socket * newsock)
+static inline int security_socket_post_accept(struct socket *sock,
+ struct socket *newsock)
{
- security_ops->socket_post_accept(sock, newsock);
+ return security_ops->socket_post_accept(sock, newsock);
}
static inline int security_socket_sendmsg(struct socket * sock,
@@ -2889,6 +2951,13 @@ static inline int security_socket_recvms
return security_ops->socket_recvmsg(sock, msg, size, flags);
}
+static inline int security_post_recv_datagram(struct sock *sk,
+ struct sk_buff *skb,
+ unsigned int flags)
+{
+ return security_ops->post_recv_datagram(sk, skb, flags);
+}
+
static inline int security_socket_getsockname(struct socket * sock)
{
return security_ops->socket_getsockname(sock);
@@ -3033,9 +3102,10 @@ static inline int security_socket_accept
return 0;
}
-static inline void security_socket_post_accept(struct socket * sock,
- struct socket * newsock)
+static inline int security_socket_post_accept(struct socket *sock,
+ struct socket *newsock)
{
+ return 0;
}
static inline int security_socket_sendmsg(struct socket * sock,
@@ -3051,6 +3121,13 @@ static inline int security_socket_recvms
return 0;
}
+static inline int security_post_recv_datagram(struct sock *sk,
+ struct sk_buff *skb,
+ unsigned int flags);
+{
+ return 0;
+}
+
static inline int security_socket_getsockname(struct socket * sock)
{
return 0;
--- linux-2.6.orig/kernel/signal.c 2007-08-23 21:25:04.000000000 +0900
+++ linux-2.6/kernel/signal.c 2007-08-24 15:51:39.000000000 +0900
@@ -2199,6 +2199,11 @@ asmlinkage long
sys_kill(int pid, int sig)
{
struct siginfo info;
+ int ret;
+
+ ret = security_task_kill_unlocked(pid, sig);
+ if (ret)
+ return ret;
info.si_signo = sig;
info.si_errno = 0;
@@ -2254,10 +2259,16 @@ static int do_tkill(int tgid, int pid, i
*/
asmlinkage long sys_tgkill(int tgid, int pid, int sig)
{
+ int ret;
+
/* This is only valid for single tasks */
if (pid <= 0 || tgid <= 0)
return -EINVAL;
+ ret = security_task_tgkill_unlocked(tgid, pid, sig);
+ if (ret)
+ return ret;
+
return do_tkill(tgid, pid, sig);
}
@@ -2267,10 +2278,16 @@ asmlinkage long sys_tgkill(int tgid, int
asmlinkage long
sys_tkill(int pid, int sig)
{
+ int ret;
+
/* This is only valid for single tasks */
if (pid <= 0)
return -EINVAL;
+ ret = security_task_tkill_unlocked(pid, sig);
+ if (ret)
+ return ret;
+
return do_tkill(0, pid, sig);
}
--- linux-2.6.orig/net/socket.c 2007-08-23 21:25:04.000000000 +0900
+++ linux-2.6/net/socket.c 2007-08-24 15:51:39.000000000 +0900
@@ -1437,13 +1437,16 @@ asmlinkage long sys_accept(int fd, struc
goto out_fd;
}
+ /* Filter connections from unwanted peers like TCP Wrapper. */
+ err = security_socket_post_accept(sock, newsock);
+ if (err)
+ goto out_fd;
+
/* File flags are not inherited via accept() unlike another OSes. */
fd_install(newfd, newfile);
err = newfd;
- security_socket_post_accept(sock, newsock);
-
out_put:
fput_light(sock->file, fput_needed);
out:
--- linux-2.6.orig/security/dummy.c 2007-08-23 21:25:04.000000000 +0900
+++ linux-2.6/security/dummy.c 2007-08-24 15:51:39.000000000 +0900
@@ -564,6 +564,21 @@ static int dummy_task_kill (struct task_
return 0;
}
+static int dummy_task_kill_unlocked(int pid, int sig)
+{
+ return 0;
+}
+
+static int dummy_task_tkill_unlocked(int pid, int sig)
+{
+ return 0;
+}
+
+static int dummy_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+ return 0;
+}
+
static int dummy_task_prctl (int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5)
{
@@ -741,10 +756,10 @@ static int dummy_socket_accept (struct s
return 0;
}
-static void dummy_socket_post_accept (struct socket *sock,
- struct socket *newsock)
+static int dummy_socket_post_accept(struct socket *sock,
+ struct socket *newsock)
{
- return;
+ return 0;
}
static int dummy_socket_sendmsg (struct socket *sock, struct msghdr *msg,
@@ -759,6 +774,13 @@ static int dummy_socket_recvmsg (struct
return 0;
}
+static int dummy_post_recv_datagram(struct sock *sk,
+ struct sk_buff *skb,
+ unsigned int flags)
+{
+ return 0;
+}
+
static int dummy_socket_getsockname (struct socket *sock)
{
return 0;
@@ -1053,6 +1075,9 @@ void security_fixup_ops (struct security
set_to_dummy_if_null(ops, task_movememory);
set_to_dummy_if_null(ops, task_wait);
set_to_dummy_if_null(ops, task_kill);
+ set_to_dummy_if_null(ops, task_kill_unlocked);
+ set_to_dummy_if_null(ops, task_tkill_unlocked);
+ set_to_dummy_if_null(ops, task_tgkill_unlocked);
set_to_dummy_if_null(ops, task_prctl);
set_to_dummy_if_null(ops, task_reparent_to_init);
set_to_dummy_if_null(ops, task_to_inode);
@@ -1096,6 +1121,7 @@ void security_fixup_ops (struct security
set_to_dummy_if_null(ops, socket_post_accept);
set_to_dummy_if_null(ops, socket_sendmsg);
set_to_dummy_if_null(ops, socket_recvmsg);
+ set_to_dummy_if_null(ops, post_recv_datagram);
set_to_dummy_if_null(ops, socket_getsockname);
set_to_dummy_if_null(ops, socket_getpeername);
set_to_dummy_if_null(ops, socket_setsockopt);
--- linux-2.6.orig/net/core/datagram.c 2007-08-23 21:25:04.000000000 +0900
+++ linux-2.6/net/core/datagram.c 2007-08-24 15:51:39.000000000 +0900
@@ -55,6 +55,7 @@
#include <net/checksum.h>
#include <net/sock.h>
#include <net/tcp_states.h>
+#include <linux/security.h>
/*
* Is a socket 'connection oriented' ?
@@ -178,6 +179,12 @@ struct sk_buff *skb_recv_datagram(struct
} else
skb = skb_dequeue(&sk->sk_receive_queue);
+ error = security_post_recv_datagram(sk, skb, flags);
+ if (error) {
+ skb_free_datagram(sk, skb);
+ goto no_packet;
+ }
+
if (skb)
return skb;
Hi!
> This patch allows administrators use conditional permission.
> TOMOYO Linux supports conditional permission based on
> process's UID,GID etc. and/or requested pathname's UID/GID.
>
> Signed-off-by: Kentaro Takeda <[email protected]>
> Signed-off-by: Tetsuo Handa <[email protected]>
> + * Since the trailing spaces are removed by tmy_normalize_line(),
> + * the last "\040if\040" sequence corresponds to condition part.
> + */
> +char *tmy_find_condition_part(char *data)
> +{
> + char *cp = strstr(data, " if ");
> + if (cp) {
> + char *cp2;
> + while ((cp2 = strstr(cp + 3, " if ")) != NULL)
> + cp = cp2;
> + *cp++ = '\0';
> + }
> + return cp;
> +}
...
> + unsigned long left_min = 0;
> + unsigned long left_max = 0;
> + unsigned long right_min = 0;
> + unsigned long right_max = 0;
> + if (strncmp(condition, "if ", 3))
> + return NULL;
> + condition += 3;
> + start = condition;
> + while (*condition) {
> + if (*condition == ' ')
> + condition++;
> + for (left = 0; left < MAX_KEYWORD; left++) {
> + if (strncmp(condition, cc_keyword[left].keyword,
> + cc_keyword[left].keyword_len))
> + continue;
> + condition += cc_keyword[left].keyword_len;
> + break;
> + }
> + if (left == MAX_KEYWORD) {
> + if (!tmy_parse_ulong(&left_min, &condition))
> + goto out;
> + counter++; /* body */
> + if (*condition != '-')
> + goto not_range1;
> + condition++;
> + if (!tmy_parse_ulong(&left_max, &condition)
> + || left_min > left_max)
> + goto out;
> + counter++; /* body */
> +not_range1: ;
> + }
> + if (strncmp(condition, "!=", 2) == 0)
> + condition += 2;
> + else if (*condition == '=')
> + condition++;
> + else
> + goto out;
> + counter++; /* header */
> + for (right = 0; right < MAX_KEYWORD; right++) {
> + if (strncmp(condition, cc_keyword[right].keyword,
> + cc_keyword[right].keyword_len))
> + continue;
> + condition += cc_keyword[right].keyword_len;
> + break;
> + }
What is that? Language parser in kernel?
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
Hi,
2007/8/25, Pavel Machek <[email protected]>:
> Hi!
>
> > This patch allows administrators use conditional permission.
> > TOMOYO Linux supports conditional permission based on
> > process's UID,GID etc. and/or requested pathname's UID/GID.
> >
> > Signed-off-by: Kentaro Takeda <[email protected]>
> > Signed-off-by: Tetsuo Handa <[email protected]>
>
> > + * Since the trailing spaces are removed by tmy_normalize_line(),
> > + * the last "\040if\040" sequence corresponds to condition part.
> > + */
> > +char *tmy_find_condition_part(char *data)
> > +{
> > + char *cp = strstr(data, " if ");
> > + if (cp) {
> > + char *cp2;
> > + while ((cp2 = strstr(cp + 3, " if ")) != NULL)
> > + cp = cp2;
> > + *cp++ = '\0';
> > + }
> > + return cp;
> > +}
> ...
>
> > + unsigned long left_min = 0;
> > + unsigned long left_max = 0;
> > + unsigned long right_min = 0;
> > + unsigned long right_max = 0;
> > + if (strncmp(condition, "if ", 3))
> > + return NULL;
> > + condition += 3;
> > + start = condition;
> > + while (*condition) {
> > + if (*condition == ' ')
> > + condition++;
> > + for (left = 0; left < MAX_KEYWORD; left++) {
> > + if (strncmp(condition, cc_keyword[left].keyword,
> > + cc_keyword[left].keyword_len))
> > + continue;
> > + condition += cc_keyword[left].keyword_len;
> > + break;
> > + }
> > + if (left == MAX_KEYWORD) {
> > + if (!tmy_parse_ulong(&left_min, &condition))
> > + goto out;
> > + counter++; /* body */
> > + if (*condition != '-')
> > + goto not_range1;
> > + condition++;
> > + if (!tmy_parse_ulong(&left_max, &condition)
> > + || left_min > left_max)
> > + goto out;
> > + counter++; /* body */
> > +not_range1: ;
> > + }
> > + if (strncmp(condition, "!=", 2) == 0)
> > + condition += 2;
> > + else if (*condition == '=')
> > + condition++;
> > + else
> > + goto out;
> > + counter++; /* header */
> > + for (right = 0; right < MAX_KEYWORD; right++) {
> > + if (strncmp(condition, cc_keyword[right].keyword,
> > + cc_keyword[right].keyword_len))
> > + continue;
> > + condition += cc_keyword[right].keyword_len;
> > + break;
> > + }
>
> What is that? Language parser in kernel?
>
> Pavel
Key idea of TOMOYO Linux is to let each process to remember the program
(path) name. Names are stored in task struct and "appended" to the list when
execve is called.
An example of /usr/lib/cups/backend/lpd.
(picked up from
http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/centos4.4/domain_policy.txt?v=policy-sample)
/etc/rc.d/init.d/cups (fork&exec)
/sbin/initlog (fork&exec)
/usr/sbin/cupsd (fork&exec)
/bin/bash (fork&exec)
/usr/lib/cups/backend/lpd (current process)
SELinux and other DTE implementations need domain definitions to work.
It is administrators task to design domains and name each domains.
TOMOYO Linux can be used as DTE MAC, but administrators don't
have to define and name domains. Because TOMOYO Linux
automatically defines domains and name them (from booting to
shutdown).
I wrote "TOMOYO Linux can be used as MAC", because
users can just view the domain transitions and analyze systems
with TOMOYO Linux. Or they can use TOMOYO Linux to
get logs with process invocation histories instead of a simple
program name.
TOMOYO Linux policy consists of path names and they are currently
handled as strings.
Thanks.
--
Toshiharu Harada
NTT DATA CORPORATION
Hello.
Pavel Machek wrote:
> What is that? Language parser in kernel?
Yes. This is a policy parser in kernel.
TOMOYO Linux' policy is passed from/to the kernel as a plain text
(i.e. ASCII printable) file via /proc/tomoyo interface.
For example, to add a permission to allow /usr/sbin/sshd
to execute /bin/bash if the authenticated user's uid = 500,
the administrator runs
# /bin/cat > /proc/tomoyo/domain_policy << EOF
select <kernel> /usr/sbin/sshd
1 /bin/bash if task.uid=500
EOF
and to remove this permission, the administrator runs
# /bin/cat > /proc/tomoyo/domain_policy << EOF
select <kernel> /usr/sbin/sshd
delete 1 /bin/bash if task.uid=500
EOF
The patch [TOMOYO 14/15] handles "if task.uid=500" part.
No compilation at userspace and
only difference between old and new policy is written.
This is similar to LDAP manipulation using LDIF format.
(To be exact, only programs that are registered in
/proc/tomoyo/manager can modify policy via /proc/tomoyo interface.
You need to use /usr/lib/ccs/loadpolicy or something
instead of /bin/cat .)
On Aug 25, 2007, at 22:13:48, Tetsuo Handa wrote:
> Pavel Machek wrote:
>> What is that? Language parser in kernel?
>
> Yes. This is a policy parser in kernel.
>
> TOMOYO Linux' policy is passed from/to the kernel as a plain text
> (i.e. ASCII printable) file via /proc/tomoyo interface.
>
> For example, to add a permission to allow /usr/sbin/sshd to
> execute /bin/bash if the authenticated user's uid = 500, the
> administrator runs
>
> # /bin/cat > /proc/tomoyo/domain_policy << EOF
> select <kernel> /usr/sbin/sshd
> 1 /bin/bash if task.uid=500
> EOF
This is probably not acceptable; I doubt there's a chance in hell
that TOMOYO will get merged as long as it has text-based-language
parsing in the kernel. You also have $NEW_RANDOM_ABUSE_OF_PROCFS and
$PATH_BASED_LSM_ISSUES. See the long flamewars on AppArmor for
discussion on the latter.
Cheers,
Kyle Moffett
On Friday, August 24 2007 8:58:28 am Kentaro Takeda wrote:
> LSM hooks for network accept and recv:
> * socket_post_accept is modified to return int.
This has been discussed several times on various lists and is not considered
an acceptable solution to blocking incoming stream connection attempts.
Please take a look at the existing LSM stream connection request hooks as
well as how SELinux makes use of them.
> * post_recv_datagram is added in skb_recv_datagram.
Can you explain to me why this is not possible using the existing
security_socket_sock_rcv_skb() LSM hook?
--
paul moore
linux security @ hp
Hello.
Paul Moore wrote:
> > * post_recv_datagram is added in skb_recv_datagram.
>
> Can you explain to me why this is not possible using the existing
> security_socket_sock_rcv_skb() LSM hook?
socket_sock_rcv_skb() is a hook for enqueue time.
I want a hook for dequeue time, because what TOMOYO Linux is doing is
not "whether a socket created by foo is permitted to pick up
an incoming packet from specific address/port"
but "whether bar is permitted to pick up
an incoming packet from specific address/port".
At the time of enqueue, I can't know who will pick up that packet.
Same reason for socket_post_accept(). What TOMOYO Linux is doing is
not "whether a socket created by foo is permitted to accept
a connection request from specific address/port"
but "whether bar is permitted to accept
a connection request from specific address/port".
At the time of enqueue, I can't know who will pick up that request.
Hello.
Kyle Moffett wrote:
> This is probably not acceptable; I doubt there's a chance in hell
> that TOMOYO will get merged as long as it has text-based-language
> parsing in the kernel. You also have $NEW_RANDOM_ABUSE_OF_PROCFS and
> $PATH_BASED_LSM_ISSUES. See the long flamewars on AppArmor for
> discussion on the latter.
Thank you for your comment.
Regarding /proc/tomoyo , this is not a big problem.
It will be possible to create a dedicated filesystem
after all other remaining problems are solved.
Regarding text-based-language parsing in the kernel,
it might be possible to use compiled policy.
So, let me explain pitfalls of path based access controls and
how TOMOYO Linux avoids these pitfalls.
Since problems of pathname based access control is scattered around,
I can't find all problems. But as far as I can see, it seems to me that
they are confusing userland-specific problems and kernel-specific problems
/ white-listing approach and black-listing approach.
Q1: Not all objects have pathnames!
A1: What TOMOYO Linux does is use pathnames
that are accessible via filesystem's mount tree.
TOMOYO Linux does not deny labeling on objects
that are not accessible via filesystem's mount tree.
Although it is not implemented yet; it's a future work.
I need to reduce my burden of catching up with the latest kernels.
Q2: Pathname based access control is bypassed by creating hard links!
Suppose an administrator wants to forbid access to /etc/shadow .
Regarding pathname based access control, one can access to /etc/shadow
by creating hardlinks to /tmp/shadow . Regarding label based access control,
one can't access to /etc/shadow by creating hardlinks to /tmp/shadow
because /tmp/shadow keeps the same label with /etc/shadow .
A2: Do you think you need to allow creating hardlinks of /etc/shadow
to /tmp/shadow ? I don't think so.
Such operation is not needed for systems to operate properly.
Thus, TOMOYO Linux forbids creating hardlinks of /etc/shadow
to /tmp/shadow from the beginning.
TOMOYO Linux handles pathnames carefully.
TOMOYO Linux checks for both link-source and link-target together.
One can't do "ln /etc/shadow /tmp/shadow" unless the administrator
explicitly give "allow_link /etc/shadow /tmp/shadow" permission.
The essential combinations of link-source and link-target are not infinite
and TOMOYO Linux allows only essential combinations to minimize the risk of
bypassing pathname based access control.
Please remember, TOMOYO Linux is white-listing and system-wide
access control.
It's black-listing's approach that denies access to /etc/shadow
but allows access to /tmp/shadow .
White-listing approach will not allow access to
/tmp/shadow unless explicitly given.
TOMOYO Linux can apply access control including login session.
One can't do "ln /etc/shadow /tmp/shadow" unless the administrator
explicitly give "allow_link /etc/shadow /tmp/shadow" permission.
Q3: Pathname based access control is bypassed by renaming files!
Suppose an administrator wants to forbid access to /etc/shadow .
Regarding pathname based access control, one can access to /etc/shadow
by renaming to /tmp/shadow . Regarding label based access control,
one can't access to /etc/shadow by renaming to /tmp/shadow
because /tmp/shadow keeps the same label as /etc/shadow .
A3: Do you think you need to allow renaming /etc/shadow to /tmp/shadow ?
I don't think so.
Such operation is not needed for systems to operate properly.
Thus, TOMOYO Linux forbids renaming /etc/shadow to /tmp/shadow
from the beginning.
TOMOYO Linux checks for both old-name and new-name together.
One can't do "mv /etc/shadow /tmp/shadow" unless the administrator
explicitly give "allow_rename /etc/shadow /tmp/shadow" permission.
The essential combinations of old-name and new-name are not infinite
and TOMOYO Linux allows only essential combinations to minimize the risk of
bypassing pathname based access control.
You might worry that /etc/shadow can be renamed to /tmp/shadow between
the moment the kernel got "dentry" and "vfsmount" from /etc/shadow and
the moment TOMOYO Linux obtains a pathname from "dentry" and "vfsmount".
Yes, the possibility is not zero.
But TOMOYO Linux is white-listing and system-wide access control.
TOMOYO Linux allows only essential combinations of renaming requests.
Thus, renaming /etc/shadow to /tmp/shadow will not happen
unless the administrator explicitly give
"allow_rename /etc/shadow /tmp/shadow" permission.
Q4: Pathname based access control is bypassed by creating symbolic links!
Suppose an administrator wants to forbid access to /etc/shadow .
Regarding pathname based access control, one can access to /etc/shadow
by creating symbolic links from /tmp/shadow to /etc/shadow .
A4: This is because you are using pathnames before resolving symbolic links
for access control, and this is a problem of performing access control
at userland level.
What TOMOYO Linux deals is performing access control at kernel level.
In the kernel, a pathname is converted into "dentry" and "vfsmount".
And one can get a pathname without symbolic links, ".", "..", "//"
by traversing "dentry" and "vfsmount" upward.
Thus, even if one requests access to /tmp/shadow , TOMOYO Linux will think
that /etc/shadow is requested.
Therefore, TOMOYO Linux will not allow access to /etc/shadow
using a symbolic link /tmp/shadow .
Q5: Pathname based access control is poor at handling random names.
Suppose an administrator wants to perform access control for temporary files
created in /tmp/ directory. Since temporary files has random names,
the administrator has to grant very wide access permission like /tmp/* .
A5: Do you think the administrator has to give such a wide permission?
I don't think so.
Most temporary files has some prefix.
Thus, the administrator seldom need to grant whole permission like /tmp/* .
If there is no prefix, it is easy to modify the source code to use prefix.
Also, you will be able to switch the location of temporary files
using environment variables like TMP/TEMP and/or config files of application
to e.g. ~/tmp/ directory.
Q6: Pathname based access control is bypassed by namespace manipulation!
a) One can change the location of a file using mount() operation.
b) One can change the location of a file using pivot_root() operation.
c) Each process can have different namespace.
d) Process can access files outside chroot jail
if chroot() operation failed.
A6: Not so regarding TOMOYO Linux.
a) TOMOYO Linux checks combination of mount_device, mount_point, filesystems
for mount operation. This is what [TOMOYO 11/15] is doing.
Thus, one can't do "mount --bind /etc/ /tmp/" unless the administrator
explicitly give "allow_mount /etc/ /tmp/ --bind 0" permission.
b) TOMOYO Linux checks combination of old_root and new_root
for pivot_root() operation (although pivot_root() is seldom used
after /sbin/init starts).
Thus, one can't do "pivot_root / /tmp/" unless the administrator
explicitly give "allow_pivot_root / /tmp/" permission.
c) Even if each process can have different namespace,
it is restricted by mount()/pivot_root() checking.
Thus, one can't freely create his/her own namespace.
d) TOMOYO Linux uses absolute pathname that is not affected by chroot()
(this is what [TOMOYO 04/15] is doing).
Pathnames derived by traversing upward up to the root of process's
namespace are used in TOMOYO Linux. It is not a process's root directory.
Thus, permissions are given using pathnames starting from outside
the chroot jail.
Q7: Pathnames don't contain owner's information.
A7: Yes, it is true. But there is no rule that pathname-based access control
must not use anything but pathname.
In fact, TOMOYO Linux allows the administrator use inode's uid/gid/ino
information in addition to pathnames (this is what [TOMOYO 14/15] is doing).
Q8: LSM hook functions are not receiving vfsmount parameter!
A8: Exactly. It is a painful thing for TOMOYO Linux too.
[TOMOYO 13/15] scans namespace list for matching vfsmount for given dentry.
Since vfsmount is not added to LSM hook functions yet, the development of
TOMOYO Linux 1.5.x (non-LSM version of TOMOYO Linux which can coexist with
SELinux and AppArmor) has been continued.
http://tomoyo.sourceforge.jp/en/1.5.x/
I'll be offline for days because of summer vacation.
Therefore, I can't respond soon. Please be patient.
Regards.
On Tuesday, August 28 2007 6:39:13 am Tetsuo Handa wrote:
> Hello.
Hello.
> Paul Moore wrote:
> > > * post_recv_datagram is added in skb_recv_datagram.
> >
> > Can you explain to me why this is not possible using the existing
> > security_socket_sock_rcv_skb() LSM hook?
>
> socket_sock_rcv_skb() is a hook for enqueue time.
> I want a hook for dequeue time, because what TOMOYO Linux is doing is
> not "whether a socket created by foo is permitted to pick up
> an incoming packet from specific address/port"
> but "whether bar is permitted to pick up
> an incoming packet from specific address/port".
> At the time of enqueue, I can't know who will pick up that packet.
>
> Same reason for socket_post_accept(). What TOMOYO Linux is doing is
> not "whether a socket created by foo is permitted to accept
> a connection request from specific address/port"
> but "whether bar is permitted to accept
> a connection request from specific address/port".
> At the time of enqueue, I can't know who will pick up that request.
I apologize for not recognizing your approach from our earlier discussion on
the LSM mailing list in July. Unfortunately, I have the same objections to
these changes that I did back then and from what I can recall of the
discussion the rest of the kernel networking community agreed that these
changes are not the preferred way of solving this problem. We offered
suggestions on how to accomplish your goals in a way that would be acceptable
upstream and I would encourage you to investigate those options further.
--
paul moore
linux security @ hp
Hello.
Paul Moore wrote:
> I apologize for not recognizing your approach from our earlier discussion on
> the LSM mailing list in July. Unfortunately, I have the same objections to
> these changes that I did back then and from what I can recall of the
> discussion the rest of the kernel networking community agreed that these
> changes are not the preferred way of solving this problem. We offered
> suggestions on how to accomplish your goals in a way that would be acceptable
> upstream and I would encourage you to investigate those options further.
When I proposed a patch in July, I was patching at post-copy_to_user() step
(i.e. after sock_recvmsg()).
This approach messed up user-supplied buffer.
This time, I'm patching at pre-copy_to_user() step
(i.e. at skb_recv_datagram()).
This approach doesn't mess up user-supplied buffer.
I think this is a cleaner way than the previous patch.
Although read() gets an error when select() said "read ready",
I can't find other place to use for accomplishing my goals.
By the way, similar thing can happen when select() against
a file descriptor said "read ready" but read() gets an error
if security policy or security-id of the file has changed
between select() and read(), isn't it?
And such behavior is acceptable, isn't it?
If such behavior can happen and is acceptable and *preferable*,
I think checking permission at dequeue time (i.e. skb_recv_datagram())
is *preferable* way than checking permission at enqueue time
(i.e. socket_sock_rcv_skb()).
Regards.
On Monday 03 September 2007 9:15:27 am Tetsuo Handa wrote:
> Hello.
Hi.
> Paul Moore wrote:
> > I apologize for not recognizing your approach from our earlier discussion
> > on the LSM mailing list in July. Unfortunately, I have the same
> > objections to these changes that I did back then and from what I can
> > recall of the discussion the rest of the kernel networking community
> > agreed that these changes are not the preferred way of solving this
> > problem. We offered suggestions on how to accomplish your goals in a way
> > that would be acceptable upstream and I would encourage you to
> > investigate those options further.
>
> When I proposed a patch in July, I was patching at post-copy_to_user() step
> (i.e. after sock_recvmsg()).
> This approach messed up user-supplied buffer.
>
> This time, I'm patching at pre-copy_to_user() step
> (i.e. at skb_recv_datagram()).
> This approach doesn't mess up user-supplied buffer.
> I think this is a cleaner way than the previous patch.
It might be cleaner than your previous patch but it ignores some of the more
important points that were brought up by the community in previous
discussions.
> Although read() gets an error when select() said "read ready",
> I can't find other place to use for accomplishing my goals.
>
> By the way, similar thing can happen when select() against
> a file descriptor said "read ready" but read() gets an error
> if security policy or security-id of the file has changed
> between select() and read(), isn't it?
> And such behavior is acceptable, isn't it?
> If such behavior can happen and is acceptable and *preferable*,
> I think checking permission at dequeue time (i.e. skb_recv_datagram())
> is *preferable* way than checking permission at enqueue time
> (i.e. socket_sock_rcv_skb()).
As mentioned before that problem with enforcing access control at "dequeue"
time is that the system has already accepted the packet from the remote
host - how do you plan to deal with these unacceptable/bad packets sitting on
a socket, waiting to be read by a process which does not have access to them?
What about connection oriented sockets where the remote host will assume
everything is okay and continue blasting the machine with more, illegal
packets? If you aren't going to allow the socket/application to read the
packet, never allow the system to accept it in the first place.
The *preferable* solution, as previously stated before by several people, is
to investigate other access control methods like the netfilter userspace
queue mechanism. At the very least, please explain how the approaches we
proposed will not accomplish what you require.
--
paul moore
linux security @ hp
Hello.
Thank you for your comment.
Hmm, I can't understand why I have to perform access control at "enqueue" time.
What I want to do is
allow process1 receive UDP packets from 10.0.0.1 port 1024
allow process2 receive UDP packets from 10.0.0.2 port 2048
when there is no guarantee that process1 and process2 are not sharing a socket.
If there is guarantee that process1 and process2 are not sharing a socket,
I can do it using netfilter.
My case is that process1 and process2 might share a socket
but allow administrator give process1 and process2 different region of interests.
> As mentioned before that problem with enforcing access control at "dequeue"
> time is that the system has already accepted the packet from the remote
> host - how do you plan to deal with these unacceptable/bad packets sitting on
> a socket, waiting to be read by a process which does not have access to them?
Just drop unacceptable/bad packet when a process attempted to pick up a packet
that is not region of interest for the process;
with a price of being unable to pick up packets that are region of interest
for other process that are sharing that socket.
But processes that administrator gives different region of interests
seldom shares a socket, the administrator seldom pays this price.
> What about connection oriented sockets where the remote host will assume
> everything is okay and continue blasting the machine with more, illegal
> packets? If you aren't going to allow the socket/application to read the
> packet, never allow the system to accept it in the first place.
I don't plan to drop connection oriented socket's packets at skb_recv_datagram().
TOMOYO Linux drops UDP or RAW packet at skb_recv_datagram()
and disconnects TCP connection at socket_post_accept().
In both cases, no uninterested data is picked up by userland process.
Sockets deliver packets between two host's kernel space,
but have no concern about delivered packets has been picked up.
It is sender process's business to verify that
receiver process has successfully picked up packets
which sender process has sent.
> The *preferable* solution, as previously stated before by several people, is
> to investigate other access control methods like the netfilter userspace
> queue mechanism. At the very least, please explain how the approaches we
> proposed will not accomplish what you require.
According to Patrick McHardy's posting at
http://www.mail-archive.com/[email protected]/msg00999.html
netfilter *socket filters* cannot know final recipient process.
Can netfilter *userspace queue mechanism* know final recipient process?
Regards.
On Sep 04, 2007, at 10:02:46, Tetsuo Handa wrote:
> Hmm, I can't understand why I have to perform access control at
> "enqueue" time.
>
> What I want to do is
> allow process1 receive UDP packets from 10.0.0.1 port 1024
> allow process2 receive UDP packets from 10.0.0.2 port 2048
> when there is no guarantee that process1 and process2 are not
> sharing a socket.
>
> If there is guarantee that process1 and process2 are not sharing a
> socket,
> I can do it using netfilter.
Well, we used to be able to do that with netfilter but it had the
same unfixable race conditions that you are presently running into
and so such support was dropped by the netfilter folks. I suspect if
you CC'ed [email protected] you would get some very precise
reasons why such filtering doesn't work.
Cheers,
Kyle Moffett
On Tuesday 04 September 2007 10:02:46 am Tetsuo Handa wrote:
> According to Patrick McHardy's posting at
> http://www.mail-archive.com/[email protected]/msg00999.
>html netfilter *socket filters* cannot know final recipient process.
> Can netfilter *userspace queue mechanism* know final recipient process?
Okay, I just went back and re-read the conversation from July as well as the
description of your current patches and I think what is basically comes down
to is that your design makes use of userspace intervention to allow/reject
connections and/or packets based on the application's domain. Unfortunately
for TOMOYO, the current LSM network hooks are placed in such a way that you
can not block and query a userspace agent for an access control decision.
Myself and some others have suggested using the netfilter userspace queue
mechanism[1]. However, I understand this may cause you problems when you try
to determine the incoming packet's destination/domain. With these
requirements I understand why you are pushing so hard to introduce these new
LSM hooks, but for many reasons I would really prefer to try and find a way
to utilize the existing hooks. I've tried to think of a way to do this over
the past day and have not been able to arrive at a clean solution.
Personally, I still question the wisdom of receiving a packet/connection only
to drop/reject it later when an application tries to read it but I might be
the only one.
Based on some of the other discussion around this patch there appears to be
other, larger issues which you still need to sort out (language parser in the
kernel, /proc issues, etc.). I would recommend addressing those concerns and
including the netdev mailing list on your next patchset as they might have
some thoughts on your network design.
[1]http://www.netfilter.org/projects/libnetfilter_queue/index.html
--
paul moore
linux security @ hp
Hello.
Thank you very much for your time, Paul.
Yes, you understood what I wanted to do.
TOMOYO Linux's approach:
(1) It uses userspace intervention to allow/reject
connections and/or packets based on the application's domain.
Since existent hooks can't be used for this purpose,
I inserted a new hook post_recv_datagram() at skb_recv_datagram()
and I modified socket_post_accept() to return error so that
I can drop/disconnect based on the application's domain.
I think skb_recv_datagram() is the only place that can remove
a message picked up with MSG_PEEK flags from the receive queue.
To remove a message picked up with MSG_PEEK flags, I noticed that
I have to do skb_kill_datagram()-like operation so that
"the head message that must not be delivered to the caller" won't prevent
picking up of "the non-head message that should be delivered to the caller"
when the caller repeats only recv(MSG_PEEK) requests.
Since skb_recv_datagram() can be called from interrupt context,
I have to use spin_lock_irqsave() instead for spin_lock_bh(), am I right?
/* from net/core/datagram.c */
@@ -178,6 +179,27 @@ struct sk_buff *skb_recv_datagram(struct
} else
skb = skb_dequeue(&sk->sk_receive_queue);
+ error = security_post_recv_datagram(sk, skb, flags);
+ if (error) {
+ unsigned long cpu_flags;
+
+ if (!(flags & MSG_PEEK))
+ goto no_peek;
+
+ spin_lock_irqsave(&sk->sk_receive_queue.lock,
+ cpu_flags);
+ if (skb == skb_peek(&sk->sk_receive_queue)) {
+ __skb_unlink(skb,
+ &sk->sk_receive_queue);
+ atomic_dec(&skb->users);
+ }
+ spin_unlock_irqrestore(&sk->sk_receive_queue.lock,
+ cpu_flags);
+no_peek:
+ skb_free_datagram(sk, skb);
+ goto no_packet;
+ }
+
if (skb)
return skb;
By the way, why can't socket_post_accept() fail?
Someone may wish to do memory allocation at socket_post_accept().
socket_accept() is too early for memory allocation because
there is no chance to free allocated memory
when sock->ops->accept() failed.
I think socket_post_accept() should be able to fail.
(2) It allows the administrator judge interactively
using a userspace agent.
Thus, the new hook has to be inserted at blockable location,
Since skb_recv_datagram() can be called from interrupt context,
I do nothing in post_recv_datagram() if called from interrupt context.
+static int tmy_post_recv_datagram(struct sock *sk,
+ struct sk_buff *skb,
+ unsigned int flags)
+{
+ int error = 0;
+ const unsigned int type = sk->sk_type;
+
+ /* skb_recv_datagram() didn't dequeue. */
+ if (!skb)
+ return 0;
+
+ /* skb_recv_datagram() can be called from interrupt context. */
+ if (in_interrupt())
+ return 0;
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if (type != SOCK_DGRAM && type != SOCK_RAW)
+ return 0;
...(sniped)...
+}
Regards.
On Thursday, September 6 2007 9:04:01 am Tetsuo Handa wrote:
> (1) It uses userspace intervention to allow/reject
> connections and/or packets based on the application's domain.
> Since existent hooks can't be used for this purpose,
> I inserted a new hook post_recv_datagram() at skb_recv_datagram()
> and I modified socket_post_accept() to return error so that
> I can drop/disconnect based on the application's domain.
>
> I think skb_recv_datagram() is the only place that can remove
> a message picked up with MSG_PEEK flags from the receive queue.
> To remove a message picked up with MSG_PEEK flags, I noticed that
> I have to do skb_kill_datagram()-like operation so that
> "the head message that must not be delivered to the caller" won't
> prevent picking up of "the non-head message that should be delivered to the
> caller" when the caller repeats only recv(MSG_PEEK) requests.
> Since skb_recv_datagram() can be called from interrupt context,
> I have to use spin_lock_irqsave() instead for spin_lock_bh(), am I
> right?
There are almost certainly better people to answer locking questions, but here
is my take on it ... If you are accessing data both in a bottom half and
elsewhere you need to make sure you disable bottom halfs from running before
you access the data outside the bottom half (spin_lock_bh()). If you are
accessing data both in an interrupt handler and elsewhere you need to make
sure you disable interrupts when accessing data outside the irq handler
(spin_lock_irqsave()).
> By the way, why can't socket_post_accept() fail?
> Someone may wish to do memory allocation at socket_post_accept().
> socket_accept() is too early for memory allocation because
> there is no chance to free allocated memory
> when sock->ops->accept() failed.
> I think socket_post_accept() should be able to fail.
>From my experience the community disapproves of approaches which go through
the entire TCP handshake and then terminate the connection, which is what
allowing security_socket_post_accept() to fail would do.
--
paul moore
linux security @ hp