forked from mirrors/linux
		
	proc: fix /proc/net/* after setns(2)
/proc entries under /proc/net/* can't be cached into dcache because
setns(2) can change current net namespace.
[akpm@linux-foundation.org: coding-style fixes]
[akpm@linux-foundation.org: avoid vim miscolorization]
[adobriyan@gmail.com: write test, add dummy ->d_revalidate hook: necessary if /proc/net/* is pinned at setns time]
  Link: http://lkml.kernel.org/r/20190108192350.GA12034@avx2
Link: http://lkml.kernel.org/r/20190107162336.GA9239@avx2
Fixes: 1da4d377f9 ("proc: revalidate misc dentries")
Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Reported-by: Mateusz Stępień <mateusz.stepien@netrounds.com>
Reported-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
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>
			
			
This commit is contained in:
		
							parent
							
								
									1723058eab
								
							
						
					
					
						commit
						1fde6f21d9
					
				
					 6 changed files with 155 additions and 1 deletions
				
			
		|  | @ -256,7 +256,7 @@ struct dentry *proc_lookup_de(struct inode *dir, struct dentry *dentry, | ||||||
| 		inode = proc_get_inode(dir->i_sb, de); | 		inode = proc_get_inode(dir->i_sb, de); | ||||||
| 		if (!inode) | 		if (!inode) | ||||||
| 			return ERR_PTR(-ENOMEM); | 			return ERR_PTR(-ENOMEM); | ||||||
| 		d_set_d_op(dentry, &proc_misc_dentry_ops); | 		d_set_d_op(dentry, de->proc_dops); | ||||||
| 		return d_splice_alias(inode, dentry); | 		return d_splice_alias(inode, dentry); | ||||||
| 	} | 	} | ||||||
| 	read_unlock(&proc_subdir_lock); | 	read_unlock(&proc_subdir_lock); | ||||||
|  | @ -429,6 +429,8 @@ static struct proc_dir_entry *__proc_create(struct proc_dir_entry **parent, | ||||||
| 	INIT_LIST_HEAD(&ent->pde_openers); | 	INIT_LIST_HEAD(&ent->pde_openers); | ||||||
| 	proc_set_user(ent, (*parent)->uid, (*parent)->gid); | 	proc_set_user(ent, (*parent)->uid, (*parent)->gid); | ||||||
| 
 | 
 | ||||||
|  | 	ent->proc_dops = &proc_misc_dentry_ops; | ||||||
|  | 
 | ||||||
| out: | out: | ||||||
| 	return ent; | 	return ent; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -44,6 +44,7 @@ struct proc_dir_entry { | ||||||
| 	struct completion *pde_unload_completion; | 	struct completion *pde_unload_completion; | ||||||
| 	const struct inode_operations *proc_iops; | 	const struct inode_operations *proc_iops; | ||||||
| 	const struct file_operations *proc_fops; | 	const struct file_operations *proc_fops; | ||||||
|  | 	const struct dentry_operations *proc_dops; | ||||||
| 	union { | 	union { | ||||||
| 		const struct seq_operations *seq_ops; | 		const struct seq_operations *seq_ops; | ||||||
| 		int (*single_show)(struct seq_file *, void *); | 		int (*single_show)(struct seq_file *, void *); | ||||||
|  |  | ||||||
|  | @ -38,6 +38,22 @@ static struct net *get_proc_net(const struct inode *inode) | ||||||
| 	return maybe_get_net(PDE_NET(PDE(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) | static int seq_open_net(struct inode *inode, struct file *file) | ||||||
| { | { | ||||||
| 	unsigned int state_size = PDE(inode)->state_size; | 	unsigned int state_size = PDE(inode)->state_size; | ||||||
|  | @ -90,6 +106,7 @@ struct proc_dir_entry *proc_create_net_data(const char *name, umode_t mode, | ||||||
| 	p = proc_create_reg(name, mode, &parent, data); | 	p = proc_create_reg(name, mode, &parent, data); | ||||||
| 	if (!p) | 	if (!p) | ||||||
| 		return NULL; | 		return NULL; | ||||||
|  | 	pde_force_lookup(p); | ||||||
| 	p->proc_fops = &proc_net_seq_fops; | 	p->proc_fops = &proc_net_seq_fops; | ||||||
| 	p->seq_ops = ops; | 	p->seq_ops = ops; | ||||||
| 	p->state_size = state_size; | 	p->state_size = state_size; | ||||||
|  | @ -133,6 +150,7 @@ struct proc_dir_entry *proc_create_net_data_write(const char *name, umode_t mode | ||||||
| 	p = proc_create_reg(name, mode, &parent, data); | 	p = proc_create_reg(name, mode, &parent, data); | ||||||
| 	if (!p) | 	if (!p) | ||||||
| 		return NULL; | 		return NULL; | ||||||
|  | 	pde_force_lookup(p); | ||||||
| 	p->proc_fops = &proc_net_seq_fops; | 	p->proc_fops = &proc_net_seq_fops; | ||||||
| 	p->seq_ops = ops; | 	p->seq_ops = ops; | ||||||
| 	p->state_size = state_size; | 	p->state_size = state_size; | ||||||
|  | @ -181,6 +199,7 @@ struct proc_dir_entry *proc_create_net_single(const char *name, umode_t mode, | ||||||
| 	p = proc_create_reg(name, mode, &parent, data); | 	p = proc_create_reg(name, mode, &parent, data); | ||||||
| 	if (!p) | 	if (!p) | ||||||
| 		return NULL; | 		return NULL; | ||||||
|  | 	pde_force_lookup(p); | ||||||
| 	p->proc_fops = &proc_net_single_fops; | 	p->proc_fops = &proc_net_single_fops; | ||||||
| 	p->single_show = show; | 	p->single_show = show; | ||||||
| 	return proc_register(parent, p); | 	return proc_register(parent, p); | ||||||
|  | @ -223,6 +242,7 @@ struct proc_dir_entry *proc_create_net_single_write(const char *name, umode_t mo | ||||||
| 	p = proc_create_reg(name, mode, &parent, data); | 	p = proc_create_reg(name, mode, &parent, data); | ||||||
| 	if (!p) | 	if (!p) | ||||||
| 		return NULL; | 		return NULL; | ||||||
|  | 	pde_force_lookup(p); | ||||||
| 	p->proc_fops = &proc_net_single_fops; | 	p->proc_fops = &proc_net_single_fops; | ||||||
| 	p->single_show = show; | 	p->single_show = show; | ||||||
| 	p->write = write; | 	p->write = write; | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								tools/testing/selftests/proc/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								tools/testing/selftests/proc/.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -10,4 +10,5 @@ | ||||||
| /proc-uptime-002 | /proc-uptime-002 | ||||||
| /read | /read | ||||||
| /self | /self | ||||||
|  | /setns-dcache | ||||||
| /thread-self | /thread-self | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ TEST_GEN_PROGS += proc-uptime-001 | ||||||
| TEST_GEN_PROGS += proc-uptime-002 | TEST_GEN_PROGS += proc-uptime-002 | ||||||
| TEST_GEN_PROGS += read | TEST_GEN_PROGS += read | ||||||
| TEST_GEN_PROGS += self | TEST_GEN_PROGS += self | ||||||
|  | TEST_GEN_PROGS += setns-dcache | ||||||
| TEST_GEN_PROGS += thread-self | TEST_GEN_PROGS += thread-self | ||||||
| 
 | 
 | ||||||
| include ../lib.mk | include ../lib.mk | ||||||
|  |  | ||||||
							
								
								
									
										129
									
								
								tools/testing/selftests/proc/setns-dcache.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								tools/testing/selftests/proc/setns-dcache.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,129 @@ | ||||||
|  | /*
 | ||||||
|  |  * Copyright © 2019 Alexey Dobriyan <adobriyan@gmail.com> | ||||||
|  |  * | ||||||
|  |  * Permission to use, copy, modify, and distribute this software for any | ||||||
|  |  * purpose with or without fee is hereby granted, provided that the above | ||||||
|  |  * copyright notice and this permission notice appear in all copies. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||||||
|  |  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||||||
|  |  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||||||
|  |  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||||||
|  |  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||||||
|  |  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||||||
|  |  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||||
|  |  */ | ||||||
|  | /*
 | ||||||
|  |  * Test that setns(CLONE_NEWNET) points to new /proc/net content even | ||||||
|  |  * if old one is in dcache. | ||||||
|  |  * | ||||||
|  |  * FIXME /proc/net/unix is under CONFIG_UNIX which can be disabled. | ||||||
|  |  */ | ||||||
|  | #undef NDEBUG | ||||||
|  | #include <assert.h> | ||||||
|  | #include <errno.h> | ||||||
|  | #include <sched.h> | ||||||
|  | #include <signal.h> | ||||||
|  | #include <stdio.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | #include <sys/types.h> | ||||||
|  | #include <sys/stat.h> | ||||||
|  | #include <fcntl.h> | ||||||
|  | #include <sys/socket.h> | ||||||
|  | 
 | ||||||
