[PATCH][RFC] Simple tamper-proof device filesystem.

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

 



Hello.

Thank you for attending discussion for previous posting
(starting from http://lkml.org/lkml/2007/12/16/23 ).

The previous posting was for feasibility test to know
whether this kind of trivial filesystem is acceptable for mainline.

Now, it seems that there is a little chance for accepting.
Therefore I rebased the patch using the -mm tree.

Regards.
----------
Subject: Simple tamper-proof device filesystem.

The goal of this filesystem is to guarantee that
"applications using well-known device locations under /dev
get the device they want" (e.g. an application that accesses /dev/null can
always get a character special device with major=1 and minor=3).

This idea sounds silly? Indeed, if you think the root can do whatever
he/she wants do do. But this filesystem makes sense when used with
access control mechanisms like MAC (mandatory access control).
I want to use this filesystem in case where a process with root privilege was
hijacked but the behavior of the hijacked process is still restricted by MAC.

Why not use FUSE?

  Because /dev has to be available through the lifetime of the kernel.
  It is not acceptable if /dev stops working due to SIGKILL or OOM-killer.

Why not use SELinux?

  Because SELinux doesn't guarantee filename and its attribute.
  As far as I know, no MAC implementation can handle filename and its attribute.
  I guess this is because

    Filename and its attributes pairs are conventionally considered as
    constant and reliable.

    It makes the MAC's policy syntax complicated to describe this attribute
    enforcement information in MAC's policy.

  I want to add functionality that the MACs are missing.
  Instead of adding this functionality per MAC,
  I propose to add it as ground work, to be combined with any MAC.

Why not drop CAP_MKNOD?

  Dropping CAP_MKNOD is not enough for emulating this filesystem because
  a process can still rename()/unlink() to break filename and its attributes
  handling (e.g. mv /dev/sda1 /dev/sda1.tmp; mv /dev/sda2 /dev/sda1;
  mv /dev/sda1.tmp /dev/sda2 or unlink /dev/null; touch /dev/null ).

This time, I'm implementing this filesystem as an extension to tmpfs
because what this filesystem does are nothing but check filename and
its attributes in addition to what tmpfs does.

Signed-off-by: Tetsuo Handa <[email protected]>
---
 fs/ramfs/inode.c   |  101 ++++-
 fs/ramfs/syaoran.h | 1066 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1160 insertions(+), 7 deletions(-)

--- linux-2.6-mm.orig/fs/ramfs/inode.c
+++ linux-2.6-mm/fs/ramfs/inode.c
@@ -35,6 +35,7 @@
 #include <linux/sched.h>
 #include <asm/uaccess.h>
 #include "internal.h"
+#include "syaoran.h"
 
 /* some random number */
 #define RAMFS_MAGIC	0x858458f6
@@ -49,7 +50,8 @@ static struct backing_dev_info ramfs_bac
 			  BDI_CAP_READ_MAP | BDI_CAP_WRITE_MAP | BDI_CAP_EXEC_MAP,
 };
 
