| From b5eedf57e9d482f4426b037bc4c10379c10fb05d Mon Sep 17 00:00:00 2001 |
| From: Alexey Dobriyan <adobriyan@gmail.com> |
| Date: Fri, 1 Feb 2019 14:20:01 -0800 |
| Subject: proc: fix /proc/net/* after setns(2) |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| [ Upstream commit 1fde6f21d90f8ba5da3cb9c54ca991ed72696c43 ] |
| |
| /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: 1da4d377f943fe4194ffb9fb9c26cc58fad4dd24 ("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> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| fs/proc/generic.c | 4 +- |
| fs/proc/internal.h | 1 + |
| fs/proc/proc_net.c | 20 +++ |
| tools/testing/selftests/proc/.gitignore | 1 + |
| tools/testing/selftests/proc/Makefile | 1 + |
| tools/testing/selftests/proc/setns-dcache.c | 129 ++++++++++++++++++++ |
| 6 files changed, 155 insertions(+), 1 deletion(-) |
| create mode 100644 tools/testing/selftests/proc/setns-dcache.c |
| |
| diff --git a/fs/proc/generic.c b/fs/proc/generic.c |
| index 8ae109429a88..e39bac94dead 100644 |
| --- a/fs/proc/generic.c |
| +++ b/fs/proc/generic.c |
| @@ -256,7 +256,7 @@ struct dentry *proc_lookup_de(struct inode *dir, struct dentry *dentry, |
| inode = proc_get_inode(dir->i_sb, de); |
| if (!inode) |
| 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); |
| } |
| 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); |
| proc_set_user(ent, (*parent)->uid, (*parent)->gid); |
| |
| + ent->proc_dops = &proc_misc_dentry_ops; |
| + |
| out: |
| return ent; |
| } |
| diff --git a/fs/proc/internal.h b/fs/proc/internal.h |
| index 5185d7f6a51e..95b14196f284 100644 |
| --- a/fs/proc/internal.h |
| +++ b/fs/proc/internal.h |
| @@ -44,6 +44,7 @@ struct proc_dir_entry { |
| struct completion *pde_unload_completion; |
| const struct inode_operations *proc_iops; |
| const struct file_operations *proc_fops; |
| + const struct dentry_operations *proc_dops; |
| union { |
| const struct seq_operations *seq_ops; |
| int (*single_show)(struct seq_file *, void *); |
| diff --git a/fs/proc/proc_net.c b/fs/proc/proc_net.c |
| index d5e0fcb3439e..a7b12435519e 100644 |
| --- a/fs/proc/proc_net.c |
| +++ b/fs/proc/proc_net.c |
| @@ -38,6 +38,22 @@ 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; |
| @@ -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); |
| if (!p) |
| return NULL; |
| + pde_force_lookup(p); |
| p->proc_fops = &proc_net_seq_fops; |
| p->seq_ops = ops; |
| 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); |
| if (!p) |
| return NULL; |
| + pde_force_lookup(p); |
| p->proc_fops = &proc_net_seq_fops; |
| p->seq_ops = ops; |
| 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); |
| if (!p) |
| return NULL; |
| + pde_force_lookup(p); |
| p->proc_fops = &proc_net_single_fops; |
| p->single_show = show; |
| 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); |
| if (!p) |
| return NULL; |
| + pde_force_lookup(p); |
| p->proc_fops = &proc_net_single_fops; |
| p->single_show = show; |
| p->write = write; |
| diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore |
| index 82121a81681f..29bac5ef9a93 100644 |
| --- a/tools/testing/selftests/proc/.gitignore |
| +++ b/tools/testing/selftests/proc/.gitignore |
| @@ -10,4 +10,5 @@ |
| /proc-uptime-002 |
| /read |
| /self |
| +/setns-dcache |
| /thread-self |
| diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile |
| index 1c12c34cf85d..434d033ee067 100644 |
| --- a/tools/testing/selftests/proc/Makefile |
| +++ b/tools/testing/selftests/proc/Makefile |
| @@ -14,6 +14,7 @@ TEST_GEN_PROGS += proc-uptime-001 |
| TEST_GEN_PROGS += proc-uptime-002 |
| TEST_GEN_PROGS += read |
| TEST_GEN_PROGS += self |
| +TEST_GEN_PROGS += setns-dcache |
| TEST_GEN_PROGS += thread-self |
| |
| include ../lib.mk |
| diff --git a/tools/testing/selftests/proc/setns-dcache.c b/tools/testing/selftests/proc/setns-dcache.c |
| new file mode 100644 |
| index 000000000000..60ab197a73fc |
| --- /dev/null |
| +++ b/tools/testing/selftests/proc/setns-dcache.c |
| @@ -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; |
| +} |
| -- |
| 2.19.1 |
| |