From: Masatake YAMATO Date: Sat, 24 Feb 2024 19:56:51 +0000 (+0900) Subject: lsns: show namespaces only kept alive by open file descriptors X-Git-Tag: v2.42-start~446^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7d5036fdafe0498a09146a5637a9f7f8351e6e72;p=thirdparty%2Futil-linux.git lsns: show namespaces only kept alive by open file descriptors Close #1884. Quoted from the original issue comment submitted by @hesch: It can happen, that a namespace is only kept alive by an open file descriptor of a program as ilustrated by A.B: 1. 'ip netns add foo' - add a namespace 2. 'sleep 999 4< /run/netns/foo & sleep 2' - open the fd to the namespace in a background job 3. 'ip netns delete foo' - delete the namespace (only deletes the /run/netns/foo) Now there exists a namespace with no process running in it and it has no bind mount so it does not show up in /proc/mounts, but it is still there and could be mounted back. Signed-off-by: Masatake YAMATO --- diff --git a/sys-utils/lsns.c b/sys-utils/lsns.c index c328f046e..9d12ca205 100644 --- a/sys-utils/lsns.c +++ b/sys-utils/lsns.c @@ -218,6 +218,7 @@ struct lsns { no_headings: 1, no_wrap : 1; + dev_t nsfs_dev; struct libmnt_table *tab; struct libscols_filter *filter; @@ -501,6 +502,33 @@ static int get_netnsid(struct path_cxt *pc __attribute__((__unused__)), } #endif /* HAVE_LINUX_NET_NAMESPACE_H */ +static struct lsns_namespace *add_namespace_for_nsfd(struct lsns *ls, int fd, ino_t ino); + +static void read_open_ns_inos(struct lsns *ls, struct path_cxt *pc) +{ + DIR *sub = NULL; + struct dirent *d = NULL; + char path[sizeof("fd/") + sizeof(stringify_value(UINT64_MAX))]; + + while (ul_path_next_dirent(pc, &sub, "fd", &d) == 0) { + uint64_t num; + struct stat st; + + if (ul_strtou64(d->d_name, &num, 10) != 0) /* only numbers */ + continue; + + snprintf(path, sizeof(path), "fd/%ju", (uintmax_t) num); + ul_path_stat(pc, &st, 0, path); + if (st.st_dev == ls->nsfs_dev) { + int fd = ul_path_open(pc, O_RDONLY, path); + if (fd >= 0) { + add_namespace_for_nsfd(ls, fd, st.st_ino); + close(fd); + } + } + } +} + static int read_process(struct lsns *ls, struct path_cxt *pc) { struct lsns_process *p = NULL; @@ -539,6 +567,8 @@ static int read_process(struct lsns *ls, struct path_cxt *pc) DBG(PROC, ul_debugobj(p, "new pid=%d", p->pid)); list_add_tail(&p->processes, &ls->processes); + + read_open_ns_inos(ls, pc); done: if (rc) free(p); @@ -1421,6 +1451,16 @@ static void __attribute__((__noreturn__)) list_colunms(bool raw, bool json) exit(EXIT_SUCCESS); } +static dev_t read_nsfs_dev(void) +{ + struct stat st; + + if (stat("/proc/self/ns/user", &st) < 0) + err(EXIT_FAILURE, _("failed to do stat /proc/self/ns/user")); + + return st.st_dev; +} + int main(int argc, char *argv[]) { struct lsns ls; @@ -1607,6 +1647,8 @@ int main(int argc, char *argv[]) if (!ls.tab) err(MNT_EX_FAIL, _("failed to parse %s"), _PATH_PROC_MOUNTINFO); + ls.nsfs_dev = read_nsfs_dev(); + r = read_processes(&ls); if (!r) r = read_namespaces(&ls); diff --git a/tests/ts/lsns/filedesc b/tests/ts/lsns/filedesc new file mode 100755 index 000000000..285ad6312 --- /dev/null +++ b/tests/ts/lsns/filedesc @@ -0,0 +1,71 @@ +#!/bin/bash +# +# Copyright (C) 2024 Masatake YAMATO +# +# This file is part of util-linux. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This file is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# + +# This test case is based on the issue (#1884) submitted by @hesch +TS_TOPDIR="${0%/*}/../.." +TS_DESC="list the namespace pointed by a file descriptor" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +ts_check_test_command "$TS_CMD_LSNS" +ts_check_test_command "$TS_CMD_LSFD" +ts_check_test_command "$TS_HELPER_MKFDS" + +ts_check_prog "ip" + +ts_skip_nonroot + +FD=4 +NS=LSNS-TEST-FILEDESC-NS +FILE=/run/netns/$NS +PID= + +cleanup() +{ + ip netns delete $NS 2> /dev/null || : +} + +cleanup + +if ! ip netns add $NS; then + ts_skip "failed to make a namespace" +fi +trap "cleanup" EXIT + +{ + coproc MKFDS { "$TS_HELPER_MKFDS" ro-regular-file $FD file=$FILE; } + if read -u ${MKFDS[0]} PID; then + # Make the namespace invisible from the file system tree. + cleanup + lsfd_expr="PID == ${PID} and FD == $FD" + lsfd_inode=$(${TS_CMD_LSFD} -n --raw -o INODE -Q "${lsfd_expr}") + lsns_expr="NS == $lsfd_inode" + lsns_output=$(${TS_CMD_LSNS} -n --raw -o TYPE,NPROCS,USER -Q "${lsns_expr}") + if ! [ "${lsns_output}" == "net 0 root" ]; then + echo lsfd_inode: $lsfd_inode + echo lsns_output: $lsns_output + echo LSFD: + ${TS_CMD_LSFD} -Q "PID == $PID" + echo LSNS: + ${TS_CMD_LSNS} + fi + echo DONE >&"${MKFDS[1]}" + fi + wait "${MKFDS_PID}" +} > $TS_OUTPUT 2>&1 +ts_finalize