diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 83d273778a60..fc4d10c92d7c 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -476,6 +476,15 @@ static int pde_set_flags(struct proc_dir_entry *pde)
if (proc_ops->proc_flags & PROC_ENTRY_SHORT_WRITES)
pde->flags |= PROC_ENTRY_SHORT_WRITES;
}
+ if (proc_ops->proc_read_buf) {
+ if (WARN_ON_ONCE(pde_is_seq_file(pde) || proc_ops->proc_read ||
+ proc_ops->proc_read_iter))
+ return -EINVAL;
+ pde->flags |= PROC_ENTRY_proc_read_buf;
+ pde->read_size = proc_ops->proc_read_size ?: PAGE_SIZE;
+ if (proc_ops->proc_flags & PROC_ENTRY_FULL_READS)
+ pde->flags |= PROC_ENTRY_FULL_READS;
+ }
return 0;
}
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index c0bb40e7ff96..3f29a1663d26 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -23,6 +23,8 @@
#include <linux/sysctl.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
+#include <linux/sched/signal.h>
+#include <linux/uio.h>
#include <linux/mount.h>
#include <linux/bug.h>
@@ -400,11 +402,67 @@ static loff_t proc_reg_llseek(struct file *file, loff_t offset, int whence)
return rv;
}
+/*
+ * The entry fills a kernel buffer and procfs copies that to userspace after
+ * unpinning the entry. A reader that faults on its buffer then doesn't hold
+ * up remove_proc_entry().
+ */
+static ssize_t pde_read_buf(struct proc_dir_entry *pde, struct kiocb *iocb,
+ struct iov_iter *iter)
+{
+ struct file *file = iocb->ki_filp;
+ char *buf __free(kvfree) = NULL;
+ ssize_t rv, done = 0;
+ size_t len, count;
+ loff_t pos;
+
+ if (!iov_iter_count(iter))
+ return 0;
+ /* neither the allocation nor the entry can honor IOCB_NOWAIT */
+ if ((iocb->ki_flags & IOCB_NOWAIT) && !(file->f_flags & O_NONBLOCK))
+ return -EAGAIN;
+ len = min_t(size_t, iov_iter_count(iter), pde->read_size);
+ buf = kvmalloc(len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* PROC_ENTRY_FULL_READS fills the read in pieces */
+ do {
+ count = min_t(size_t, iov_iter_count(iter), len);
+ pos = iocb->ki_pos;
+ if (!pde_pin(pde)) {
+ rv = -EIO;
+ break;
+ }
+ rv = pde->proc_ops->proc_read_buf(file, buf, count, &pos);
+ pde_unpin(pde);
+ if (rv <= 0)
+ break;
+ if (WARN_ON_ONCE(rv > count)) {
+ rv = -EIO;
+ break;
+ }
+ if (copy_to_iter(buf, rv, iter) != rv) {
+ rv = -EFAULT;
+ break;
+ }
+ iocb->ki_pos = pos;
+ done += rv;
+ if (!pde_has_full_reads(pde) || rv < count)
+ break;
+ cond_resched();
+ } while (iov_iter_count(iter) && !fatal_signal_pending(current));
+
+ return done ?: rv;
+}
+
static ssize_t proc_reg_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
struct proc_dir_entry *pde = PDE(file_inode(iocb->ki_filp));
ssize_t ret;
+ if (pde_has_proc_read_buf(pde))
+ return pde_read_buf(pde, iocb, iter);
if (!pde_pin(pde))
return -EIO;
ret = pde->proc_ops->proc_read_iter(iocb, iter);@@ -814,7 +872,7 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
inode->i_op = de->proc_iops;
if (pde_is_seq_file(de))
inode->i_fop = &proc_seq_file_ops;
- else if (pde_has_proc_read_iter(de))
+ else if (pde_has_proc_read_iter(de) || pde_has_proc_read_buf(de))
inode->i_fop = &proc_iter_file_ops;
else
inode->i_fop = &proc_reg_file_ops;
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index ed22c6dfb0bf..11bd3fa07705 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -52,6 +52,7 @@ struct proc_dir_entry {
void *data;
unsigned int state_size;
unsigned int write_size;
+ unsigned int read_size;
unsigned int low_ino;
nlink_t nlink;
kuid_t uid;@@ -118,6 +119,16 @@ static inline bool pde_has_short_writes(const struct proc_dir_entry *pde)
return pde->flags & PROC_ENTRY_SHORT_WRITES;
}
+static inline bool pde_has_proc_read_buf(const struct proc_dir_entry *pde)
+{
+ return pde->flags & PROC_ENTRY_proc_read_buf;
+}
+
+static inline bool pde_has_full_reads(const struct proc_dir_entry *pde)
+{
+ return pde->flags & PROC_ENTRY_FULL_READS;
+}
+
/* 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 c81c93325044..0f0997898ba3 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -38,6 +38,13 @@ enum {
* failing with -EINVAL.
*/
PROC_ENTRY_SHORT_WRITES = 1U << 9,
+ PROC_ENTRY_proc_read_buf = 1U << 10,
+
+ /*
+ * Reads larger than ->proc_read_size are filled in pieces instead of
+ * coming up short.
+ */
+ PROC_ENTRY_FULL_READS = 1U << 11,
};
struct proc_ops {@@ -84,6 +91,15 @@ struct proc_ops {
*/
ssize_t (*proc_write_buf)(struct file *, char *, size_t, loff_t *);
unsigned int proc_write_size;
+ /*
+ * procfs calls ->proc_read_buf() to fill a kernel buffer of at most
+ * ->proc_read_size bytes, PAGE_SIZE if that's 0, and copies it to
+ * userspace afterwards. Larger reads come up short unless the entry
+ * asks for PROC_ENTRY_FULL_READS. The position the entry moves to only
+ * sticks if the copy works.
+ */
+ ssize_t (*proc_read_buf)(struct file *, char *, size_t, loff_t *);
+ unsigned int proc_read_size;
} __randomize_layout;
/* definitions for hide_pid field */
--
2.53.0