[TOMOYO #4 11/13] LSM adapter for TOMOYO.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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 |  600 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 600 insertions(+)

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/tomoyo.c	2007-10-11 16:00:02.000000000 +0900
@@ -0,0 +1,600 @@
+/*
+ * 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/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. */
+
+LIST_HEAD(domain_list);
+
+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_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 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,
+};
+
+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");
+
+	INIT_LIST_HEAD(&KERNEL_DOMAIN.list);
+	INIT_LIST_HEAD(&KERNEL_DOMAIN.acl_info_list);
+	KERNEL_DOMAIN.domainname = tmy_save_name(TMY_ROOT_NAME);
+	mb(); /* Avoid out-of-order execution. */
+	/* RCU: No protection is needed here. */
+	list_add_tail_rcu(&KERNEL_DOMAIN.list, &domain_list);
+
+	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);

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux