--- /dev/null
+From 9957078a9f6ed9bfe32ee6330522f0d5b44c7022 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 4 Dec 2019 16:49:59 -0800
+Subject: proc: change ->nlink under proc_subdir_lock
+
+From: Alexey Dobriyan <adobriyan@gmail.com>
+
+[ Upstream commit e06689bf57017ac022ccf0f2a5071f760821ce0f ]
+
+Currently gluing PDE into global /proc tree is done under lock, but
+changing ->nlink is not. Additionally struct proc_dir_entry::nlink is
+not atomic so updates can be lost.
+
+Link: http://lkml.kernel.org/r/20190925202436.GA17388@avx2
+Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
+Cc: Al Viro <viro@zeniv.linux.org.uk>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/proc/generic.c | 31 +++++++++++++++----------------
+ 1 file changed, 15 insertions(+), 16 deletions(-)
+
+diff --git a/fs/proc/generic.c b/fs/proc/generic.c
+index 64e9ee1b129e2..d4f353187d67c 100644
+--- a/fs/proc/generic.c
++++ b/fs/proc/generic.c
+@@ -138,8 +138,12 @@ static int proc_getattr(const struct path *path, struct kstat *stat,
+ {
+ struct inode *inode = d_inode(path->dentry);
+ struct proc_dir_entry *de = PDE(inode);
+- if (de && de->nlink)
+- set_nlink(inode, de->nlink);
++ if (de) {
++ nlink_t nlink = READ_ONCE(de->nlink);
++ if (nlink > 0) {
++ set_nlink(inode, nlink);
++ }
++ }
+
+ generic_fillattr(inode, stat);
+ return 0;
+@@ -362,6 +366,7 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
+ write_unlock(&proc_subdir_lock);
+ goto out_free_inum;
+ }
++ dir->nlink++;
+ write_unlock(&proc_subdir_lock);
+
+ return dp;
+@@ -472,10 +477,7 @@ struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode,
+ ent->data = data;
+ ent->proc_fops = &proc_dir_operations;
+ ent->proc_iops = &proc_dir_inode_operations;
+- parent->nlink++;
+ ent = proc_register(parent, ent);
+- if (!ent)
+- parent->nlink--;
+ }
+ return ent;
+ }
+@@ -505,10 +507,7 @@ struct proc_dir_entry *proc_create_mount_point(const char *name)
+ ent->data = NULL;
+ ent->proc_fops = NULL;
+ ent->proc_iops = NULL;
+- parent->nlink++;
+ ent = proc_register(parent, ent);
+- if (!ent)
+- parent->nlink--;
+ }
+ return ent;
+ }
+@@ -666,8 +665,12 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
+ len = strlen(fn);
+
+ de = pde_subdir_find(parent, fn, len);
+- if (de)
++ if (de) {
+ rb_erase(&de->subdir_node, &parent->subdir);
++ if (S_ISDIR(de->mode)) {
++ parent->nlink--;
++ }
++ }
+ write_unlock(&proc_subdir_lock);
+ if (!de) {
+ WARN(1, "name '%s'\n", name);
+@@ -676,9 +679,6 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
+
+ proc_entry_rundown(de);
+
+- if (S_ISDIR(de->mode))
+- parent->nlink--;
+- de->nlink = 0;
+ WARN(pde_subdir_first(de),
+ "%s: removing non-empty directory '%s/%s', leaking at least '%s'\n",
+ __func__, de->parent->name, de->name, pde_subdir_first(de)->name);
+@@ -714,13 +714,12 @@ int remove_proc_subtree(const char *name, struct proc_dir_entry *parent)
+ de = next;
+ continue;
+ }
+- write_unlock(&proc_subdir_lock);
+-
+- proc_entry_rundown(de);
+ next = de->parent;
+ if (S_ISDIR(de->mode))
+ next->nlink--;
+- de->nlink = 0;
++ write_unlock(&proc_subdir_lock);
++
++ proc_entry_rundown(de);
+ if (de == root)
+ break;
+ pde_put(de);
+--
+2.27.0
+
--- /dev/null
+From 1718d51d68e7eb566ffc1d972a22b68266f992bd Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 15 Dec 2020 20:42:39 -0800
+Subject: proc: fix lookup in /proc/net subdirectories after setns(2)
+
+From: Alexey Dobriyan <adobriyan@gmail.com>
+
+[ Upstream commit c6c75deda81344c3a95d1d1f606d5cee109e5d54 ]
+
+Commit 1fde6f21d90f ("proc: fix /proc/net/* after setns(2)") only forced
+revalidation of regular files under /proc/net/
+
+However, /proc/net/ is unusual in the sense of /proc/net/foo handlers
+take netns pointer from parent directory which is old netns.
+
+Steps to reproduce:
+
+ (void)open("/proc/net/sctp/snmp", O_RDONLY);
+ unshare(CLONE_NEWNET);
+
+ int fd = open("/proc/net/sctp/snmp", O_RDONLY);
+ read(fd, &c, 1);
+
+Read will read wrong data from original netns.
+
+Patch forces lookup on every directory under /proc/net .
+
+Link: https://lkml.kernel.org/r/20201205160916.GA109739@localhost.localdomain
+Fixes: 1da4d377f943 ("proc: revalidate misc dentries")
+Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
+Reported-by: "Rantala, Tommi T. (Nokia - FI/Espoo)" <tommi.t.rantala@nokia.com>
+Cc: Al Viro <viro@zeniv.linux.org.uk>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/proc/generic.c | 24 ++++++++++++++++++++++--
+ fs/proc/internal.h | 7 +++++++
+ fs/proc/proc_net.c | 16 ----------------
+ include/linux/proc_fs.h | 8 +++++++-
+ 4 files changed, 36 insertions(+), 19 deletions(-)
+
+diff --git a/fs/proc/generic.c b/fs/proc/generic.c
+index d4f353187d67c..8c3dbe13e647c 100644
+--- a/fs/proc/generic.c
++++ b/fs/proc/generic.c
+@@ -342,6 +342,16 @@ static const struct file_operations proc_dir_operations = {
+ .iterate_shared = proc_readdir,
+ };
+
++static int proc_net_d_revalidate(struct dentry *dentry, unsigned int flags)
++{
++ return 0;
++}
++
++const struct dentry_operations proc_net_dentry_ops = {
++ .d_revalidate = proc_net_d_revalidate,
++ .d_delete = always_delete_dentry,
++};
++
+ /*
+ * proc directories can do almost nothing..
+ */
+@@ -464,8 +474,8 @@ struct proc_dir_entry *proc_symlink(const char *name,
+ }
+ EXPORT_SYMBOL(proc_symlink);
+
+-struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode,
+- struct proc_dir_entry *parent, void *data)
++struct proc_dir_entry *_proc_mkdir(const char *name, umode_t mode,
++ struct proc_dir_entry *parent, void *data, bool force_lookup)
+ {
+ struct proc_dir_entry *ent;
+
+@@ -477,10 +487,20 @@ struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode,
+ ent->data = data;
+ ent->proc_fops = &proc_dir_operations;
+ ent->proc_iops = &proc_dir_inode_operations;
++ if (force_lookup) {
++ pde_force_lookup(ent);
++ }
+ ent = proc_register(parent, ent);
+ }
+ return ent;
+ }
++EXPORT_SYMBOL_GPL(_proc_mkdir);
++
++struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode,
++ struct proc_dir_entry *parent, void *data)
++{
++ return _proc_mkdir(name, mode, parent, data, false);
++}
+ EXPORT_SYMBOL_GPL(proc_mkdir_data);
+
+ struct proc_dir_entry *proc_mkdir_mode(const char *name, umode_t mode,
+diff --git a/fs/proc/internal.h b/fs/proc/internal.h
+index cd0c8d5ce9a13..269acc165055d 100644
+--- a/fs/proc/internal.h
++++ b/fs/proc/internal.h
+@@ -299,3 +299,10 @@ extern unsigned long task_statm(struct mm_struct *,
+ unsigned long *, unsigned long *,
+ unsigned long *, unsigned long *);
+ extern void task_mem(struct seq_file *, struct mm_struct *);
++
++extern const struct dentry_operations proc_net_dentry_ops;
++static inline void pde_force_lookup(struct proc_dir_entry *pde)
++{
++ /* /proc/net/ entries can be changed under us by setns(CLONE_NEWNET) */
++ pde->proc_dops = &proc_net_dentry_ops;
++}
+diff --git a/fs/proc/proc_net.c b/fs/proc/proc_net.c
+index 76ae278df1c47..313b7c751867f 100644
+--- a/fs/proc/proc_net.c
++++ b/fs/proc/proc_net.c
+@@ -39,22 +39,6 @@ static struct net *get_proc_net(const struct inode *inode)
+ return maybe_get_net(PDE_NET(PDE(inode)));
+ }
+
+-static int proc_net_d_revalidate(struct dentry *dentry, unsigned int flags)
+-{
+- return 0;
+-}
+-
+-static const struct dentry_operations proc_net_dentry_ops = {
+- .d_revalidate = proc_net_d_revalidate,
+- .d_delete = always_delete_dentry,
+-};
+-
+-static void pde_force_lookup(struct proc_dir_entry *pde)
+-{
+- /* /proc/net/ entries can be changed under us by setns(CLONE_NEWNET) */
+- pde->proc_dops = &proc_net_dentry_ops;
+-}
+-
+ static int seq_open_net(struct inode *inode, struct file *file)
+ {
+ unsigned int state_size = PDE(inode)->state_size;
+diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
+index a705aa2d03f91..865d02c224ada 100644
+--- a/include/linux/proc_fs.h
++++ b/include/linux/proc_fs.h
+@@ -21,6 +21,7 @@ extern void proc_flush_task(struct task_struct *);
+
+ extern struct proc_dir_entry *proc_symlink(const char *,
+ struct proc_dir_entry *, const char *);
++struct proc_dir_entry *_proc_mkdir(const char *, umode_t, struct proc_dir_entry *, void *, bool);
+ extern struct proc_dir_entry *proc_mkdir(const char *, struct proc_dir_entry *);
+ extern struct proc_dir_entry *proc_mkdir_data(const char *, umode_t,
+ struct proc_dir_entry *, void *);
+@@ -99,6 +100,11 @@ static inline struct proc_dir_entry *proc_symlink(const char *name,
+ static inline struct proc_dir_entry *proc_mkdir(const char *name,
+ struct proc_dir_entry *parent) {return NULL;}
+ static inline struct proc_dir_entry *proc_create_mount_point(const char *name) { return NULL; }
++static inline struct proc_dir_entry *_proc_mkdir(const char *name, umode_t mode,
++ struct proc_dir_entry *parent, void *data, bool force_lookup)
++{
++ return NULL;
++}
+ static inline struct proc_dir_entry *proc_mkdir_data(const char *name,
+ umode_t mode, struct proc_dir_entry *parent, void *data) { return NULL; }
+ static inline struct proc_dir_entry *proc_mkdir_mode(const char *name,
+@@ -136,7 +142,7 @@ struct net;
+ static inline struct proc_dir_entry *proc_net_mkdir(
+ struct net *net, const char *name, struct proc_dir_entry *parent)
+ {
+- return proc_mkdir_data(name, 0, parent, net);
++ return _proc_mkdir(name, 0, parent, net, true);
+ }
+
+ struct ns_common;
+--
+2.27.0
+