]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lsns: show namespaces only kept alive by open file descriptors
authorMasatake YAMATO <yamato@redhat.com>
Sat, 24 Feb 2024 19:56:51 +0000 (04:56 +0900)
committerMasatake YAMATO <yamato@redhat.com>
Tue, 2 Apr 2024 14:44:25 +0000 (23:44 +0900)
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 <yamato@redhat.com>
sys-utils/lsns.c
tests/ts/lsns/filedesc [new file with mode: 0755]

index c328f046e168e5cbaefd3dce423acfeecfbe1145..9d12ca205b443be0435b59311733b3f70a379e70 100644 (file)
@@ -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 (executable)
index 0000000..285ad63
--- /dev/null
@@ -0,0 +1,71 @@
+#!/bin/bash
+#
+# Copyright (C) 2024 Masatake YAMATO <yamato@redhat.com>
+#
+# 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