diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 761f9958558f..eff5c38e2219 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -434,19 +434,41 @@ static const struct inode_operations proc_dir_inode_operations = {
.setattr = proc_setattr,
};
-static void pde_set_flags(struct proc_dir_entry *pde)
+static int pde_set_flags(struct proc_dir_entry *pde)
{
const struct proc_ops *proc_ops = pde->proc_ops;
if (!proc_ops)
- return;
+ return 0;
if (proc_ops->proc_flags & PROC_ENTRY_PERMANENT)
pde->flags |= PROC_ENTRY_PERMANENT;
- if (proc_ops->proc_read_iter)
- pde->flags |= PROC_ENTRY_proc_read_iter;
- if (proc_ops->proc_lseek)
+ if (proc_ops->proc_seq_ops) {
+ pde->seq_ops = proc_ops->proc_seq_ops;
+ pde->state_size = proc_ops->proc_state_size;
+ pde->flags |= PROC_ENTRY_proc_seq_ops;
+ } else if (proc_ops->proc_show) {
+ pde->single_show = proc_ops->proc_show;
+ pde->flags |= PROC_ENTRY_proc_show;
+ }
+ if (pde_is_seq_file(pde)) {
+ /* procfs reads and seeks through the seq_file itself */
+ if (WARN_ON_ONCE(proc_ops->proc_read || proc_ops->proc_read_iter ||
+ proc_ops->proc_lseek))
+ return -EINVAL;
+ /* and frees it, see pde_seq_release() */
+ if (WARN_ON_ONCE(proc_ops->proc_release == seq_release ||
+ proc_ops->proc_release == seq_release_private ||
+ proc_ops->proc_release == single_release))
+ return -EINVAL;
pde->flags |= PROC_ENTRY_proc_lseek;
+ } else {
+ if (proc_ops->proc_read_iter)
+ pde->flags |= PROC_ENTRY_proc_read_iter;
+ if (proc_ops->proc_lseek)
+ pde->flags |= PROC_ENTRY_proc_lseek;
+ }
+ return 0;
}
/* returns the registered entry, or frees dp and returns NULL on failure */@@ -456,8 +478,8 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
if (proc_alloc_inum(&dp->low_ino))
goto out_free_entry;
- if (!S_ISDIR(dp->mode))
- pde_set_flags(dp);
+ if (!S_ISDIR(dp->mode) && pde_set_flags(dp))
+ goto out_free_inum;
write_lock(&proc_subdir_lock);
dp->parent = dir;
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 66a3688757dd..8425465aae5d 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -218,6 +218,110 @@ static void pde_unpin(struct proc_dir_entry *pde)
unuse_pde(pde);
}
+/*
+ * procfs owns the seq_file of a seq_file entry and only calls into the entry
+ * with it pinned. A read copies to userspace without the pin, so a reader
+ * that faults on its buffer doesn't hold up remove_proc_entry().
+ *
+ * An entry that only has a show function is iterated like single_open() does
+ * it, see kernfs_seq_start().
+ *
+ * Like ERR_PTR(-ENODEV) in kernfs_seq_start(), ERR_PTR(-EIO) means the entry
+ * isn't pinned. If the entry returns it itself, it is unpinned right away.
+ */
+static struct proc_dir_entry *seq_pde(struct seq_file *m)
+{
+ return PDE(file_inode(m->file));
+}
+
+static void pde_seq_stop_pinned(struct seq_file *m, void *v)
+{
+ struct proc_dir_entry *pde = seq_pde(m);
+
+ if (pde_has_proc_seq_ops(pde))
+ pde->seq_ops->stop(m, v);
+ pde_unpin(pde);
+}
+
+static void *pde_seq_start(struct seq_file *m, loff_t *pos)
+{
+ struct proc_dir_entry *pde = seq_pde(m);
+ void *v;
+
+ if (!pde_pin(pde))
+ return ERR_PTR(-EIO);
+ if (!pde_has_proc_seq_ops(pde))
+ return single_start(m, pos);
+ v = pde->seq_ops->start(m, pos);
+ if (v == ERR_PTR(-EIO))
+ pde_seq_stop_pinned(m, v);
+ return v;
+}
+
+static void *pde_seq_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ struct proc_dir_entry *pde = seq_pde(m);
+ loff_t old = *pos;
+
+ if (!pde_has_proc_seq_ops(pde)) {
+ ++*pos;
+ return NULL;
+ }
+ v = pde->seq_ops->next(m, v, pos);
+ if (v == ERR_PTR(-EIO))
+ pde_seq_stop_pinned(m, v);
+ /* seq_file would blame these seq_operations, blame the entry's */
+ if (*pos == old) {
+ pr_info_ratelimited("buggy .next function %ps did not update position index\n",
+ pde->seq_ops->next);
+ ++*pos;
+ }
+ return v;
+}
+
+static void pde_seq_stop(struct seq_file *m, void *v)
+{
+ if (v != ERR_PTR(-EIO))
+ pde_seq_stop_pinned(m, v);
+}
+
+static int pde_seq_show(struct seq_file *m, void *v)
+{
+ struct proc_dir_entry *pde = seq_pde(m);
+
+ if (pde_has_proc_seq_ops(pde))
+ return pde->seq_ops->show(m, v);
+ return pde->single_show(m, v);
+}
+
+static const struct seq_operations pde_seq_ops = {
+ .start = pde_seq_start,
+ .next = pde_seq_next,
+ .stop = pde_seq_stop,
+ .show = pde_seq_show,
+};
+
+static int pde_seq_open(struct proc_dir_entry *pde, struct file *file)
+{
+ int err;
+
+ if (pde->state_size)
+ return seq_open_private(file, &pde_seq_ops, pde->state_size);
+ err = seq_open(file, &pde_seq_ops);
+ if (!err && pde_has_proc_show(pde))
+ ((struct seq_file *)file->private_data)->private = pde->data;
+ return err;
+}
+
+static void pde_seq_release(struct proc_dir_entry *pde, struct inode *inode,
+ struct file *file)
+{
+ if (pde->state_size)
+ seq_release_private(inode, file);
+ else
+ seq_release(inode, file);
+}
+
/*
* At most 2 contexts can enter this function: the one doing the last
* close on the descriptor and whoever is deleting PDE itself.@@ -474,22 +578,35 @@ static unsigned int proc_reg_mmap_capabilities(struct file *file)
}
#endif
+static int pde_open(struct proc_dir_entry *pde, struct inode *inode,
+ struct file *file)
+{
+ const auto open = pde->proc_ops->proc_open;
+ int rv;
+
+ if (!pde_is_seq_file(pde))
+ return open ? open(inode, file) : 0;
+
+ rv = pde_seq_open(pde, file);
+ if (rv || !open)
+ return rv;
+ rv = open(inode, file);
+ if (rv)
+ pde_seq_release(pde, inode, file);
+ return rv;
+}
+
static int proc_reg_open(struct inode *inode, struct file *file)
{
struct proc_dir_entry *pde = PDE(inode);
int rv = 0;
- typeof_member(struct proc_ops, proc_open) open;
struct pde_opener *pdeo;
if (!pde_has_proc_lseek(pde))
file->f_mode &= ~FMODE_LSEEK;
- if (pde_is_permanent(pde)) {
- open = pde->proc_ops->proc_open;
- if (open)
- rv = open(inode, file);
- return rv;
- }
+ if (pde_is_permanent(pde))
+ return pde_open(pde, inode, file);
/*
* Ensure that@@ -514,9 +631,7 @@ static int proc_reg_open(struct inode *inode, struct file *file)
}
}
- open = pde->proc_ops->proc_open;
- if (open)
- rv = open(inode, file);
+ rv = pde_open(pde, inode, file);
if (release) {
if (rv == 0) {@@ -544,21 +659,43 @@ static int proc_reg_release(struct inode *inode, struct file *file)
if (pde_is_permanent(pde)) {
const auto release = pde->proc_ops->proc_release;
if (release)
- return release(inode, file);
- return 0;
+ release(inode, file);
+ goto out;
}
spin_lock(&pde->pde_unload_lock);
list_for_each_entry(pdeo, &pde->pde_openers, lh) {
if (pdeo->file == file) {
close_pdeo(pde, pdeo);
- return 0;
+ goto out;
}
}
spin_unlock(&pde->pde_unload_lock);
+out:
+ /* ->proc_release() has run, here or in remove_proc_entry() */
+ if (pde_is_seq_file(pde))
+ pde_seq_release(pde, inode, file);
return 0;
}
+/* For seq_file entries, see pde_seq_ops. */
+static const struct file_operations proc_seq_file_ops = {
+ .llseek = seq_lseek,
+ .read_iter = seq_read_iter,
+ .splice_read = copy_splice_read,
+ .write = proc_reg_write,
+ .poll = proc_reg_poll,
+ .unlocked_ioctl = proc_reg_unlocked_ioctl,
+ .compat_ioctl = proc_reg_compat_ioctl,
+ .mmap = proc_reg_mmap,
+ .get_unmapped_area = proc_reg_get_unmapped_area,
+#ifndef CONFIG_MMU
+ .mmap_capabilities = proc_reg_mmap_capabilities,
+#endif
+ .open = proc_reg_open,
+ .release = proc_reg_release,
+};
+
static const struct file_operations proc_reg_file_ops = {
.llseek = proc_reg_llseek,
.read = proc_reg_read,@@ -642,7 +779,9 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
if (S_ISREG(inode->i_mode)) {
inode->i_op = de->proc_iops;
- if (pde_has_proc_read_iter(de))
+ if (pde_is_seq_file(de))
+ inode->i_fop = &proc_seq_file_ops;
+ else if (pde_has_proc_read_iter(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 41e00c978ddd..d8093451524a 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -97,6 +97,22 @@ static inline bool pde_has_proc_lseek(const struct proc_dir_entry *pde)
return pde->flags & PROC_ENTRY_proc_lseek;
}
+static inline bool pde_has_proc_seq_ops(const struct proc_dir_entry *pde)
+{
+ return pde->flags & PROC_ENTRY_proc_seq_ops;
+}
+
+static inline bool pde_has_proc_show(const struct proc_dir_entry *pde)
+{
+ return pde->flags & PROC_ENTRY_proc_show;
+}
+
+/* procfs owns the seq_file, see pde_seq_ops */
+static inline bool pde_is_seq_file(const struct proc_dir_entry *pde)
+{
+ return pde_has_proc_seq_ops(pde) || pde_has_proc_show(pde);
+}
+
extern struct kmem_cache *proc_dir_entry_cache;
void pde_free(struct proc_dir_entry *pde);
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 6ad5150020bf..ea55ed85a2d6 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -27,6 +27,8 @@ enum {
PROC_ENTRY_proc_read_iter = 1U << 1,
PROC_ENTRY_proc_lseek = 1U << 3,
+ PROC_ENTRY_proc_seq_ops = 1U << 4,
+ PROC_ENTRY_proc_show = 1U << 5,
PROC_ENTRY_FORCE_LOOKUP = 1U << 7,
};@@ -37,7 +39,10 @@ struct proc_ops {
ssize_t (*proc_read)(struct file *, char __user *, size_t, loff_t *);
ssize_t (*proc_read_iter)(struct kiocb *, struct iov_iter *);
ssize_t (*proc_write)(struct file *, const char __user *, size_t, loff_t *);
- /* mandatory unless nonseekable_open() or equivalent is used */
+ /*
+ * mandatory unless nonseekable_open() or equivalent is used, procfs
+ * seeks seq_file entries itself
+ */
loff_t (*proc_lseek)(struct file *, loff_t, int);
int (*proc_release)(struct inode *, struct file *);
__poll_t (*proc_poll)(struct file *, struct poll_table_struct *);@@ -47,6 +52,21 @@ struct proc_ops {
#endif
int (*proc_mmap)(struct file *, struct vm_area_struct *);
unsigned long (*proc_get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
+ /*
+ * For seq_file entries procfs opens the seq_file, reads and seeks
+ * through it and frees it once the file is released. ->proc_open()
+ * finds the seq_file in ->private_data and ->proc_release() only
+ * releases what ->proc_open() set up. It can run while a reader still
+ * copies out of the seq_file, so it must leave that alone. procfs
+ * allocates ->proc_state_size bytes of ->private state for
+ * ->proc_seq_ops and points ->private at the entry's data for
+ * ->proc_show(). Nothing that runs with the entry pinned may wait for
+ * something a copy to or from userspace can hold, like the seq_file's
+ * lock.
+ */
+ const struct seq_operations *proc_seq_ops;
+ int (*proc_show)(struct seq_file *, void *);
+ unsigned int proc_state_size;
} __randomize_layout;
/* definitions for hide_pid field */
--
2.53.0