diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 6ed8bc313db0..7dc7df101286 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -468,6 +468,14 @@ static int pde_set_flags(struct proc_dir_entry *pde)
if (proc_ops->proc_lseek)
pde->flags |= PROC_ENTRY_proc_lseek;
}
+ if (proc_ops->proc_write_buf) {
+ if (WARN_ON_ONCE(proc_ops->proc_write))
+ return -EINVAL;
+ pde->flags |= PROC_ENTRY_proc_write_buf;
+ pde->write_size = proc_ops->proc_write_size ?: PAGE_SIZE - 1;
+ if (proc_ops->proc_flags & PROC_ENTRY_SHORT_WRITES)
+ pde->flags |= PROC_ENTRY_SHORT_WRITES;
+ }
return 0;
}
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 8425465aae5d..c0bb40e7ff96 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -440,11 +440,44 @@ static ssize_t pde_write(struct proc_dir_entry *pde, struct file *file, const ch
return -EIO;
}
+/*
+ * procfs copies in what is written before it pins the entry. A writer that
+ * faults on its buffer then doesn't hold up remove_proc_entry().
+ */
+static ssize_t pde_write_buf(struct proc_dir_entry *pde, struct file *file,
+ const char __user *ubuf, size_t count, loff_t *ppos)
+{
+ char *buf __free(kvfree) = NULL;
+ size_t size;
+ ssize_t rv;
+
+ size = pde->write_size;
+ if (count > size) {
+ if (!pde_has_short_writes(pde))
+ return -EINVAL;
+ count = size;
+ }
+ buf = kvmalloc(count + 1, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ if (copy_from_user(buf, ubuf, count))
+ return -EFAULT;
+ buf[count] = '\0';
+
+ if (!pde_pin(pde))
+ return -EIO;
+ rv = pde->proc_ops->proc_write_buf(file, buf, count, ppos);
+ pde_unpin(pde);
+ return rv;
+}
+
static ssize_t proc_reg_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
struct proc_dir_entry *pde = PDE(file_inode(file));
ssize_t rv;
+ if (pde_has_proc_write_buf(pde))
+ return pde_write_buf(pde, file, buf, count, ppos);
if (!pde_pin(pde))
return -EIO;
rv = pde_write(pde, file, buf, count, ppos);diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index d8093451524a..80b346a3299d 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -51,6 +51,7 @@ struct proc_dir_entry {
proc_write_t write;
void *data;
unsigned int state_size;
+ unsigned int write_size;
unsigned int low_ino;
nlink_t nlink;
kuid_t uid;@@ -107,6 +108,16 @@ static inline bool pde_has_proc_show(const struct proc_dir_entry *pde)
return pde->flags & PROC_ENTRY_proc_show;
}
+static inline bool pde_has_proc_write_buf(const struct proc_dir_entry *pde)
+{
+ return pde->flags & PROC_ENTRY_proc_write_buf;
+}
+
+static inline bool pde_has_short_writes(const struct proc_dir_entry *pde)
+{
+ return pde->flags & PROC_ENTRY_SHORT_WRITES;
+}
+
/* procfs owns the seq_file, see pde_seq_ops */
static inline bool pde_is_seq_file(const struct proc_dir_entry *pde)
{diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index ea55ed85a2d6..c81c93325044 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -31,6 +31,13 @@ enum {
PROC_ENTRY_proc_show = 1U << 5,
PROC_ENTRY_FORCE_LOOKUP = 1U << 7,
+ PROC_ENTRY_proc_write_buf = 1U << 8,
+
+ /*
+ * Writes larger than ->proc_write_size come up short instead of
+ * failing with -EINVAL.
+ */
+ PROC_ENTRY_SHORT_WRITES = 1U << 9,
};
struct proc_ops {@@ -67,6 +74,16 @@ struct proc_ops {
const struct seq_operations *proc_seq_ops;
int (*proc_show)(struct seq_file *, void *);
unsigned int proc_state_size;
+ /*
+ * procfs copies what is written into a kernel buffer and calls
+ * ->proc_write_buf() with it. The buffer is NUL-terminated and holds
+ * at most ->proc_write_size bytes, PAGE_SIZE - 1 if that's 0. Larger
+ * writes fail with -EINVAL unless the entry asks for
+ * PROC_ENTRY_SHORT_WRITES. The position is NULL for a stream, like
+ * for ->write().
+ */
+ ssize_t (*proc_write_buf)(struct file *, char *, size_t, loff_t *);
+ unsigned int proc_write_size;
} __randomize_layout;
/* definitions for hide_pid field */
--
2.53.0