diff options
Diffstat (limited to 'fs/devpts/inode.c')
| -rw-r--r-- | fs/devpts/inode.c | 246 | 
1 files changed, 181 insertions, 65 deletions
diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index 1bb547c9cad..cfe8466f7fe 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -10,6 +10,8 @@   *   * ------------------------------------------------------------------------- */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #include <linux/module.h>  #include <linux/init.h>  #include <linux/fs.h> @@ -36,7 +38,61 @@  #define DEVPTS_DEFAULT_PTMX_MODE 0000  #define PTMX_MINOR	2 -extern int pty_limit;			/* Config limit on Unix98 ptys */ +/* + * sysctl support for setting limits on the number of Unix98 ptys allocated. + * Otherwise one can eat up all kernel memory by opening /dev/ptmx repeatedly. + */ +static int pty_limit = NR_UNIX98_PTY_DEFAULT; +static int pty_reserve = NR_UNIX98_PTY_RESERVE; +static int pty_limit_min; +static int pty_limit_max = INT_MAX; +static int pty_count; + +static struct ctl_table pty_table[] = { +	{ +		.procname	= "max", +		.maxlen		= sizeof(int), +		.mode		= 0644, +		.data		= &pty_limit, +		.proc_handler	= proc_dointvec_minmax, +		.extra1		= &pty_limit_min, +		.extra2		= &pty_limit_max, +	}, { +		.procname	= "reserve", +		.maxlen		= sizeof(int), +		.mode		= 0644, +		.data		= &pty_reserve, +		.proc_handler	= proc_dointvec_minmax, +		.extra1		= &pty_limit_min, +		.extra2		= &pty_limit_max, +	}, { +		.procname	= "nr", +		.maxlen		= sizeof(int), +		.mode		= 0444, +		.data		= &pty_count, +		.proc_handler	= proc_dointvec, +	}, +	{} +}; + +static struct ctl_table pty_kern_table[] = { +	{ +		.procname	= "pty", +		.mode		= 0555, +		.child		= pty_table, +	}, +	{} +}; + +static struct ctl_table pty_root_table[] = { +	{ +		.procname	= "kernel", +		.mode		= 0555, +		.child		= pty_kern_table, +	}, +	{} +}; +  static DEFINE_MUTEX(allocated_ptys_lock);  static struct vfsmount *devpts_mnt; @@ -44,15 +100,16 @@ static struct vfsmount *devpts_mnt;  struct pts_mount_opts {  	int setuid;  	int setgid; -	uid_t   uid; -	gid_t   gid; +	kuid_t   uid; +	kgid_t   gid;  	umode_t mode;  	umode_t ptmxmode;  	int newinstance; +	int max;  };  enum { -	Opt_uid, Opt_gid, Opt_mode, Opt_ptmxmode, Opt_newinstance, +	Opt_uid, Opt_gid, Opt_mode, Opt_ptmxmode, Opt_newinstance,  Opt_max,  	Opt_err  }; @@ -63,6 +120,7 @@ static const match_table_t tokens = {  #ifdef CONFIG_DEVPTS_MULTIPLE_INSTANCES  	{Opt_ptmxmode, "ptmxmode=%o"},  	{Opt_newinstance, "newinstance"}, +	{Opt_max, "max=%d"},  #endif  	{Opt_err, NULL}  }; @@ -92,23 +150,26 @@ static inline struct super_block *pts_sb_from_inode(struct inode *inode)  /*   * parse_mount_options(): - * 	Set @opts to mount options specified in @data. If an option is not - * 	specified in @data, set it to its default value. The exception is - * 	'newinstance' option which can only be set/cleared on a mount (i.e. - * 	cannot be changed during remount). + *	Set @opts to mount options specified in @data. If an option is not + *	specified in @data, set it to its default value. The exception is + *	'newinstance' option which can only be set/cleared on a mount (i.e. + *	cannot be changed during remount).   *   * Note: @data may be NULL (in which case all options are set to default).   */  static int parse_mount_options(char *data, int op, struct pts_mount_opts *opts)  {  	char *p; +	kuid_t uid; +	kgid_t gid;  	opts->setuid  = 0;  	opts->setgid  = 0; -	opts->uid     = 0; -	opts->gid     = 0; +	opts->uid     = GLOBAL_ROOT_UID; +	opts->gid     = GLOBAL_ROOT_GID;  	opts->mode    = DEVPTS_DEFAULT_MODE;  	opts->ptmxmode = DEVPTS_DEFAULT_PTMX_MODE; +	opts->max     = NR_UNIX98_PTY_MAX;  	/* newinstance makes sense only on initial mount */  	if (op == PARSE_MOUNT) @@ -127,13 +188,19 @@ static int parse_mount_options(char *data, int op, struct pts_mount_opts *opts)  		case Opt_uid:  			if (match_int(&args[0], &option))  				return -EINVAL; -			opts->uid = option; +			uid = make_kuid(current_user_ns(), option); +			if (!uid_valid(uid)) +				return -EINVAL; +			opts->uid = uid;  			opts->setuid = 1;  			break;  		case Opt_gid:  			if (match_int(&args[0], &option))  				return -EINVAL; -			opts->gid = option; +			gid = make_kgid(current_user_ns(), option); +			if (!gid_valid(gid)) +				return -EINVAL; +			opts->gid = gid;  			opts->setgid = 1;  			break;  		case Opt_mode: @@ -152,9 +219,15 @@ static int parse_mount_options(char *data, int op, struct pts_mount_opts *opts)  			if (op == PARSE_MOUNT)  				opts->newinstance = 1;  			break; +		case Opt_max: +			if (match_int(&args[0], &option) || +			    option < 0 || option > NR_UNIX98_PTY_MAX) +				return -EINVAL; +			opts->max = option; +			break;  #endif  		default: -			printk(KERN_ERR "devpts: called with bogus options\n"); +			pr_err("called with bogus options\n");  			return -EINVAL;  		}  	} @@ -172,6 +245,13 @@ static int mknod_ptmx(struct super_block *sb)  	struct dentry *root = sb->s_root;  	struct pts_fs_info *fsi = DEVPTS_SB(sb);  	struct pts_mount_opts *opts = &fsi->mount_opts; +	kuid_t root_uid; +	kgid_t root_gid; + +	root_uid = make_kuid(current_user_ns(), 0); +	root_gid = make_kgid(current_user_ns(), 0); +	if (!uid_valid(root_uid) || !gid_valid(root_gid)) +		return -EINVAL;  	mutex_lock(&root->d_inode->i_mutex); @@ -183,7 +263,7 @@ static int mknod_ptmx(struct super_block *sb)  	dentry = d_alloc_name(root, "ptmx");  	if (!dentry) { -		printk(KERN_NOTICE "Unable to alloc dentry for ptmx node\n"); +		pr_err("Unable to alloc dentry for ptmx node\n");  		goto out;  	} @@ -192,7 +272,7 @@ static int mknod_ptmx(struct super_block *sb)  	 */  	inode = new_inode(sb);  	if (!inode) { -		printk(KERN_ERR "Unable to alloc inode for ptmx node\n"); +		pr_err("Unable to alloc inode for ptmx node\n");  		dput(dentry);  		goto out;  	} @@ -202,6 +282,8 @@ static int mknod_ptmx(struct super_block *sb)  	mode = S_IFCHR|opts->ptmxmode;  	init_special_inode(inode, mode, MKDEV(TTYAUX_MAJOR, 2)); +	inode->i_uid = root_uid; +	inode->i_gid = root_gid;  	d_add(dentry, inode); @@ -223,7 +305,7 @@ static void update_ptmx_mode(struct pts_fs_info *fsi)  #else  static inline void update_ptmx_mode(struct pts_fs_info *fsi)  { -       return; +	return;  }  #endif @@ -233,6 +315,7 @@ static int devpts_remount(struct super_block *sb, int *flags, char *data)  	struct pts_fs_info *fsi = DEVPTS_SB(sb);  	struct pts_mount_opts *opts = &fsi->mount_opts; +	sync_filesystem(sb);  	err = parse_mount_options(data, PARSE_REMOUNT, opts);  	/* @@ -246,18 +329,22 @@ static int devpts_remount(struct super_block *sb, int *flags, char *data)  	return err;  } -static int devpts_show_options(struct seq_file *seq, struct vfsmount *vfs) +static int devpts_show_options(struct seq_file *seq, struct dentry *root)  { -	struct pts_fs_info *fsi = DEVPTS_SB(vfs->mnt_sb); +	struct pts_fs_info *fsi = DEVPTS_SB(root->d_sb);  	struct pts_mount_opts *opts = &fsi->mount_opts;  	if (opts->setuid) -		seq_printf(seq, ",uid=%u", opts->uid); +		seq_printf(seq, ",uid=%u", +			   from_kuid_munged(&init_user_ns, opts->uid));  	if (opts->setgid) -		seq_printf(seq, ",gid=%u", opts->gid); +		seq_printf(seq, ",gid=%u", +			   from_kgid_munged(&init_user_ns, opts->gid));  	seq_printf(seq, ",mode=%03o", opts->mode);  #ifdef CONFIG_DEVPTS_MULTIPLE_INSTANCES  	seq_printf(seq, ",ptmxmode=%03o", opts->ptmxmode); +	if (opts->max < NR_UNIX98_PTY_MAX) +		seq_printf(seq, ",max=%d", opts->max);  #endif  	return 0; @@ -301,23 +388,20 @@ devpts_fill_super(struct super_block *s, void *data, int silent)  	inode = new_inode(s);  	if (!inode) -		goto free_fsi; +		goto fail;  	inode->i_ino = 1;  	inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;  	inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO | S_IWUSR;  	inode->i_op = &simple_dir_inode_operations;  	inode->i_fop = &simple_dir_operations; -	inode->i_nlink = 2; +	set_nlink(inode, 2); -	s->s_root = d_alloc_root(inode); +	s->s_root = d_make_root(inode);  	if (s->s_root)  		return 0; -	printk(KERN_ERR "devpts: get root dentry failed\n"); -	iput(inode); +	pr_err("get root dentry failed\n"); -free_fsi: -	kfree(s->s_fs_info);  fail:  	return -ENOMEM;  } @@ -368,16 +452,22 @@ static struct dentry *devpts_mount(struct file_system_type *fs_type,  	if (error)  		return ERR_PTR(error); +	/* Require newinstance for all user namespace mounts to ensure +	 * the mount options are not changed. +	 */ +	if ((current_user_ns() != &init_user_ns) && !opts.newinstance) +		return ERR_PTR(-EINVAL); +  	if (opts.newinstance) -		s = sget(fs_type, NULL, set_anon_super, NULL); +		s = sget(fs_type, NULL, set_anon_super, flags, NULL);  	else -		s = sget(fs_type, compare_init_pts_sb, set_anon_super, NULL); +		s = sget(fs_type, compare_init_pts_sb, set_anon_super, flags, +			 NULL);  	if (IS_ERR(s))  		return ERR_CAST(s);  	if (!s->s_root) { -		s->s_flags = flags;  		error = devpts_fill_super(s, data, flags & MS_SILENT ? 1 : 0);  		if (error)  			goto out_undo_sget; @@ -413,6 +503,7 @@ static void devpts_kill_sb(struct super_block *sb)  {  	struct pts_fs_info *fsi = DEVPTS_SB(sb); +	ida_destroy(&fsi->allocated_ptys);  	kfree(fsi);  	kill_litter_super(sb);  } @@ -421,6 +512,9 @@ static struct file_system_type devpts_fs_type = {  	.name		= "devpts",  	.mount		= devpts_mount,  	.kill_sb	= devpts_kill_sb, +#ifdef CONFIG_DEVPTS_MULTIPLE_INSTANCES +	.fs_flags	= FS_USERNS_MOUNT | FS_USERNS_DEV_MOUNT, +#endif  };  /* @@ -440,6 +534,12 @@ retry:  		return -ENOMEM;  	mutex_lock(&allocated_ptys_lock); +	if (pty_count >= pty_limit - +			(fsi->mount_opts.newinstance ? pty_reserve : 0)) { +		mutex_unlock(&allocated_ptys_lock); +		return -ENOSPC; +	} +  	ida_ret = ida_get_new(&fsi->allocated_ptys, &index);  	if (ida_ret < 0) {  		mutex_unlock(&allocated_ptys_lock); @@ -448,11 +548,12 @@ retry:  		return -EIO;  	} -	if (index >= pty_limit) { +	if (index >= fsi->mount_opts.max) {  		ida_remove(&fsi->allocated_ptys, index);  		mutex_unlock(&allocated_ptys_lock); -		return -EIO; +		return -ENOSPC;  	} +	pty_count++;  	mutex_unlock(&allocated_ptys_lock);  	return index;  } @@ -464,57 +565,69 @@ void devpts_kill_index(struct inode *ptmx_inode, int idx)  	mutex_lock(&allocated_ptys_lock);  	ida_remove(&fsi->allocated_ptys, idx); +	pty_count--;  	mutex_unlock(&allocated_ptys_lock);  } -int devpts_pty_new(struct inode *ptmx_inode, struct tty_struct *tty) +/** + * devpts_pty_new -- create a new inode in /dev/pts/ + * @ptmx_inode: inode of the master + * @device: major+minor of the node to be created + * @index: used as a name of the node + * @priv: what's given back by devpts_get_priv + * + * The created inode is returned. Remove it from /dev/pts/ by devpts_pty_kill. + */ +struct inode *devpts_pty_new(struct inode *ptmx_inode, dev_t device, int index, +		void *priv)  { -	/* tty layer puts index from devpts_new_index() in here */ -	int number = tty->index; -	struct tty_driver *driver = tty->driver; -	dev_t device = MKDEV(driver->major, driver->minor_start+number);  	struct dentry *dentry;  	struct super_block *sb = pts_sb_from_inode(ptmx_inode); -	struct inode *inode = new_inode(sb); +	struct inode *inode;  	struct dentry *root = sb->s_root;  	struct pts_fs_info *fsi = DEVPTS_SB(sb);  	struct pts_mount_opts *opts = &fsi->mount_opts;  	char s[12]; -	/* We're supposed to be given the slave end of a pty */ -	BUG_ON(driver->type != TTY_DRIVER_TYPE_PTY); -	BUG_ON(driver->subtype != PTY_TYPE_SLAVE); - +	inode = new_inode(sb);  	if (!inode) -		return -ENOMEM; +		return ERR_PTR(-ENOMEM); -	inode->i_ino = number + 3; +	inode->i_ino = index + 3;  	inode->i_uid = opts->setuid ? opts->uid : current_fsuid();  	inode->i_gid = opts->setgid ? opts->gid : current_fsgid();  	inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;  	init_special_inode(inode, S_IFCHR|opts->mode, device); -	inode->i_private = tty; -	tty->driver_data = inode; +	inode->i_private = priv; -	sprintf(s, "%d", number); +	sprintf(s, "%d", index);  	mutex_lock(&root->d_inode->i_mutex);  	dentry = d_alloc_name(root, s); -	if (!IS_ERR(dentry)) { +	if (dentry) {  		d_add(dentry, inode);  		fsnotify_create(root->d_inode, dentry); +	} else { +		iput(inode); +		inode = ERR_PTR(-ENOMEM);  	}  	mutex_unlock(&root->d_inode->i_mutex); -	return 0; +	return inode;  } -struct tty_struct *devpts_get_tty(struct inode *pts_inode, int number) +/** + * devpts_get_priv -- get private data for a slave + * @pts_inode: inode of the slave + * + * Returns whatever was passed as priv in devpts_pty_new for a given inode. + */ +void *devpts_get_priv(struct inode *pts_inode)  {  	struct dentry *dentry; -	struct tty_struct *tty; +	void *priv = NULL;  	BUG_ON(pts_inode->i_rdev == MKDEV(TTYAUX_MAJOR, PTMX_MINOR)); @@ -523,18 +636,22 @@ struct tty_struct *devpts_get_tty(struct inode *pts_inode, int number)  	if (!dentry)  		return NULL; -	tty = NULL;  	if (pts_inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC) -		tty = (struct tty_struct *)pts_inode->i_private; +		priv = pts_inode->i_private;  	dput(dentry); -	return tty; +	return priv;  } -void devpts_pty_kill(struct tty_struct *tty) +/** + * devpts_pty_kill -- remove inode form /dev/pts/ + * @inode: inode of the slave to be removed + * + * This is an inverse operation of devpts_pty_new. + */ +void devpts_pty_kill(struct inode *inode)  { -	struct inode *inode = tty->driver_data;  	struct super_block *sb = pts_sb_from_inode(inode);  	struct dentry *root = sb->s_root;  	struct dentry *dentry; @@ -544,28 +661,27 @@ void devpts_pty_kill(struct tty_struct *tty)  	mutex_lock(&root->d_inode->i_mutex);  	dentry = d_find_alias(inode); -	if (IS_ERR(dentry)) -		goto out; - -	if (dentry) { -		inode->i_nlink--; -		d_delete(dentry); -		dput(dentry);	/* d_alloc_name() in devpts_pty_new() */ -	} +	drop_nlink(inode); +	d_delete(dentry); +	dput(dentry);	/* d_alloc_name() in devpts_pty_new() */  	dput(dentry);		/* d_find_alias above */ -out: +  	mutex_unlock(&root->d_inode->i_mutex);  }  static int __init init_devpts_fs(void)  {  	int err = register_filesystem(&devpts_fs_type); +	struct ctl_table_header *table; +  	if (!err) { +		table = register_sysctl_table(pty_root_table);  		devpts_mnt = kern_mount(&devpts_fs_type);  		if (IS_ERR(devpts_mnt)) {  			err = PTR_ERR(devpts_mnt);  			unregister_filesystem(&devpts_fs_type); +			unregister_sysctl_table(table);  		}  	}  	return err;  | 
