summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/devpts/inode.c85
1 files changed, 81 insertions, 4 deletions
diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c
index c4e2a58a2e82..1c6f908e38ca 100644
--- a/fs/devpts/inode.c
+++ b/fs/devpts/inode.c
@@ -36,7 +36,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;
@@ -49,10 +103,11 @@ struct pts_mount_opts {
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 +118,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}
};
@@ -109,6 +165,7 @@ static int parse_mount_options(char *data, int op, struct pts_mount_opts *opts)
opts->gid = 0;
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)
@@ -152,6 +209,12 @@ 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");
@@ -258,6 +321,8 @@ static int devpts_show_options(struct seq_file *seq, struct dentry *root)
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;
@@ -438,6 +503,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);
@@ -446,11 +517,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;
}
@@ -462,6 +534,7 @@ 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);
}
@@ -558,11 +631,15 @@ void devpts_pty_kill(struct tty_struct *tty)
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;