-struct inode *ramfs_get_inode(struct super_block *sb, int mode, dev_t dev)
+struct inode *__ramfs_get_inode(struct super_block *sb, int mode, dev_t dev,
+				const int mac)
 {
 	struct inode * inode = new_inode(sb);
 
@@ -65,10 +67,19 @@ struct inode *ramfs_get_inode(struct sup
 		switch (mode & S_IFMT) {
 		default:
 			init_special_inode(inode, mode, dev);
+			if (mac) {
+				if (S_ISBLK(mode))
+					inode->i_fop = &wrapped_def_blk_fops;
+				else if (S_ISCHR(mode))
+					inode->i_fop = &wrapped_def_chr_fops;
+				inode->i_op = &syaoran_file_inode_operations;
+			}
 			break;
 		case S_IFREG:
 			inode->i_op = &ramfs_file_inode_operations;
 			inode->i_fop = &ramfs_file_operations;
+			if (mac)
+				inode->i_op = &syaoran_file_inode_operations;
 			break;
 		case S_IFDIR:
 			inode->i_op = &ramfs_dir_inode_operations;
@@ -79,12 +90,19 @@ struct inode *ramfs_get_inode(struct sup
 			break;
 		case S_IFLNK:
 			inode->i_op = &page_symlink_inode_operations;
+			if (mac)
+				inode->i_op = &syaoran_symlink_inode_operations;
 			break;
 		}
 	}
 	return inode;
 }
 
+struct inode *ramfs_get_inode(struct super_block *sb, int mode, dev_t dev)
+{
+	return __ramfs_get_inode(sb, mode, dev, 0);
+}
+
 /*
  * File creation. Allocate an inode, and we're done..
  */
@@ -92,9 +110,17 @@ struct inode *ramfs_get_inode(struct sup
 static int
 ramfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
 {
-	struct inode * inode = ramfs_get_inode(dir->i_sb, mode, dev);
+	struct inode *inode;
 	int error = -ENOSPC;
 
+	/*** SYAORAN start. ***/
+	if (dir->i_sb->s_op == &syaoran_ops) {
+		if (syaoran_may_create_node(dentry, mode, dev) < 0)
+			return -EPERM;
+		inode = syaoran_get_inode(dir->i_sb, mode, dev);
+	/*** SYAORAN end. ***/
+	} else
+		inode = ramfs_get_inode(dir->i_sb, mode, dev);
 	if (inode) {
 		if (dir->i_mode & S_ISGID) {
 			inode->i_gid = dir->i_gid;
@@ -127,7 +153,14 @@ static int ramfs_symlink(struct inode * 
 	struct inode *inode;
 	int error = -ENOSPC;
 
-	inode = ramfs_get_inode(dir->i_sb, S_IFLNK|S_IRWXUGO, 0);
+	/*** SYAORAN start. ***/
+	if (dir->i_sb->s_op == &syaoran_ops) {
+		if (syaoran_may_create_node(dentry, S_IFLNK, 0) < 0)
+			return -EPERM;
+		inode = syaoran_get_inode(dir->i_sb, S_IFLNK|S_IRWXUGO, 0);
+	/*** SYAORAN end. ***/
+	} else
+		inode = ramfs_get_inode(dir->i_sb, S_IFLNK|S_IRWXUGO, 0);
 	if (inode) {
 		int l = strlen(symname)+1;
 		error = page_symlink(inode, symname, l);
@@ -146,13 +179,14 @@ static int ramfs_symlink(struct inode * 
 static const struct inode_operations ramfs_dir_inode_operations = {
 	.create		= ramfs_create,
 	.lookup		= simple_lookup,
-	.link		= simple_link,
-	.unlink		= simple_unlink,
+	.link		= ramfs_link,
+	.unlink		= ramfs_unlink,
 	.symlink	= ramfs_symlink,
 	.mkdir		= ramfs_mkdir,
-	.rmdir		= simple_rmdir,
+	.rmdir		= ramfs_rmdir,
 	.mknod		= ramfs_mknod,
-	.rename		= simple_rename,
+	.rename		= ramfs_rename,
+	.setattr        = ramfs_setattr,
 };
 
 static const struct super_operations ramfs_ops = {
@@ -184,6 +218,35 @@ static int ramfs_fill_super(struct super
 	return 0;
 }
 
+static int syaoran_fill_super(struct super_block *sb, void *data, int silent)
+{
+	struct inode *inode;
+	struct dentry *root;
+	int error;
+
+	sb->s_maxbytes = MAX_LFS_FILESIZE;
+	sb->s_blocksize = PAGE_CACHE_SIZE;
+	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
+	sb->s_magic = SYAORAN_MAGIC;
+	sb->s_op = &syaoran_ops;
+	sb->s_time_gran = 1;
+	error = syaoran_initialize(sb, data);
+	if (error < 0)
+		return error;
+	inode = syaoran_get_inode(sb, S_IFDIR | 0755, 0);
+	if (!inode)
+		return -ENOMEM;
+
+	root = d_alloc_root(inode);
+	if (!root) {
+		iput(inode);
+		return -ENOMEM;
+	}
+	sb->s_root = root;
+	syaoran_make_initial_nodes(sb);
+	return 0;
+}
+
 int ramfs_get_sb(struct file_system_type *fs_type,
 	int flags, const char *dev_name, void *data, struct vfsmount *mnt)
 {
@@ -197,6 +260,13 @@ static int rootfs_get_sb(struct file_sys
 			    mnt);
 }
 
+static int syaoran_get_sb(struct file_system_type *fs_type, int flags,
+			  const char *dev_name, void *data,
+			  struct vfsmount *mnt)
+{
+	return get_sb_nodev(fs_type, flags, data, syaoran_fill_super, mnt);
+}
+
 static struct file_system_type ramfs_fs_type = {
 	.name		= "ramfs",
 	.get_sb		= ramfs_get_sb,
@@ -207,6 +277,11 @@ static struct file_system_type rootfs_fs
 	.get_sb		= rootfs_get_sb,
 	.kill_sb	= kill_litter_super,
 };
+static struct file_system_type syaoran_fs_type = {
+	.name           = "syaoran",
+	.get_sb         = syaoran_get_sb,
+	.kill_sb        = kill_litter_super,
+};
 
 static int __init init_ramfs_fs(void)
 {
@@ -237,3 +312,15 @@ int __init init_rootfs(void)
 }
 
 MODULE_LICENSE("GPL");
+
+static int __init init_syaoran_fs(void)
+{
+	return register_filesystem(&syaoran_fs_type);
+}
+
+static void __exit exit_syaoran_fs(void)
+{
+	unregister_filesystem(&syaoran_fs_type);
+}
+module_init(init_syaoran_fs);
+module_exit(exit_syaoran_fs);
--- /dev/null
+++ linux-2.6-mm/fs/ramfs/syaoran.h
@@ -0,0 +1,1066 @@
+/*
+ * fs/ramfs/syaoran.h
+ *
+ * Implementation of the Tamper-Proof Device Filesystem.
+ *
+ * Copyright (C) 2005-2007  NTT DATA CORPORATION
+ *
+ * Version: 1.5.3-pre   2007/12/23
+ */
+
+#include <linux/namei.h>
+#include <linux/mm.h>
+#include <linux/quotaops.h>
+
+#define list_for_each_cookie(pos, cookie, head) \
+	for ((cookie) || ((cookie) = (head)), pos = (cookie)->next; \
+		prefetch(pos->next), pos != (head) || ((cookie) = NULL); \
+		(cookie) = pos, pos = pos->next)
+
+/* The following constants are used to restrict operations.*/
+
+#define MAY_CREATE          1 /* This file is allowed to be mknod()ed.        */
+#define MAY_DELETE          2 /* This file is allowed to be unlink()ed.       */
+#define MAY_CHMOD           4 /* This file is allowed to be chmod()ed.        */
+#define MAY_CHOWN           8 /* This file is allowed to be chown()ed.        */
+#define DEVICE_USED        16 /* This block or character device file is used. */
+#define NO_CREATE_AT_MOUNT 32 /* Don't create this file at mount().           */
+
+/* some random number */
+#define SYAORAN_MAGIC    0x2F646576 /* = '/dev' */
+
+static struct inode_operations syaoran_file_inode_operations;
+static struct inode_operations syaoran_symlink_inode_operations;
+
+static void syaoran_put_super(struct super_block *sb);
+static int syaoran_initialize(struct super_block *sb, void *data);
+static void syaoran_make_initial_nodes(struct super_block *sb);
+static int syaoran_may_create_node(struct dentry *dentry, int mode, int dev);
+static int syaoran_may_modify_node(struct dentry *dentry, unsigned int flags);
+static int syaoran_create_tracelog(struct super_block *sb,
+				   const char *filename);
+static struct inode *__ramfs_get_inode(struct super_block *sb, int mode,
+				       dev_t dev, int mac);
+
+static struct inode *syaoran_get_inode(struct super_block *sb, int mode,
+				       dev_t dev)
+{
+	return __ramfs_get_inode(sb, mode, dev, 1);
+}
+
+static struct super_operations syaoran_ops = {
+	.statfs     = simple_statfs,
+	.drop_inode = generic_delete_inode,
+	.put_super  = syaoran_put_super,
+};
+
+/* Wraps blkdev_open() to trace open operation for block devices. */
+static int (*org_blkdev_open) (struct inode *inode, struct file *filp);
+static struct file_operations wrapped_def_blk_fops;
+
+static int wrapped_blkdev_open(struct inode *inode, struct file *filp)
+{
+	int error = org_blkdev_open(inode, filp);
+	if (error != -ENXIO)
+		syaoran_may_modify_node(filp->f_dentry, DEVICE_USED);
+	return error;
+}
+
+/* Wraps chrdev_open() to trace open operation for character devices. */
+static int (*org_chrdev_open) (struct inode *inode, struct file *filp);
+static struct file_operations wrapped_def_chr_fops;
+
+static int wrapped_chrdev_open(struct inode *inode, struct file *filp)
+{
+	int error = org_chrdev_open(inode, filp);
+	if (error != -ENXIO)
+		syaoran_may_modify_node(filp->f_dentry, DEVICE_USED);
+	return error;
+}
+
+struct dev_entry {
+	struct list_head list;
+	/* Binary form of pathname under mount point. Never NULL. */
+	char *name;
+	/*
+	 * Mode and permissions. setuid/setgid/sticky bits are not supported.
+	 */
+	mode_t mode;
+	uid_t uid;
+	gid_t gid;
+	dev_t kdev;
+	/*
+	 * Binary form of initial contents for the symlink.
+	 *  NULL if not symlink.
+	 */
+	char *symlink_data;
+	/* File access control flags. */
+	unsigned int flags;
+	/* Text form of pathname under mount point. Never NULL. */
+	const char *printable_name;
+	/*
+	 * Text form of initial contents for the symlink.
+	 * NULL if not symlink.
+	 */
+	const char *printable_symlink_data;
+};
+
+struct syaoran_sb_info {
+	struct list_head list;
+	bool initialize_done;  /* False if initialization is in progress. */
+	bool is_permissive_mode; /* True if permissive mode. */
+};
+
+static void syaoran_put_super(struct super_block *sb)
+{
+	struct syaoran_sb_info *info;
+	struct dev_entry *entry;
+	struct dev_entry *tmp;
+	if (!sb)
+		return;
+	info = (struct syaoran_sb_info *) sb->s_fs_info;
+	if (!info)
+		return;
+	list_for_each_entry_safe(entry, tmp, &info->list, list) {
+		kfree(entry->name);
+		kfree(entry->symlink_data);
+		kfree(entry->printable_name);
+		kfree(entry->printable_symlink_data);
+		list_del(&entry->list);
+		/* printk("Entry removed.\n"); */
+		kfree(entry);
+	}
+	kfree(info);
+	sb->s_fs_info = NULL;
+	printk(KERN_DEBUG "%s: Unused memory freed.\n", __FUNCTION__);
+}
+
+/* Get absolute pathname from mount point. */
+static int get_local_absolute_path(struct dentry *dentry, char *buffer,
+				   int buflen)
+{
+	char *start = buffer;
+	char *end = buffer + buflen;
+	int namelen;
+
+	if (buflen < 256)
+		goto out;
+
+	*--end = '\0';
+	buflen--;
+	for (;;) {
+		struct dentry *parent;
+		if (IS_ROOT(dentry))
+			break;
+		parent = dentry->d_parent;
+		namelen = dentry->d_name.len;
+		buflen -= namelen + 1;
+		if (buflen < 0)
+			goto out;
+		end -= namelen;
+		memcpy(end, dentry->d_name.name, namelen);
+		*--end = '/';
+		dentry = parent;
+	}
+	if (*end == '/') {
+		buflen++;
+		end++;
+	}
+	namelen = dentry->d_name.len;
+	buflen -= namelen;
+	if (buflen < 0)
+		goto out;
+	end -= namelen;
+	memcpy(end, dentry->d_name.name, namelen);
+	memmove(start, end, strlen(end) + 1);
+	return 0;
+out:
+	return -ENOMEM;
+}
+
+/* Get absolute pathname of the given dentry from mount point. */
+static int local_realpath_from_dentry(struct dentry *dentry, char *newname,
+				      int newname_len)
+{
+	int error;
+	struct dentry *d_dentry;
+	if (!dentry || !newname || newname_len <= 0)
+		return -EINVAL;
+	d_dentry = dget(dentry);
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&dcache_lock);
+	error = get_local_absolute_path(d_dentry, newname, newname_len);
+	spin_unlock(&dcache_lock);
+	/***** CRITICAL SECTION END *****/
+	dput(d_dentry);
+	return error;
+}
+
+static int syaoran_check_flags(struct syaoran_sb_info *info,
+			       struct dentry *dentry, int mode, int dev,
+			       unsigned int flags)
+{
+	int error = -EPERM;
+	struct dev_entry *entry;
+	/*
+	 * Since local_realpath_from_dentry() holds dcache_lock,
+	 * allocating buffer using kmalloc() won't help improving concurrency.
+	 * Therefore, I use static buffer here.
+	 */
+	static char filename[PAGE_SIZE];
+	static DEFINE_SPINLOCK(lock);
+	spin_lock(&lock);
+	memset(filename, 0, sizeof(filename));
+	if (local_realpath_from_dentry(dentry, filename, sizeof(filename) - 1))
+		goto out;
+	list_for_each_entry(entry, &info->list, list) {
+		if ((mode & S_IFMT) != (entry->mode & S_IFMT))
+			continue;
+		if ((S_ISBLK(mode) || S_ISCHR(mode)) && dev != entry->kdev)
+			continue;
+		if (strcmp(entry->name, filename + 1))
+			continue;
+		if (info->is_permissive_mode) {
+			entry->flags |= flags;
+			error = 0;
+		} else {
+			if ((entry->flags & flags) == flags)
+				error = 0;
+		}
+		break;
+	}
+out:
+	if (error && strlen(filename) < (sizeof(filename) / 4) - 16) {
+		const char *name;
+		const uid_t uid = current->fsuid;
+		const gid_t gid = current->fsgid;
+		const mode_t perm = mode & 0777;
+		flags &= ~DEVICE_USED;
+		{
+			char *end = filename + sizeof(filename) - 1;
+			const char *cp = strchr(filename, '\0') - 1;
+			while (cp > filename) {
+				const unsigned char c = *cp--;
+				if (c == '\\') {
+					*--end = '\\';
+					*--end = '\\';
+				} else if (c > ' ' && c < 127) {
+					*--end = c;
+				} else {
+					*--end = (c & 7) + '0';
+					*--end = ((c >> 3) & 7) + '0';
+					*--end = (c >> 6) + '0';
+					*--end = '\\';
+				}
+			}
+			name = end;
+		}
+		switch (mode & S_IFMT) {
+		case S_IFCHR:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c %3u %3u\n",
+			       name, perm, uid, gid, flags, 'c',
+			       MAJOR(dev), MINOR(dev));
+			break;
+		case S_IFBLK:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c %3u %3u\n",
+			       name, perm, uid, gid, flags, 'b',
+			       MAJOR(dev), MINOR(dev));
+			break;
+		case S_IFIFO:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c\n",
+			       name, perm, uid, gid, flags, 'p');
+			break;
+		case S_IFSOCK:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c\n",
+			       name, perm, uid, gid, flags, 's');
+			break;
+		case S_IFDIR:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c\n",
+			       name, perm, uid, gid, flags, 'd');
+			break;
+		case S_IFLNK:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c %s\n",
+			       name, perm, uid, gid, flags, 'l', "unknown");
+			break;
+		case S_IFREG:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c\n",
+			       name, perm, uid, gid, flags, 'f');
+			break;
+		}
+	}
+	spin_unlock(&lock);
+	return error;
+}
+
+/* Check whether the given dentry is allowed to mknod. */
+static int syaoran_may_create_node(struct dentry *dentry, int mode, int dev)
+{
+	struct syaoran_sb_info *info =
+		(struct syaoran_sb_info *) dentry->d_sb->s_fs_info;
+	if (!info) {
+		printk(KERN_DEBUG "%s: dentry->d_sb->s_fs_info == NULL\n",
+		       __FUNCTION__);
+		return -EPERM;
+	}
+	if (!info->initialize_done)
+		return 0;
+	return syaoran_check_flags(info, dentry, mode, dev, MAY_CREATE);
+}
+
+/* Check whether the given dentry is allowed to chmod/chown/unlink. */
+static int syaoran_may_modify_node(struct dentry *dentry, unsigned int flags)
+{
+	struct syaoran_sb_info *info =
+		(struct syaoran_sb_info *) dentry->d_sb->s_fs_info;
+	if (!info) {
+		printk(KERN_DEBUG "%s: dentry->d_sb->s_fs_info == NULL\n",
+		       __FUNCTION__);
+		return -EPERM;
+	}
+	if (flags == DEVICE_USED && !info->is_permissive_mode)
+		return 0;
+	if (!dentry->d_inode)
+		return -ENOENT;
+	return syaoran_check_flags(info, dentry, dentry->d_inode->i_mode,
+				   dentry->d_inode->i_rdev, flags);
+}
+
+static int ramfs_link(struct dentry *old_dentry, struct inode *dir,
+			struct dentry *dentry)
+{
+	struct inode *inode;
+	if (dir->i_sb->s_op != &syaoran_ops)
+		goto ok;
+	/*** SYAORAN start. ***/
+	inode = old_dentry->d_inode;
+	if (!inode ||
+	    syaoran_may_create_node(dentry, inode->i_mode, inode->i_rdev))
+		return -EPERM;
+	/*** SYAORAN end. ***/
+ok:
+	return simple_link(old_dentry, dir, dentry);
+}
+
+static int ramfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+	if (dir->i_sb->s_op != &syaoran_ops)
+		goto ok;
+	/*** SYAORAN start. ***/
+	if (syaoran_may_modify_node(dentry, MAY_DELETE))
+		return -EPERM;
+	/*** SYAORAN end. ***/
+ok:
+	return simple_unlink(dir, dentry);
+}
+
+static int ramfs_rename(struct inode *old_dir, struct dentry *old_dentry,
+			  struct inode *new_dir, struct dentry *new_dentry)
+{
+	struct inode *inode;
+	if (old_dir->i_sb->s_op != &syaoran_ops)
+		goto ok;
+	/*** SYAORAN start. ***/
+	inode = old_dentry->d_inode;
+	if (!inode || syaoran_may_modify_node(old_dentry, MAY_DELETE) ||
+	    syaoran_may_create_node(new_dentry, inode->i_mode, inode->i_rdev))
+		return -EPERM;
+	/*** SYAORAN end. ***/
+ok:
+	return simple_rename(old_dir, old_dentry, new_dir, new_dentry);
+}
+
+static int ramfs_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	if (dir->i_sb->s_op != &syaoran_ops)
+		goto ok;
+	/*** SYAORAN start. ***/
+	if (syaoran_may_modify_node(dentry, MAY_DELETE))
+		return -EPERM;
+	/*** SYAORAN end. ***/
+ok:
+	return simple_rmdir(dir, dentry);
+}
+
+/*
+ * Original tmpfs doesn't set ramfs_dir_inode_operations.setattr field.
+ * Now I'm setting the field to share tmpfs/rootfs/SYAORAN code.
+ * Side effect is that the checking order of notify_change() has changed from
+ *  inode_change_ok() -> security_inode_setattr() ->
+ *                                     DQUOT_TRANSFER() -> inode_setattr()
+ * to
+ *  security_inode_setattr() -> inode_change_ok() ->
+ *                                     DQUOT_TRANSFER() -> inode_setattr()
+ *
+ * Is this change problematic? If problematic, I'll stop sharing the field.
+ */
+static int ramfs_setattr(struct dentry *dentry, struct iattr *attr)
+{
+	unsigned int ia_valid;
+	unsigned int flags = 0;
+	struct inode *inode = dentry->d_inode;
+	int error = inode_change_ok(inode, attr);
+	if (inode->i_sb->s_op != &syaoran_ops)
+		goto ok;
+	/*** SYAORAN start. ***/
+	ia_valid = attr->ia_valid;
+	if (ia_valid & (ATTR_UID | ATTR_GID))
+		flags |= MAY_CHOWN;
+	if (ia_valid & ATTR_MODE)
+		flags |= MAY_CHMOD;
+	if (syaoran_may_modify_node(dentry, flags))
+		return -EPERM;
+	/*** SYAORAN end. ***/
+ok:
+	if (!error) {
+		if ((ia_valid & ATTR_UID && attr->ia_uid != inode->i_uid) ||
+		    (ia_valid & ATTR_GID && attr->ia_gid != inode->i_gid))
+			error = DQUOT_TRANSFER(inode, attr) ? -EDQUOT : 0;
+		if (!error)
+			error = inode_setattr(inode, attr);
+	}
+	return error;
+}
+
+static struct inode_operations syaoran_file_inode_operations = {
+	.getattr        = simple_getattr,
+	.setattr        = ramfs_setattr,
+};
+
+static struct inode_operations syaoran_symlink_inode_operations = {
+	.readlink       = generic_readlink,
+	.follow_link    = page_follow_link_light,
+	.put_link       = page_put_link,
+	.setattr        = ramfs_setattr,
+};
+
+/*
+ * The following codes are used for processing the policy file and
+ * creating initial nodes.
+ */
+
+/* lookup_create() without nameidata. Called only while initialization. */
+static struct dentry *lookup_create2(const char *name, struct dentry *base,
+				     const bool is_dir)
+{
+	struct dentry *dentry;
+	const int len = name ? strlen(name) : 0;
+	mutex_lock(&base->d_inode->i_mutex);
+	dentry = lookup_one_len(name, base, len);
+	if (IS_ERR(dentry))
+		goto fail;
+	if (!is_dir && name[len] && !dentry->d_inode)
+		goto enoent;
+	return dentry;
+enoent:
+	dput(dentry);
+	dentry = ERR_PTR(-ENOENT);
+fail:
+	return dentry;
+}
+
+/* mkdir(). Called only while initialization. */
+static int fs_mkdir(const char *pathname, struct dentry *base, int mode,
+		    uid_t user, gid_t group)
+{
+	struct dentry *dentry = lookup_create2(pathname, base, 1);
+	int error = PTR_ERR(dentry);
+	if (!IS_ERR(dentry)) {
+		error = vfs_mkdir(base->d_inode, dentry, mode);
+		if (!error) {
+			lock_kernel();
+			dentry->d_inode->i_uid = user;
+			dentry->d_inode->i_gid = group;
+			unlock_kernel();
+		}
+		dput(dentry);
+	}
+	mutex_unlock(&base->d_inode->i_mutex);
+	return error;
+}
+
+/* mknod(). Called only while initialization. */
+static int fs_mknod(const char *filename, struct dentry *base, int mode,
+		    dev_t dev, uid_t user, gid_t group)
+{
+	struct dentry *dentry;
+	int error;
+	switch (mode & S_IFMT) {
+	case S_IFCHR:
+	case S_IFBLK:
+	case S_IFIFO:
+	case S_IFSOCK:
+	case S_IFREG:
+		break;
+	default:
+		return -EPERM;
+	}
+	dentry = lookup_create2(filename, base, 0);
+	error = PTR_ERR(dentry);
+	if (!IS_ERR(dentry)) {
+		error = vfs_mknod(base->d_inode, dentry, mode, dev);
+		if (!error) {
+			lock_kernel();
+			dentry->d_inode->i_uid = user;
+			dentry->d_inode->i_gid = group;
+			unlock_kernel();
+		}
+		dput(dentry);
+	}
+	mutex_unlock(&base->d_inode->i_mutex);
+	return error;
+}
+
+/* symlink(). Called only while initialization. */
+static int fs_symlink(const char *pathname, struct dentry *base,
+		      char *oldname, int mode, uid_t user, gid_t group)
+{
+	struct dentry *dentry = lookup_create2(pathname, base, 0);
+	int error = PTR_ERR(dentry);
+	if (!IS_ERR(dentry)) {
+		error = vfs_symlink(base->d_inode, dentry, oldname, S_IALLUGO);
+		if (!error) {
+			lock_kernel();
+			dentry->d_inode->i_mode = mode;
+			dentry->d_inode->i_uid = user;
+			dentry->d_inode->i_gid = group;
+			unlock_kernel();
+		}
+		dput(dentry);
+	}
+	mutex_unlock(&base->d_inode->i_mutex);
+	return error;
+}
+
+/*
+ * Format string.
+ * Leading and trailing whitespaces are removed.
+ * Multiple whitespaces are packed into single space.
+ */
+static void syaoran_normalize_line(unsigned char *buffer)
+{
+	unsigned char *sp = buffer;
+	unsigned char *dp = buffer;
+	bool first = 1;
+	while (*sp && (*sp <= ' ' || *sp >= 127))
+		sp++;
+	while (*sp) {
+		if (!first)
+			*dp++ = ' ';
+		first = 0;
+		while (*sp > ' ' && *sp < 127)
+			*dp++ = *sp++;
+		while (*sp && (*sp <= ' ' || *sp >= 127))
+			sp++;
+	}
+	*dp = '\0';
+}
+
+/* Convert text form of filename into binary form. */
+static void syaoran_unescape(char *filename)
+{
+	char *cp = filename;
+	char c, d, e;
+	if (!cp)
+		return;
+	while ((c = *filename++) != '\0') {
+		if (c != '\\') {
+			*cp++ = c;
+			continue;
+		}
+		if ((c = *filename++) == '\\') {
+			*cp++ = c;
+			continue;
+		}
+		if (c < '0' || c > '3')
+			break;
+		d = *filename++;
+		if (d < '0' || d > '7')
+			break;
+		e = *filename++;
+		if (e < '0' || e > '7')
+			break;
+		*(unsigned char *) cp++ = (unsigned char)
+			(((unsigned char) (c - '0') << 6) +
+			 ((unsigned char) (d - '0') << 3) +
+			 (unsigned char) (e - '0'));
+	}
+	*cp = '\0';
+}
+
+static inline char *strdup(const char *data)
+{
+	return kstrdup(data, GFP_KERNEL);
+}
+
+static int register_node_info(char *buffer, struct super_block *sb)
+{
+	enum {
+		ARG_FILENAME     = 0,
+		ARG_PERMISSION   = 1,
+		ARG_UID          = 2,
+		ARG_GID          = 3,
+		ARG_FLAGS        = 4,
+		ARG_DEV_TYPE     = 5,
+		ARG_SYMLINK_DATA = 6,
+		ARG_DEV_MAJOR    = 6,
+		ARG_DEV_MINOR    = 7,
+		MAX_ARG          = 8
+	};
+	char *args[MAX_ARG];
+	int i;
+	int error = -EINVAL;
+	unsigned int perm, uid, gid, flags, major = 0, minor = 0;
+	struct syaoran_sb_info *info = (struct syaoran_sb_info *) sb->s_fs_info;
+	struct dev_entry *entry;
+	memset(args, 0, sizeof(args));
+	args[0] = buffer;
+	for (i = 1; i < MAX_ARG; i++) {
+		args[i] = strchr(args[i - 1] + 1, ' ');
+		if (!args[i])
+			break;
+		*args[i]++ = '\0';
+	}
+	/*
+	  printk("<%s> <%s> <%s> <%s> <%s> <%s> <%s> <%s>\n",
+	  args[0], args[1], args[2], args[3], args[4], args[5],
+	  args[6], args[7]);
+	*/
+	if (!args[ARG_FILENAME] || !args[ARG_PERMISSION] || !args[ARG_UID] ||
+	    !args[ARG_GID] || !args[ARG_DEV_TYPE] || !args[ARG_FLAGS])
+		goto out;
+	if (sscanf(args[ARG_PERMISSION], "%o", &perm) != 1 || !(perm <= 0777)
+	    || sscanf(args[ARG_UID], "%u", &uid) != 1
+	    || sscanf(args[ARG_GID], "%u", &gid) != 1
+	    || sscanf(args[ARG_FLAGS], "%u", &flags) != 1
+	    || *(args[ARG_DEV_TYPE] + 1))
+		goto out;
+	switch (*args[ARG_DEV_TYPE]) {
+	case 'c':
+		perm |= S_IFCHR;
+		if (!args[ARG_DEV_MAJOR]
+		    || sscanf(args[ARG_DEV_MAJOR], "%u", &major) != 1
+		    || !args[ARG_DEV_MINOR]
+		    || sscanf(args[ARG_DEV_MINOR], "%u", &minor) != 1)
+			goto out;
+		break;
+	case 'b':
+		perm |= S_IFBLK;
+		if (!args[ARG_DEV_MAJOR]
+		    || sscanf(args[ARG_DEV_MAJOR], "%u", &major) != 1
+		    || !args[ARG_DEV_MINOR]
+		    || sscanf(args[ARG_DEV_MINOR], "%u", &minor) != 1)
+			goto out;
+		break;
+	case 'l':
+		perm |= S_IFLNK;
+		if (!args[ARG_SYMLINK_DATA])
+			goto out;
+		break;
+	case 'd':
+		perm |= S_IFDIR;
+		break;
+	case 's':
+		perm |= S_IFSOCK;
+		break;
+	case 'p':
+		perm |= S_IFIFO;
+		break;
+	case 'f':
+		perm |= S_IFREG;
+		break;
+	default:
+		goto out;
+	}
+	error = -ENOMEM;
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		goto out;
+	if (S_ISLNK(perm)) {
+		entry->printable_symlink_data = strdup(args[ARG_SYMLINK_DATA]);
+		if (!entry->printable_symlink_data)
+			goto out_freemem;
+	}
+	entry->printable_name = strdup(args[ARG_FILENAME]);
+	if (!entry->printable_name)
+		goto out_freemem;
+	if (S_ISLNK(perm)) {
+		entry->symlink_data = strdup(entry->printable_symlink_data);
+		if (!entry->symlink_data)
+			goto out_freemem;
+		syaoran_unescape(entry->symlink_data);
+	}
+	entry->name = strdup(entry->printable_name);
+	if (!entry->name)
+		goto out_freemem;
+	syaoran_unescape(entry->name);
+	/*
+	 * Drop trailing '/', for GetLocalAbsolutePath() doesn't append
+	 *  trailing '/'.
+	 */
+	i = strlen(entry->name);
+	if (i && entry->name[i - 1] == '/')
+		entry->name[i - 1] = '\0';
+	entry->mode = perm;
+	entry->uid = uid;
+	entry->gid = gid;
+	entry->kdev = S_ISCHR(perm) || S_ISBLK(perm) ? MKDEV(major, minor) : 0;
+	entry->flags = flags;
+	list_add_tail(&entry->list, &info->list);
+	/* printk("Entry added.\n"); */
+	error = 0;
+out:
+	return error;
+out_freemem:
+	kfree(entry->printable_symlink_data);
+	kfree(entry->printable_name);
+	kfree(entry->symlink_data);
+	kfree(entry);
+	goto out;
+}
+
+static int read_config_file(struct file *file, struct super_block *sb)
+{
+	char *buffer;
+	int error = -ENOMEM;
+	if (!file)
+		return -EINVAL;
+	buffer = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (buffer) {
+		int len;
+		char *cp;
+		unsigned long offset = 0;
+		while ((len = kernel_read(file, offset, buffer, PAGE_SIZE)) > 0
+		       && (cp = memchr(buffer, '\n', len)) != NULL) {
+			*cp = '\0';
+			offset += cp - buffer + 1;
+			syaoran_normalize_line(buffer);
+			if (register_node_info(buffer, sb) == -ENOMEM)
+				goto out;
+		}
+		error = 0;
+	}
+out:
+	kfree(buffer);
+	return error;
+}
+
+static void make_node(struct dev_entry *entry, struct dentry *root)
+{
+	struct dentry *base = dget(root);
+	char *filename = entry->name;
+	char *name = filename;
+	unsigned int c;
+	const mode_t perm = entry->mode;
+	const uid_t uid = entry->uid;
+	const gid_t gid = entry->gid;
+	goto start;
+	while ((c = *(unsigned char *) filename) != '\0') {
+		if (c == '/') {
+			struct dentry *new_base;
+			const int len = filename - name;
+			*filename = '\0';
+			mutex_lock(&base->d_inode->i_mutex);
+			new_base = lookup_one_len(name, base, len);
+			mutex_unlock(&base->d_inode->i_mutex);
+			dput(base);
+			*filename = '/';
+			filename++;
+			if (IS_ERR(new_base))
+				return;
+			if (!new_base->d_inode ||
+			    !S_ISDIR(new_base->d_inode->i_mode)) {
+				dput(new_base);
+				return;
+			}
+			base = new_base;
+start:
+			name = filename;
+		} else {
+			filename++;
+		}
+	}
+	filename = (char *) name;
+	if (S_ISLNK(perm)) {
+		fs_symlink(filename, base, entry->symlink_data, perm, uid, gid);
+	} else if (S_ISDIR(perm)) {
+		fs_mkdir(filename, base, perm ^ S_IFDIR, uid, gid);
+	} else if (S_ISSOCK(perm) || S_ISFIFO(perm) || S_ISREG(perm)) {
+		fs_mknod(filename, base, perm, 0, uid, gid);
+	} else if (S_ISCHR(perm) || S_ISBLK(perm)) {
+		fs_mknod(filename, base, perm, entry->kdev, uid, gid);
+	}
+	dput(base);
+}
+
+/* Create files according to the policy file. */
+static void syaoran_make_initial_nodes(struct super_block *sb)
+{
+	struct syaoran_sb_info *info;
+	struct dev_entry *entry;
+	if (!sb)
+		return;
+	info = (struct syaoran_sb_info *) sb->s_fs_info;
+	if (!info)
+		return;
+	if (info->is_permissive_mode) {
+		syaoran_create_tracelog(sb, ".syaoran");
+		syaoran_create_tracelog(sb, ".syaoran_all");
+	}
+	list_for_each_entry(entry, &info->list, list) {
+		if ((entry->flags & NO_CREATE_AT_MOUNT) == 0)
+			make_node(entry, sb->s_root);
+	}
+	info->initialize_done = 1;
+}
+
+/* Read policy file. */
+static int syaoran_initialize(struct super_block *sb, void *data)
+{
+	int error = -EINVAL;
+	static bool first = 1;
+	if (first) {
+		first = 0;
+		printk(KERN_INFO "SYAORAN: 1.5.3-pre   2007/12/23\n");
+	}
+	{
+		struct inode *inode = new_inode(sb);
+		if (!inode)
+			return -EINVAL;
+		/* Create /dev/ram0 to get the value of blkdev_open(). */
+		init_special_inode(inode, S_IFBLK | 0666, MKDEV(1, 0));
+		wrapped_def_blk_fops = *inode->i_fop;
+		iput(inode);
+		org_blkdev_open = wrapped_def_blk_fops.open;
+		wrapped_def_blk_fops.open = wrapped_blkdev_open;
+	}
+	{
+		struct inode *inode = new_inode(sb);
+		if (!inode)
+			return -EINVAL;
+		/* Create /dev/null to get the value of chrdev_open(). */
+		init_special_inode(inode, S_IFCHR | 0666, MKDEV(1, 3));
+		wrapped_def_chr_fops = *inode->i_fop;
+		iput(inode);
+		org_chrdev_open = wrapped_def_chr_fops.open;
+		wrapped_def_chr_fops.open = wrapped_chrdev_open;
+	}
+	if (data) {
+		struct file *f;
+		char *filename = (char *) data;
+		bool is_permissive_mode = 0;
+		if (strncmp(filename, "accept=", 7) == 0) {
+			filename += 7;
+			is_permissive_mode = 1;
+		} else if (strncmp(filename, "enforce=", 8) == 0) {
+			filename += 8;
+			is_permissive_mode = 0;
+		} else {
+			printk(KERN_INFO
+			       "SYAORAN: Missing 'accept=' or 'enforce='.\n");
+			return -EINVAL;
+		}
+		f = open_pathname(AT_FDCWD, filename, O_RDONLY, 0600);
+		if (!IS_ERR(f)) {
+			struct syaoran_sb_info *p;
+			if (!S_ISREG(f->f_dentry->d_inode->i_mode))
+				goto out;
+			p = kzalloc(sizeof(*p), GFP_KERNEL);
+			if (!p)
+				goto out;
+			p->is_permissive_mode = is_permissive_mode;
+			sb->s_fs_info = p;
+			INIT_LIST_HEAD(&((struct syaoran_sb_info *)
+					 sb->s_fs_info)->list);
+			printk(KERN_INFO "SYAORAN: Reading '%s'\n", filename);
+			error = read_config_file(f, sb);
+out:
+			if (error)
+				printk(KERN_INFO "SYAORAN: Can't read '%s'\n",
+				       filename);
+			filp_close(f, NULL);
+		} else {
+			printk(KERN_INFO "SYAORAN: Can't open '%s'\n",
+			       filename);
+		}
+	} else {
+		printk(KERN_INFO "SYAORAN: Missing config-file path.\n");
+	}
+	return error;
+}
+
+/*
+ * The following structure and codes are used for transferring data
+ * to interfaces files.
+ */
+
+struct syaoran_read_struct {
+	char *buf;               /* Buffer for reading.                */
+	int avail;               /* Bytes available for reading.       */
+	struct super_block *sb;  /* The super_block of this partition. */
+	struct dev_entry *entry; /* The entry currently reading from.  */
+	_Bool read_all;          /* Dump all entries?                  */
+	struct list_head *pos;   /* Current position.                  */
+};
+
+static void syaoran_read_table(struct syaoran_read_struct *head, char *buf,
+			       int count)
+{
+	struct super_block *sb = head->sb;
+	struct syaoran_sb_info *info =
+		(struct syaoran_sb_info *) sb->s_fs_info;
+	struct list_head *pos;
+	const _Bool read_all = head->read_all;
+	if (!info)
+		return;
+	if (!head->pos)
+		return;
+	list_for_each_cookie(pos, head->pos, &info->list) {
+		struct dev_entry *entry =
+			list_entry(pos, struct dev_entry, list);
+		const unsigned int flags =
+			read_all ? entry->flags : entry->flags & ~DEVICE_USED;
+		const char *name = entry->printable_name;
+		const uid_t uid = entry->uid;
+		const gid_t gid = entry->gid;
+		const mode_t perm = entry->mode & 0777;
+		int len = 0;
+		switch (entry->mode & S_IFMT) {
+		case S_IFCHR:
+			if (!head->read_all && !(entry->flags & DEVICE_USED))
+				break;
+			len = snprintf(buf, count,
+				       "%-20s %3o %3u %3u %2u %c %3u %3u\n",
+				       name, perm, uid, gid, flags, 'c',
+				       MAJOR(entry->kdev), MINOR(entry->kdev));
+			break;
+		case S_IFBLK:
+			if (!head->read_all && !(entry->flags & DEVICE_USED))
+				break;
+			len = snprintf(buf, count,
+				       "%-20s %3o %3u %3u %2u %c %3u %3u\n",
+				       name, perm, uid, gid, flags, 'b',
+				       MAJOR(entry->kdev), MINOR(entry->kdev));
+			break;
+		case S_IFIFO:
+			len = snprintf(buf, count,
+				       "%-20s %3o %3u %3u %2u %c\n",
+				       name, perm, uid, gid, flags, 'p');
+			break;
+		case S_IFSOCK:
+			len = snprintf(buf, count,
+				       "%-20s %3o %3u %3u %2u %c\n",
+				       name, perm, uid, gid, flags, 's');
+			break;
+		case S_IFDIR:
+			len = snprintf(buf, count,
+				       "%-20s %3o %3u %3u %2u %c\n",
+				       name, perm, uid, gid, flags, 'd');
+			break;
+		case S_IFLNK:
+			len = snprintf(buf, count,
+				       "%-20s %3o %3u %3u %2u %c %s\n",
+				       name, perm, uid, gid, flags, 'l',
+				       entry->printable_symlink_data);
+			break;
+		case S_IFREG:
+			len = snprintf(buf, count,
+				       "%-20s %3o %3u %3u %2u %c\n",
+				       name, perm, uid, gid, flags, 'f');
+			break;
+		}
+		if (len < 0 || count <= len)
+			break;
+		count -= len;
+		buf += len;
+		head->avail += len;
+	}
+}
+
+static int syaoran_trace_open(struct inode *inode, struct file *file)
+{
+	struct syaoran_read_struct *head =
+		kzalloc(sizeof(*head), GFP_KERNEL);
+	if (!head)
+		return -ENOMEM;
+	head->sb = inode->i_sb;
+	head->read_all =
+		(strcmp(file->f_dentry->d_name.name, ".syaoran_all") == 0);
+	head->pos = &((struct syaoran_sb_info *) head->sb->s_fs_info)->list;
+	head->buf = kzalloc(PAGE_SIZE * 2, GFP_KERNEL);
+	if (!head->buf) {
+		kfree(head);
+		return -ENOMEM;
+	}
+	file->private_data = head;
+	return 0;
+}
+
+static int syaoran_trace_release(struct inode *inode, struct file *file)
+{
+	struct syaoran_read_struct *head = file->private_data;
+	kfree(head->buf);
+	kfree(head);
+	file->private_data = NULL;
+	return 0;
+}
+
+static ssize_t syaoran_trace_read(struct file *file, char __user *buf,
+				  size_t count, loff_t *ppos)
+{
+	struct syaoran_read_struct *head =
+		(struct syaoran_read_struct *) file->private_data;
+	int len = head->avail;
+	char *cp = head->buf;
+	if (!access_ok(VERIFY_WRITE, buf, count))
+		return -EFAULT;
+	syaoran_read_table(head, cp + len, PAGE_SIZE * 2 - len);
+	len = head->avail;
+	if (len > count)
+		len = count;
+	if (len > 0) {
+		if (copy_to_user(buf, cp, len))
+			return -EFAULT;
+		head->avail -= len;
+		memmove(cp, cp + len, head->avail);
+	}
+	return len;
+}
+
+static struct file_operations syaoran_trace_operations = {
+	.open    = syaoran_trace_open,
+	.release = syaoran_trace_release,
+	.read    = syaoran_trace_read,
+};
+
+/* Create interface files for reading status. */
+static int syaoran_create_tracelog(struct super_block *sb, const char *filename)
+{
+	struct inode *inode;
+	struct dentry *base = dget(sb->s_root);
+	struct dentry *dentry = lookup_create2(filename, base, 0);
+	int error = PTR_ERR(dentry);
+	if (IS_ERR(dentry))
+		goto out;
+	inode = syaoran_get_inode(sb, S_IFREG | 0400, 0);
+	if (!inode)
+		error = -ENOSPC;
+	else {
+		/* Override file operation. */
+		inode->i_fop = &syaoran_trace_operations;
+		d_instantiate(dentry, inode);
+		dget(dentry); /* Extra count - pin the dentry in core */
+		error = 0;
+	}
+	dput(dentry);
+out:
+	mutex_unlock(&base->d_inode->i_mutex);
+	dput(base);
+	return error;
+}
--
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