|  | static pid_t pid = -1; | ||||||
|  | 
 | ||||||
|  | static void f(void) | ||||||
|  | { | ||||||
|  | 	if (pid > 0) { | ||||||
|  | 		kill(pid, SIGTERM); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int main(void) | ||||||
|  | { | ||||||
|  | 	int fd[2]; | ||||||
|  | 	char _ = 0; | ||||||
|  | 	int nsfd; | ||||||
|  | 
 | ||||||
|  | 	atexit(f); | ||||||
|  | 
 | ||||||
|  | 	/* Check for priviledges and syscall availability straight away. */ | ||||||
|  | 	if (unshare(CLONE_NEWNET) == -1) { | ||||||
|  | 		if (errno == ENOSYS || errno == EPERM) { | ||||||
|  | 			return 4; | ||||||
|  | 		} | ||||||
|  | 		return 1; | ||||||
|  | 	} | ||||||
|  | 	/* Distinguisher between two otherwise empty net namespaces. */ | ||||||
|  | 	if (socket(AF_UNIX, SOCK_STREAM, 0) == -1) { | ||||||
|  | 		return 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (pipe(fd) == -1) { | ||||||
|  | 		return 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pid = fork(); | ||||||
|  | 	if (pid == -1) { | ||||||
|  | 		return 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (pid == 0) { | ||||||
|  | 		if (unshare(CLONE_NEWNET) == -1) { | ||||||
|  | 			return 1; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (write(fd[1], &_, 1) != 1) { | ||||||
|  | 			return 1; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		pause(); | ||||||
|  | 
 | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (read(fd[0], &_, 1) != 1) { | ||||||
|  | 		return 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	{ | ||||||
|  | 		char buf[64]; | ||||||
|  | 		snprintf(buf, sizeof(buf), "/proc/%u/ns/net", pid); | ||||||
|  | 		nsfd = open(buf, O_RDONLY); | ||||||
|  | 		if (nsfd == -1) { | ||||||
|  | 			return 1; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* Reliably pin dentry into dcache. */ | ||||||
|  | 	(void)open("/proc/net/unix", O_RDONLY); | ||||||
|  | 
 | ||||||
|  | 	if (setns(nsfd, CLONE_NEWNET) == -1) { | ||||||
|  | 		return 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	kill(pid, SIGTERM); | ||||||
|  | 	pid = 0; | ||||||
|  | 
 | ||||||
|  | 	{ | ||||||
|  | 		char buf[4096]; | ||||||
|  | 		ssize_t rv; | ||||||
|  | 		int fd; | ||||||
|  | 
 | ||||||
|  | 		fd = open("/proc/net/unix", O_RDONLY); | ||||||
|  | 		if (fd == -1) { | ||||||
|  | 			return 1; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | #define S "Num       RefCount Protocol Flags    Type St Inode Path\n" | ||||||
|  | 		rv = read(fd, buf, sizeof(buf)); | ||||||
|  | 
 | ||||||
|  | 		assert(rv == strlen(S)); | ||||||
|  | 		assert(memcmp(buf, S, strlen(S)) == 0); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
		Loading…
	
		Reference in a new issue
	
	 Alexey Dobriyan
						Alexey Dobriyan