summaryrefslogtreecommitdiff
path: root/fs/proc
diff options
context:
space:
mode:
Diffstat (limited to 'fs/proc')
-rw-r--r--fs/proc/generic.c26
-rw-r--r--fs/proc/inode.c5
-rw-r--r--fs/proc/internal.h20
-rw-r--r--fs/proc/proc_net.c92
-rw-r--r--fs/proc/root.c3
5 files changed, 134 insertions, 12 deletions
diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 7b4d9714f248..6ac1c92997ea 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -409,7 +409,7 @@ static struct proc_dir_entry *__proc_create(struct proc_dir_entry **parent,
if (!ent)
goto out;
- if (qstr.len + 1 <= sizeof(ent->inline_name)) {
+ if (qstr.len + 1 <= SIZEOF_PDE_INLINE_NAME) {
ent->name = ent->inline_name;
} else {
ent->name = kmalloc(qstr.len + 1, GFP_KERNEL);
@@ -740,3 +740,27 @@ void *PDE_DATA(const struct inode *inode)
return __PDE_DATA(inode);
}
EXPORT_SYMBOL(PDE_DATA);
+
+/*
+ * Pull a user buffer into memory and pass it to the file's write handler if
+ * one is supplied. The ->write() method is permitted to modify the
+ * kernel-side buffer.
+ */
+ssize_t proc_simple_write(struct file *f, const char __user *ubuf, size_t size,
+ loff_t *_pos)
+{
+ struct proc_dir_entry *pde = PDE(file_inode(f));
+ char *buf;
+ int ret;
+
+ if (!pde->write)
+ return -EACCES;
+ if (size == 0 || size > PAGE_SIZE - 1)
+ return -EINVAL;
+ buf = memdup_user_nul(ubuf, size);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+ ret = pde->write(f, buf, size);
+ kfree(buf);
+ return ret == 0 ? size : ret;
+}
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 2cf3b74391ca..85ffbd27f288 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -105,9 +105,8 @@ void __init proc_init_kmemcache(void)
kmem_cache_create("pde_opener", sizeof(struct pde_opener), 0,
SLAB_ACCOUNT|SLAB_PANIC, NULL);
proc_dir_entry_cache = kmem_cache_create_usercopy(
- "proc_dir_entry", sizeof(struct proc_dir_entry), 0, SLAB_PANIC,
- offsetof(struct proc_dir_entry, inline_name),
- sizeof_field(struct proc_dir_entry, inline_name), NULL);
+ "proc_dir_entry", SIZEOF_PDE_SLOT, 0, SLAB_PANIC,
+ OFFSETOF_PDE_NAME, SIZEOF_PDE_INLINE_NAME, NULL);
}
static int proc_show_options(struct seq_file *seq, struct dentry *root)
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 50cb22a08c2f..da3dbfa09e79 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -48,6 +48,7 @@ struct proc_dir_entry {
const struct seq_operations *seq_ops;
int (*single_show)(struct seq_file *, void *);
};
+ proc_write_t write;
void *data;
unsigned int state_size;
unsigned int low_ino;
@@ -61,14 +62,20 @@ struct proc_dir_entry {
char *name;
umode_t mode;
u8 namelen;
-#ifdef CONFIG_64BIT
-#define SIZEOF_PDE_INLINE_NAME (192-155)
-#else
-#define SIZEOF_PDE_INLINE_NAME (128-95)
-#endif
- char inline_name[SIZEOF_PDE_INLINE_NAME];
+ char inline_name[];
} __randomize_layout;
+#define OFFSETOF_PDE_NAME offsetof(struct proc_dir_entry, inline_name)
+#define SIZEOF_PDE_SLOT \
+ (OFFSETOF_PDE_NAME + 34 <= 64 ? 64 : \
+ OFFSETOF_PDE_NAME + 34 <= 128 ? 128 : \
+ OFFSETOF_PDE_NAME + 34 <= 192 ? 192 : \
+ OFFSETOF_PDE_NAME + 34 <= 256 ? 256 : \
+ OFFSETOF_PDE_NAME + 34 <= 512 ? 512 : \
+ 0)
+
+#define SIZEOF_PDE_INLINE_NAME (SIZEOF_PDE_SLOT - OFFSETOF_PDE_NAME)
+
extern struct kmem_cache *proc_dir_entry_cache;
void pde_free(struct proc_dir_entry *pde);
@@ -189,6 +196,7 @@ static inline bool is_empty_pde(const struct proc_dir_entry *pde)
{
return S_ISDIR(pde->mode) && !pde->proc_iops;
}
+extern ssize_t proc_simple_write(struct file *, const char __user *, size_t, loff_t *);
/*
* inode.c
diff --git a/fs/proc/proc_net.c b/fs/proc/proc_net.c
index 7d94fa005b0d..d5e0fcb3439e 100644
--- a/fs/proc/proc_net.c
+++ b/fs/proc/proc_net.c
@@ -46,6 +46,9 @@ static int seq_open_net(struct inode *inode, struct file *file)
WARN_ON_ONCE(state_size < sizeof(*p));
+ if (file->f_mode & FMODE_WRITE && !PDE(inode)->write)
+ return -EACCES;
+
net = get_proc_net(inode);
if (!net)
return -ENXIO;
@@ -73,6 +76,7 @@ static int seq_release_net(struct inode *ino, struct file *f)
static const struct file_operations proc_net_seq_fops = {
.open = seq_open_net,
.read = seq_read,
+ .write = proc_simple_write,
.llseek = seq_lseek,
.release = seq_release_net,
};
@@ -93,6 +97,50 @@ struct proc_dir_entry *proc_create_net_data(const char *name, umode_t mode,
}
EXPORT_SYMBOL_GPL(proc_create_net_data);
+/**
+ * proc_create_net_data_write - Create a writable net_ns-specific proc file
+ * @name: The name of the file.
+ * @mode: The file's access mode.
+ * @parent: The parent directory in which to create.
+ * @ops: The seq_file ops with which to read the file.
+ * @write: The write method which which to 'modify' the file.
+ * @data: Data for retrieval by PDE_DATA().
+ *
+ * Create a network namespaced proc file in the @parent directory with the
+ * specified @name and @mode that allows reading of a file that displays a
+ * series of elements and also provides for the file accepting writes that have
+ * some arbitrary effect.
+ *
+ * The functions in the @ops table are used to iterate over items to be
+ * presented and extract the readable content using the seq_file interface.
+ *
+ * The @write function is called with the data copied into a kernel space
+ * scratch buffer and has a NUL appended for convenience. The buffer may be
+ * modified by the @write function. @write should return 0 on success.
+ *
+ * The @data value is accessible from the @show and @write functions by calling
+ * PDE_DATA() on the file inode. The network namespace must be accessed by
+ * calling seq_file_net() on the seq_file struct.
+ */
+struct proc_dir_entry *proc_create_net_data_write(const char *name, umode_t mode,
+ struct proc_dir_entry *parent,
+ const struct seq_operations *ops,
+ proc_write_t write,
+ unsigned int state_size, void *data)
+{
+ struct proc_dir_entry *p;
+
+ p = proc_create_reg(name, mode, &parent, data);
+ if (!p)
+ return NULL;
+ p->proc_fops = &proc_net_seq_fops;
+ p->seq_ops = ops;
+ p->state_size = state_size;
+ p->write = write;
+ return proc_register(parent, p);
+}
+EXPORT_SYMBOL_GPL(proc_create_net_data_write);
+
static int single_open_net(struct inode *inode, struct file *file)
{
struct proc_dir_entry *de = PDE(inode);
@@ -119,6 +167,7 @@ static int single_release_net(struct inode *ino, struct file *f)
static const struct file_operations proc_net_single_fops = {
.open = single_open_net,
.read = seq_read,
+ .write = proc_simple_write,
.llseek = seq_lseek,
.release = single_release_net,
};
@@ -138,6 +187,49 @@ struct proc_dir_entry *proc_create_net_single(const char *name, umode_t mode,
}
EXPORT_SYMBOL_GPL(proc_create_net_single);
+/**
+ * proc_create_net_single_write - Create a writable net_ns-specific proc file
+ * @name: The name of the file.
+ * @mode: The file's access mode.
+ * @parent: The parent directory in which to create.
+ * @show: The seqfile show method with which to read the file.
+ * @write: The write method which which to 'modify' the file.
+ * @data: Data for retrieval by PDE_DATA().
+ *
+ * Create a network-namespaced proc file in the @parent directory with the
+ * specified @name and @mode that allows reading of a file that displays a
+ * single element rather than a series and also provides for the file accepting
+ * writes that have some arbitrary effect.
+ *
+ * The @show function is called to extract the readable content via the
+ * seq_file interface.
+ *
+ * The @write function is called with the data copied into a kernel space
+ * scratch buffer and has a NUL appended for convenience. The buffer may be
+ * modified by the @write function. @write should return 0 on success.
+ *
+ * The @data value is accessible from the @show and @write functions by calling
+ * PDE_DATA() on the file inode. The network namespace must be accessed by
+ * calling seq_file_single_net() on the seq_file struct.
+ */
+struct proc_dir_entry *proc_create_net_single_write(const char *name, umode_t mode,
+ struct proc_dir_entry *parent,
+ int (*show)(struct seq_file *, void *),
+ proc_write_t write,
+ void *data)
+{
+ struct proc_dir_entry *p;
+
+ p = proc_create_reg(name, mode, &parent, data);
+ if (!p)
+ return NULL;
+ p->proc_fops = &proc_net_single_fops;
+ p->single_show = show;
+ p->write = write;
+ return proc_register(parent, p);
+}
+EXPORT_SYMBOL_GPL(proc_create_net_single_write);
+
static struct net *get_proc_task_net(struct inode *dir)
{
struct task_struct *task;
diff --git a/fs/proc/root.c b/fs/proc/root.c
index 61b7340b357a..f4b1a9d2eca6 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -204,8 +204,7 @@ struct proc_dir_entry proc_root = {
.proc_fops = &proc_root_operations,
.parent = &proc_root,
.subdir = RB_ROOT,
- .name = proc_root.inline_name,
- .inline_name = "/proc",
+ .name = "/proc",
};
int pid_ns_prepare_proc(struct pid_namespace *ns)