diff options
Diffstat (limited to 'drivers/android/binderfs.c')
| -rw-r--r-- | drivers/android/binderfs.c | 282 | 
1 files changed, 151 insertions, 131 deletions
diff --git a/drivers/android/binderfs.c b/drivers/android/binderfs.c index 7496b10532aa..6a2185eb66c5 100644 --- a/drivers/android/binderfs.c +++ b/drivers/android/binderfs.c @@ -11,6 +11,7 @@  #include <linux/kdev_t.h>  #include <linux/kernel.h>  #include <linux/list.h> +#include <linux/namei.h>  #include <linux/magic.h>  #include <linux/major.h>  #include <linux/miscdevice.h> @@ -20,6 +21,7 @@  #include <linux/parser.h>  #include <linux/radix-tree.h>  #include <linux/sched.h> +#include <linux/seq_file.h>  #include <linux/slab.h>  #include <linux/spinlock_types.h>  #include <linux/stddef.h> @@ -30,7 +32,7 @@  #include <linux/xarray.h>  #include <uapi/asm-generic/errno-base.h>  #include <uapi/linux/android/binder.h> -#include <uapi/linux/android/binder_ctl.h> +#include <uapi/linux/android/binderfs.h>  #include "binder_internal.h" @@ -39,14 +41,32 @@  #define INODE_OFFSET 3  #define INTSTRLEN 21  #define BINDERFS_MAX_MINOR (1U << MINORBITS) - -static struct vfsmount *binderfs_mnt; +/* Ensure that the initial ipc namespace always has devices available. */ +#define BINDERFS_MAX_MINOR_CAPPED (BINDERFS_MAX_MINOR - 4)  static dev_t binderfs_dev;  static DEFINE_MUTEX(binderfs_minors_mutex);  static DEFINE_IDA(binderfs_minors);  /** + * binderfs_mount_opts - mount options for binderfs + * @max: maximum number of allocatable binderfs binder devices + */ +struct binderfs_mount_opts { +	int max; +}; + +enum { +	Opt_max, +	Opt_err +}; + +static const match_table_t tokens = { +	{ Opt_max, "max=%d" }, +	{ Opt_err, NULL     } +}; + +/**   * binderfs_info - information about a binderfs mount   * @ipc_ns:         The ipc namespace the binderfs mount belongs to.   * @control_dentry: This records the dentry of this binderfs mount @@ -55,13 +75,16 @@ static DEFINE_IDA(binderfs_minors);   *                  created.   * @root_gid:       gid that needs to be used when a new binder device is   *                  created. + * @mount_opts:     The mount options in use. + * @device_count:   The current number of allocated binder devices.   */  struct binderfs_info {  	struct ipc_namespace *ipc_ns;  	struct dentry *control_dentry;  	kuid_t root_uid;  	kgid_t root_gid; - +	struct binderfs_mount_opts mount_opts; +	int device_count;  };  static inline struct binderfs_info *BINDERFS_I(const struct inode *inode) @@ -84,7 +107,7 @@ bool is_binderfs_device(const struct inode *inode)   * @userp:     buffer to copy information about new device for userspace to   * @req:       struct binderfs_device as copied from userspace   * - * This function allocated a new binder_device and reserves a new minor + * This function allocates a new binder_device and reserves a new minor   * number for it.   * Minor numbers are limited and tracked globally in binderfs_minors. The   * function will stash a struct binder_device for the specific binder @@ -100,20 +123,34 @@ static int binderfs_binder_device_create(struct inode *ref_inode,  					 struct binderfs_device *req)  {  	int minor, ret; -	struct dentry *dentry, *dup, *root; +	struct dentry *dentry, *root;  	struct binder_device *device; -	size_t name_len = BINDERFS_MAX_NAME + 1;  	char *name = NULL; +	size_t name_len;  	struct inode *inode = NULL;  	struct super_block *sb = ref_inode->i_sb;  	struct binderfs_info *info = sb->s_fs_info; +#if defined(CONFIG_IPC_NS) +	bool use_reserve = (info->ipc_ns == &init_ipc_ns); +#else +	bool use_reserve = true; +#endif  	/* Reserve new minor number for the new device. */  	mutex_lock(&binderfs_minors_mutex); -	minor = ida_alloc_max(&binderfs_minors, BINDERFS_MAX_MINOR, GFP_KERNEL); -	mutex_unlock(&binderfs_minors_mutex); -	if (minor < 0) +	if (++info->device_count <= info->mount_opts.max) +		minor = ida_alloc_max(&binderfs_minors, +				      use_reserve ? BINDERFS_MAX_MINOR : +						    BINDERFS_MAX_MINOR_CAPPED, +				      GFP_KERNEL); +	else +		minor = -ENOSPC; +	if (minor < 0) { +		--info->device_count; +		mutex_unlock(&binderfs_minors_mutex);  		return minor; +	} +	mutex_unlock(&binderfs_minors_mutex);  	ret = -ENOMEM;  	device = kzalloc(sizeof(*device), GFP_KERNEL); @@ -132,12 +169,13 @@ static int binderfs_binder_device_create(struct inode *ref_inode,  	inode->i_uid = info->root_uid;  	inode->i_gid = info->root_gid; -	name = kmalloc(name_len, GFP_KERNEL); +	req->name[BINDERFS_MAX_NAME] = '\0'; /* NUL-terminate */ +	name_len = strlen(req->name); +	/* Make sure to include terminating NUL byte */ +	name = kmemdup(req->name, name_len + 1, GFP_KERNEL);  	if (!name)  		goto err; -	strscpy(name, req->name, name_len); -  	device->binderfs_inode = inode;  	device->context.binder_context_mgr_uid = INVALID_UID;  	device->context.name = name; @@ -156,28 +194,25 @@ static int binderfs_binder_device_create(struct inode *ref_inode,  	root = sb->s_root;  	inode_lock(d_inode(root)); -	dentry = d_alloc_name(root, name); -	if (!dentry) { + +	/* look it up */ +	dentry = lookup_one_len(name, root, name_len); +	if (IS_ERR(dentry)) {  		inode_unlock(d_inode(root)); -		ret = -ENOMEM; +		ret = PTR_ERR(dentry);  		goto err;  	} -	/* Verify that the name userspace gave us is not already in use. */ -	dup = d_lookup(root, &dentry->d_name); -	if (dup) { -		if (d_really_is_positive(dup)) { -			dput(dup); -			dput(dentry); -			inode_unlock(d_inode(root)); -			ret = -EEXIST; -			goto err; -		} -		dput(dup); +	if (d_really_is_positive(dentry)) { +		/* already exists */ +		dput(dentry); +		inode_unlock(d_inode(root)); +		ret = -EEXIST; +		goto err;  	}  	inode->i_private = device; -	d_add(dentry, inode); +	d_instantiate(dentry, inode);  	fsnotify_create(root->d_inode, dentry);  	inode_unlock(d_inode(root)); @@ -187,6 +222,7 @@ err:  	kfree(name);  	kfree(device);  	mutex_lock(&binderfs_minors_mutex); +	--info->device_count;  	ida_free(&binderfs_minors, minor);  	mutex_unlock(&binderfs_minors_mutex);  	iput(inode); @@ -232,6 +268,7 @@ static long binder_ctl_ioctl(struct file *file, unsigned int cmd,  static void binderfs_evict_inode(struct inode *inode)  {  	struct binder_device *device = inode->i_private; +	struct binderfs_info *info = BINDERFS_I(inode);  	clear_inode(inode); @@ -239,6 +276,7 @@ static void binderfs_evict_inode(struct inode *inode)  		return;  	mutex_lock(&binderfs_minors_mutex); +	--info->device_count;  	ida_free(&binderfs_minors, device->miscdev.minor);  	mutex_unlock(&binderfs_minors_mutex); @@ -246,43 +284,87 @@ static void binderfs_evict_inode(struct inode *inode)  	kfree(device);  } +/** + * binderfs_parse_mount_opts - parse binderfs mount options + * @data: options to set (can be NULL in which case defaults are used) + */ +static int binderfs_parse_mount_opts(char *data, +				     struct binderfs_mount_opts *opts) +{ +	char *p; +	opts->max = BINDERFS_MAX_MINOR; + +	while ((p = strsep(&data, ",")) != NULL) { +		substring_t args[MAX_OPT_ARGS]; +		int token; +		int max_devices; + +		if (!*p) +			continue; + +		token = match_token(p, tokens, args); +		switch (token) { +		case Opt_max: +			if (match_int(&args[0], &max_devices) || +			    (max_devices < 0 || +			     (max_devices > BINDERFS_MAX_MINOR))) +				return -EINVAL; + +			opts->max = max_devices; +			break; +		default: +			pr_err("Invalid mount options\n"); +			return -EINVAL; +		} +	} + +	return 0; +} + +static int binderfs_remount(struct super_block *sb, int *flags, char *data) +{ +	struct binderfs_info *info = sb->s_fs_info; +	return binderfs_parse_mount_opts(data, &info->mount_opts); +} + +static int binderfs_show_mount_opts(struct seq_file *seq, struct dentry *root) +{ +	struct binderfs_info *info; + +	info = root->d_sb->s_fs_info; +	if (info->mount_opts.max <= BINDERFS_MAX_MINOR) +		seq_printf(seq, ",max=%d", info->mount_opts.max); + +	return 0; +} +  static const struct super_operations binderfs_super_ops = { -	.statfs = simple_statfs, -	.evict_inode = binderfs_evict_inode, +	.evict_inode    = binderfs_evict_inode, +	.remount_fs	= binderfs_remount, +	.show_options	= binderfs_show_mount_opts, +	.statfs         = simple_statfs,  }; +static inline bool is_binderfs_control_device(const struct dentry *dentry) +{ +	struct binderfs_info *info = dentry->d_sb->s_fs_info; +	return info->control_dentry == dentry; +} +  static int binderfs_rename(struct inode *old_dir, struct dentry *old_dentry,  			   struct inode *new_dir, struct dentry *new_dentry,  			   unsigned int flags)  { -	struct inode *inode = d_inode(old_dentry); - -	/* binderfs doesn't support directories. */ -	if (d_is_dir(old_dentry)) +	if (is_binderfs_control_device(old_dentry) || +	    is_binderfs_control_device(new_dentry))  		return -EPERM; -	if (flags & ~RENAME_NOREPLACE) -		return -EINVAL; - -	if (!simple_empty(new_dentry)) -		return -ENOTEMPTY; - -	if (d_really_is_positive(new_dentry)) -		simple_unlink(new_dir, new_dentry); - -	old_dir->i_ctime = old_dir->i_mtime = new_dir->i_ctime = -		new_dir->i_mtime = inode->i_ctime = current_time(old_dir); - -	return 0; +	return simple_rename(old_dir, old_dentry, new_dir, new_dentry, flags);  }  static int binderfs_unlink(struct inode *dir, struct dentry *dentry)  { -	/* -	 * The control dentry is only ever touched during mount so checking it -	 * here should not require us to take lock. -	 */ -	if (BINDERFS_I(dir)->control_dentry == dentry) +	if (is_binderfs_control_device(dentry))  		return -EPERM;  	return simple_unlink(dir, dentry); @@ -318,8 +400,6 @@ static int binderfs_binder_ctl_create(struct super_block *sb)  	if (!device)  		return -ENOMEM; -	inode_lock(d_inode(root)); -  	/* If we have already created a binder-control node, return. */  	if (info->control_dentry) {  		ret = 0; @@ -358,12 +438,10 @@ static int binderfs_binder_ctl_create(struct super_block *sb)  	inode->i_private = device;  	info->control_dentry = dentry;  	d_add(dentry, inode); -	inode_unlock(d_inode(root));  	return 0;  out: -	inode_unlock(d_inode(root));  	kfree(device);  	iput(inode); @@ -378,12 +456,9 @@ static const struct inode_operations binderfs_dir_inode_operations = {  static int binderfs_fill_super(struct super_block *sb, void *data, int silent)  { +	int ret;  	struct binderfs_info *info; -	int ret = -ENOMEM;  	struct inode *inode = NULL; -	struct ipc_namespace *ipc_ns = sb->s_fs_info; - -	get_ipc_ns(ipc_ns);  	sb->s_blocksize = PAGE_SIZE;  	sb->s_blocksize_bits = PAGE_SHIFT; @@ -405,11 +480,17 @@ static int binderfs_fill_super(struct super_block *sb, void *data, int silent)  	sb->s_op = &binderfs_super_ops;  	sb->s_time_gran = 1; -	info = kzalloc(sizeof(struct binderfs_info), GFP_KERNEL); -	if (!info) -		goto err_without_dentry; +	sb->s_fs_info = kzalloc(sizeof(struct binderfs_info), GFP_KERNEL); +	if (!sb->s_fs_info) +		return -ENOMEM; +	info = sb->s_fs_info; + +	info->ipc_ns = get_ipc_ns(current->nsproxy->ipc_ns); + +	ret = binderfs_parse_mount_opts(data, &info->mount_opts); +	if (ret) +		return ret; -	info->ipc_ns = ipc_ns;  	info->root_gid = make_kgid(sb->s_user_ns, 0);  	if (!gid_valid(info->root_gid))  		info->root_gid = GLOBAL_ROOT_GID; @@ -417,11 +498,9 @@ static int binderfs_fill_super(struct super_block *sb, void *data, int silent)  	if (!uid_valid(info->root_uid))  		info->root_uid = GLOBAL_ROOT_UID; -	sb->s_fs_info = info; -  	inode = new_inode(sb);  	if (!inode) -		goto err_without_dentry; +		return -ENOMEM;  	inode->i_ino = FIRST_INODE;  	inode->i_fop = &simple_dir_operations; @@ -432,79 +511,28 @@ static int binderfs_fill_super(struct super_block *sb, void *data, int silent)  	sb->s_root = d_make_root(inode);  	if (!sb->s_root) -		goto err_without_dentry; - -	ret = binderfs_binder_ctl_create(sb); -	if (ret) -		goto err_with_dentry; - -	return 0; - -err_with_dentry: -	dput(sb->s_root); -	sb->s_root = NULL; - -err_without_dentry: -	put_ipc_ns(ipc_ns); -	iput(inode); -	kfree(info); - -	return ret; -} - -static int binderfs_test_super(struct super_block *sb, void *data) -{ -	struct binderfs_info *info = sb->s_fs_info; - -	if (info) -		return info->ipc_ns == data; - -	return 0; -} +		return -ENOMEM; -static int binderfs_set_super(struct super_block *sb, void *data) -{ -	sb->s_fs_info = data; -	return set_anon_super(sb, NULL); +	return binderfs_binder_ctl_create(sb);  }  static struct dentry *binderfs_mount(struct file_system_type *fs_type,  				     int flags, const char *dev_name,  				     void *data)  { -	struct super_block *sb; -	struct ipc_namespace *ipc_ns = current->nsproxy->ipc_ns; - -	if (!ns_capable(ipc_ns->user_ns, CAP_SYS_ADMIN)) -		return ERR_PTR(-EPERM); - -	sb = sget_userns(fs_type, binderfs_test_super, binderfs_set_super, -			 flags, ipc_ns->user_ns, ipc_ns); -	if (IS_ERR(sb)) -		return ERR_CAST(sb); - -	if (!sb->s_root) { -		int ret = binderfs_fill_super(sb, data, flags & SB_SILENT ? 1 : 0); -		if (ret) { -			deactivate_locked_super(sb); -			return ERR_PTR(ret); -		} - -		sb->s_flags |= SB_ACTIVE; -	} - -	return dget(sb->s_root); +	return mount_nodev(fs_type, flags, data, binderfs_fill_super);  }  static void binderfs_kill_super(struct super_block *sb)  {  	struct binderfs_info *info = sb->s_fs_info; +	kill_litter_super(sb); +  	if (info && info->ipc_ns)  		put_ipc_ns(info->ipc_ns);  	kfree(info); -	kill_litter_super(sb);  }  static struct file_system_type binder_fs_type = { @@ -530,14 +558,6 @@ static int __init init_binderfs(void)  		return ret;  	} -	binderfs_mnt = kern_mount(&binder_fs_type); -	if (IS_ERR(binderfs_mnt)) { -		ret = PTR_ERR(binderfs_mnt); -		binderfs_mnt = NULL; -		unregister_filesystem(&binder_fs_type); -		unregister_chrdev_region(binderfs_dev, BINDERFS_MAX_MINOR); -	} -  	return ret;  }  | 
