From: Dmitry Safonov <0x7f454c46@gmail.com> Date: Fri, 13 Sep 2024 22:29:55 +0000 (+0100) Subject: nsenter: Provide an option to join target process's socket net namespace X-Git-Tag: v2.42-start~198^2~2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=55c7120accab83c5837c2182cd6b4637b0c21c44;p=thirdparty%2Futil-linux.git nsenter: Provide an option to join target process's socket net namespace The network namespace of a socket can be different from the target process. Previously there were some userspace issues where a net-namespace was held alive by a socket leak. For this purpose Arista's linux kernel has a patch to provide socket => netns map by procfs pid/fd directory links. Add nsenter option to join the network namespace of a target process' socket. Signed-off-by: Dmitry Safonov <0x7f454c46@gmail.com> --- diff --git a/configure.ac b/configure.ac index 0ca2ebcf7..c18802e9e 100644 --- a/configure.ac +++ b/configure.ac @@ -628,6 +628,7 @@ AC_CHECK_FUNCS([ \ ntp_gettime \ open_tree \ personality \ + pidfd_getfd \ pidfd_open \ pidfd_send_signal \ posix_fadvise \ @@ -682,6 +683,7 @@ AS_IF([test "x$ul_cv_syscall_setns" = xno], [ have_setns_syscall="no" ]) +UL_CHECK_SYSCALL([pidfd_getfd]) UL_CHECK_SYSCALL([pidfd_open]) UL_CHECK_SYSCALL([pidfd_send_signal]) UL_CHECK_SYSCALL([close_range]) diff --git a/include/pidfd-utils.h b/include/pidfd-utils.h index 4ac9f79d9..d43bad26f 100644 --- a/include/pidfd-utils.h +++ b/include/pidfd-utils.h @@ -31,6 +31,13 @@ static inline int pidfd_open(pid_t pid, unsigned int flags) } # endif +# ifndef HAVE_PIDFD_GETFD +static inline int pidfd_getfd(int pidfd, int targetfd, unsigned int flags) +{ + return syscall(SYS_pidfd_getfd, pidfd, targetfd, flags); +} +# endif + # define UL_HAVE_PIDFD 1 # endif /* SYS_pidfd_send_signal */ @@ -52,6 +59,14 @@ static inline int pidfd_open(pid_t pid __attribute__((unused)), errno = ENOSYS; return -1; } + +static inline int pidfd_getfd(int pidfd __attribute__((unused)), + int targetfd __attribute__((unused)), + unsigned int flags __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} #endif #endif /* UTIL_LINUX_PIDFD_UTILS */ diff --git a/sys-utils/nsenter.1.adoc b/sys-utils/nsenter.1.adoc index 58dd12548..31b5c4d7d 100644 --- a/sys-utils/nsenter.1.adoc +++ b/sys-utils/nsenter.1.adoc @@ -91,6 +91,9 @@ Enter the IPC namespace. If no file is specified, enter the IPC namespace of the *-n*, *--net*[=_file_]:: Enter the network namespace. If no file is specified, enter the network namespace of the target process. If _file_ is specified, enter the network namespace specified by _file_. +*-N*, *--net-socket* _fd_:: +Enter the network namespace of the target process's socket. It requires *--target* process specified. + *-p*, *--pid*[=_file_]:: Enter the PID namespace. If no file is specified, enter the PID namespace of the target process. If _file_ is specified, enter the PID namespace specified by _file_. diff --git a/sys-utils/nsenter.c b/sys-utils/nsenter.c index 0f5babaad..eb41149e0 100644 --- a/sys-utils/nsenter.c +++ b/sys-utils/nsenter.c @@ -25,6 +25,7 @@ #include #include +#include #ifdef HAVE_LINUX_NSFS_H # include #endif @@ -49,6 +50,7 @@ #include "caputils.h" #include "statfs_magic.h" #include "pathnames.h" +#include "pidfd-utils.h" static struct namespace_file { int nstype; @@ -92,6 +94,7 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -u, --uts[=] enter UTS namespace (hostname etc)\n"), out); fputs(_(" -i, --ipc[=] enter System V IPC namespace\n"), out); fputs(_(" -n, --net[=] enter network namespace\n"), out); + fputs(_(" -N, --net-socket enter socket's network namespace (use with --target)\n"), out); fputs(_(" -p, --pid[=] enter pid namespace\n"), out); fputs(_(" -C, --cgroup[=] enter cgroup namespace\n"), out); fputs(_(" -U, --user[=] enter user namespace\n"), out); @@ -189,6 +192,40 @@ static void open_namespace_fd(int nstype, const char *path) assert(nsfile->nstype); } +static void open_target_sk_netns(int pid, int sock_fd) +{ + struct namespace_file *nsfile; + struct stat sb; + int pidfd, sk, nsfd; + + for (nsfile = namespace_files; nsfile->nstype; nsfile++) { + if (nsfile->nstype == CLONE_NEWNET) + break; + } + assert(nsfile->nstype); + + pidfd = pidfd_open(pid, 0); + if (pidfd < 0) + err(EXIT_FAILURE, _("failed to pidfd_open() for %d"), pid); + + sk = pidfd_getfd(pidfd, sock_fd, 0); + if (sk < 0) + err(EXIT_FAILURE, _("pidfd_getfd(%d, %u)"), pidfd, sock_fd); + + if (fstat(sk, &sb) < 0) + err(EXIT_FAILURE, _("fstat(%d)"), sk); + + nsfd = ioctl(sk, SIOCGSKNS); + if (nsfd < 0) + err(EXIT_FAILURE, _("ioctl(%d, SIOCGSKNS)"), sk); + + if (nsfile->fd >= 0) + close(nsfile->fd); + nsfile->fd = nsfd; + close(sk); + close(pidfd); +} + static int get_ns_ino(const char *path, ino_t *ino) { struct stat st; @@ -329,6 +366,7 @@ int main(int argc, char *argv[]) { "uts", optional_argument, NULL, 'u' }, { "ipc", optional_argument, NULL, 'i' }, { "net", optional_argument, NULL, 'n' }, + { "net-socket", required_argument, NULL, 'N' }, { "pid", optional_argument, NULL, 'p' }, { "user", optional_argument, NULL, 'U' }, { "cgroup", optional_argument, NULL, 'C' }, @@ -365,6 +403,7 @@ int main(int argc, char *argv[]) uid_t uid = 0; gid_t gid = 0; int keepcaps = 0; + int sock_fd = -1; struct ul_env_list *envls; #ifdef HAVE_LIBSELINUX bool selinux = 0; @@ -376,7 +415,7 @@ int main(int argc, char *argv[]) close_stdout_atexit(); while ((c = - getopt_long(argc, argv, "+ahVt:m::u::i::n::p::C::U::T::S:G:r::w::W::ecFZ", + getopt_long(argc, argv, "+ahVt:m::u::i::n::N:p::C::U::T::S:G:r::w::W::ecFZ", longopts, NULL)) != -1) { err_exclusive_options(c, longopts, excl, excl_st); @@ -413,6 +452,10 @@ int main(int argc, char *argv[]) else namespaces |= CLONE_NEWNET; break; + case 'N': + sock_fd = str2num_or_err(optarg, 10, _("failed to parse file descriptor"), + 0, INT_MAX); + break; case 'p': if (optarg) open_namespace_fd(CLONE_NEWPID, optarg); @@ -562,6 +605,13 @@ int main(int argc, char *argv[]) namespaces |= nsfile->nstype; } + if (sock_fd != -1) { + if (!namespace_target_pid) + errx(EXIT_FAILURE, _("--net-socket needs target PID")); + open_target_sk_netns(namespace_target_pid, sock_fd); + namespaces |= CLONE_NEWNET; + } + /* for user namespaces we always set UID and GID (default is 0) * and clear root's groups if --preserve-credentials is no specified */ if ((namespaces & CLONE_NEWUSER) && !preserve_cred) {