Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752919AbXLaUC6 (ORCPT ); Mon, 31 Dec 2007 15:02:58 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1751616AbXLaUCr (ORCPT ); Mon, 31 Dec 2007 15:02:47 -0500 Received: from e1.ny.us.ibm.com ([32.97.182.141]:35627 "EHLO e1.ny.us.ibm.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751196AbXLaUCo (ORCPT ); Mon, 31 Dec 2007 15:02:44 -0500 Date: Mon, 31 Dec 2007 14:02:47 -0600 From: "Serge E. Hallyn" To: Tetsuo Handa Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: Re: [PATCH][RFC] Simple tamper-proof device filesystem. Message-ID: <20071231200247.GA30373@sergelap.austin.ibm.com> References: <200712232344.JBJ90661.FQOFtFOVLJHSOM@I-love.SAKURA.ne.jp> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <200712232344.JBJ90661.FQOFtFOVLJHSOM@I-love.SAKURA.ne.jp> User-Agent: Mutt/1.5.16 (2007-06-09) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 41405 Lines: 1352 Quoting Tetsuo Handa (penguin-kernel@I-love.SAKURA.ne.jp): > 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 > --- > 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 > #include > #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 > +#include > +#include > + > +#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); To integrate this nicer into tmpfs, at least define TMPFS_IS_MAC as 1 and TMPFS_NOT_MAC as 0 and pass those values instead of just 1 and 0. > +} > + > +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; Again, I should think you'd actually want to take blkdev_open() from fs/block_dev.c and chrdev_open() from fs/char_dev.c. Surely your method of grabbing it here is not acceptable for upstream code. That's all I've got for now - though if you'd just break up some of these functions - especially syaoran_initialize() with it's set of {} blocks, it would help. thanks, -serge > + > +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-fsdevel" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/