Skip to contentSkip to conversation

/proc/net/sctp/snmp, setns, proc: revalidate misc dentries

PATCH Applied Rantala, Tommi T. (Nokia - FI/Espoo) 2 participants 3 messages Dec 2, 2020 – Dec 5, 2020

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

Hello,

Bisected problems with setns() and /proc/net/sctp/snmp to this:

commit 1da4d377f943fe4194ffb9fb9c26cc58fad4dd24

Author: Alexey Dobriyan <[email protected]>
Date:   Fri Apr 13 15:35:42 2018 -0700

proc: revalidate misc dentries

Reproduces for example with Fedora 5.9.10-100.fc32.x86_64, so 1fde6f21d90f ("proc: fix /proc/net/* after setns(2)") does not seem to cover /proc/net/sctp/snmp

Reproducer attached, that does open+read+close of /proc/net/sctp/snmp before and after setns() syscall. The second open+read+close of /proc/net/sctp/snmp incorrectly produces results for the default namespace, not the target namespace.

Example, create netns and do some sctp:

# ./iperf-netns
+ modprobe sctp
+ ip netns add test
+ ip netns exec test ip link set lo up
+ ip netns exec test iperf3 -s -1
-----------------------------------------------------------
Server listening on 5201
-----------------------------------------------------------
+ ip netns exec test iperf3 -c 127.0.0.1 --sctp --bitrate 50M --time 4
Connecting to host 127.0.0.1, port 5201
Accepted connection from 127.0.0.1, port 50696
[  5] local 127.0.0.1 port 54735 connected to 127.0.0.1 port 5201
[  5] local 127.0.0.1 port 5201 connected to 127.0.0.1 port 54735
[ ID] Interval           Transfer     Bitrate
[ ID] Interval           Transfer     Bitrate
[  5]   0.00-1.00   sec  6.00 MBytes  50.3 Mbits/sec                  
[  5]   0.00-1.00   sec  6.00 MBytes  50.3 Mbits/sec                  
[  5]   1.00-2.00   sec  5.94 MBytes  49.8 Mbits/sec                  
[  5]   1.00-2.00   sec  5.94 MBytes  49.8 Mbits/sec                  
[  5]   2.00-3.00   sec  6.00 MBytes  50.3 Mbits/sec                  
[  5]   2.00-3.00   sec  6.00 MBytes  50.3 Mbits/sec                  
[  5]   3.00-4.00   sec  5.94 MBytes  49.8 Mbits/sec                  
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate
[  5]   0.00-4.00   sec  23.9 MBytes  50.1
Mbits/sec                  receiver
[  5]   3.00-4.00   sec  5.94 MBytes  49.8 Mbits/sec                  
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate
[  5]   0.00-4.00   sec  23.9 MBytes  50.1 Mbits/sec
[  5]   0.00-4.00   sec  23.9 MBytes  50.1

iperf Done.
+ cat /proc/net/sctp/snmp

SctpCurrEstab                           0
SctpActiveEstabs                        0
SctpPassiveEstabs                       0
SctpAborteds                            0
SctpShutdowns                           0
SctpOutOfBlues                          0
SctpChecksumErrors                      0
[...]
+ ip netns exec test cat /proc/net/sctp/snmp
SctpCurrEstab                           0
SctpActiveEstabs                        2
SctpPassiveEstabs                       2
SctpAborteds                            0
SctpShutdowns                           4
SctpOutOfBlues                          0
SctpChecksumErrors                      0
SctpOutCtrlChunks                       1544
SctpOutOrderChunks                      1530

[...]
+ wait

But now we see all zeroes in /proc/net/sctp/snmp with the reproducer:

$ gcc repro.c -o repro      
# ./repro
/proc/net/sctp/snmp [pid: 175998]
SctpCurrEstab                           0
SctpActiveEstabs                        0
SctpPassiveEstabs                       0
SctpAborteds                            0
SctpShutdowns                           0
[...]

setns(/run/netns/test) ...
/proc/net/sctp/snmp [pid: 175998]

SctpCurrEstab                           0
SctpActiveEstabs                        0
SctpPassiveEstabs                       0
SctpAborteds                            0
SctpShutdowns                           0
SctpOutOfBlues                          0

[...]

-Tommi

#define _GNU_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void slurp(const char *fn)

{
	char buf[8192];
	ssize_t r;
	int fd;

	printf("%s [pid: %d]\n", fn, getpid()); fflush(stdout);

	fd = open(fn, O_RDONLY);
	if (fd < 0) { perror("open"); exit(1); }

	r = read(fd, buf, sizeof(buf)-1);
	if (r < 0) { perror("read"); exit(1); }
	buf[r] = 0;
	puts(buf); fflush(stdout);

	if (close(fd) < 0) { perror("close"); exit(1); }
}

void newnet(const char *ns)

{
	int fd;
	fd = open(ns, O_RDONLY);
	if (fd < 0) { perror("open"); exit(1); }
	if (setns(fd, CLONE_NEWNET) < 0) { perror("setns"); exit(1); }
	if (close(fd) < 0) { perror("close"); exit(1); }
}

int main(int argc, char **argv)

{
	const char *ns = "/run/netns/test";
	const char *fn = "/proc/net/sctp/snmp";
	int d = 1;

	// Optional args: /run/netns/... /proc/net/... n
	if (argc >= 2) ns = argv[1];
	if (argc >= 3) fn = argv[2];
	if (argc >= 4 && argv[3][0] == 'n') d = 0;

	if (d) slurp(fn);
	printf("setns(%s) ...\n", ns); fflush(stdout);
	newnet(ns);
	slurp(fn);
}
#1 lore raw

PATCH 1/1 proc: fix lookup in /proc/net subdirectories after setns(2)

	commit 1fde6f21d90f8ba5da3cb9c54ca991ed72696c43
	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 .

Fixes 1da4d377f943 ("proc: revalidate misc dentries")

---

 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(-)
fs/proc/generic.c +22 -2
@@ -349,6 +349,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..
  */
@@ -471,8 +481,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;
 
@@ -484,10 +494,20 @@ struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode,
 		ent->data = data;
 		ent->proc_dir_ops = &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,
fs/proc/internal.h +7 -0
@@ -310,3 +310,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;
+}
fs/proc/proc_net.c +0 -16
@@ -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;
include/linux/proc_fs.h +7 -1
@@ -80,6 +80,7 @@ extern void proc_flush_pid(struct pid *);
 
 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 *);
@@ -162,6 +163,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,
@@ -199,7 +205,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 lore raw

PATCH 1/1 proc: fix lookup in /proc/net subdirectories after setns(2)

	commit 1fde6f21d90f8ba5da3cb9c54ca991ed72696c43
	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 .

Fixes 1da4d377f943 ("proc: revalidate misc dentries")

---

 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(-)
fs/proc/generic.c +22 -2
@@ -349,6 +349,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..
  */
@@ -471,8 +481,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;
 
@@ -484,10 +494,20 @@ struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode,
 		ent->data = data;
 		ent->proc_dir_ops = &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,
fs/proc/internal.h +7 -0
@@ -310,3 +310,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;
+}
fs/proc/proc_net.c +0 -16
@@ -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;
include/linux/proc_fs.h +7 -1
@@ -80,6 +80,7 @@ extern void proc_flush_pid(struct pid *);
 
 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 *);
@@ -162,6 +163,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,
@@ -199,7 +205,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;