public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: David Gibson <david@gibson.dropbear.id.au>
To: passt-dev@passt.top, Stefano Brivio <sbrivio@redhat.com>
Cc: David Gibson <david@gibson.dropbear.id.au>
Subject: [PATCH v2 06/14] nstool: Detect what namespaces target is in
Date: Thu,  6 Apr 2023 13:28:11 +1000	[thread overview]
Message-ID: <20230406032819.707441-7-david@gibson.dropbear.id.au> (raw)
In-Reply-To: <20230406032819.707441-1-david@gibson.dropbear.id.au>

Give nstool the ability to detect what namespaces the target process is in,
relative to where it's called.  That is, those namespace types for which
the target is not in the same namespace as the caller.  For now, just
print this information with "info", which can be useful for debugging.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/nstool.c | 157 +++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 143 insertions(+), 14 deletions(-)

diff --git a/test/nstool.c b/test/nstool.c
index 5f54439..146b66e 100644
--- a/test/nstool.c
+++ b/test/nstool.c
@@ -15,8 +15,13 @@
 #include <errno.h>
 #include <unistd.h>
 #include <getopt.h>
+#include <stdarg.h>
+#include <limits.h>
 #include <sys/socket.h>
 #include <linux/un.h>
+#include <sched.h>
+
+#define	ARRAY_SIZE(a)	((int)(sizeof(a) / sizeof((a)[0])))
 
 #define die(...)				\
 	do {					\
@@ -24,6 +29,28 @@
 		exit(1);			\
 	} while (0)
 
+struct ns_type {
+	int flag;
+	const char *name;
+};
+
+const struct ns_type nstypes[] = {
+	{ CLONE_NEWCGROUP, "cgroup" },
+	{ CLONE_NEWIPC, "ipc" },
+	{ CLONE_NEWNET, "net" },
+	{ CLONE_NEWNS, "mnt" },
+	{ CLONE_NEWPID, "pid" },
+	{ CLONE_NEWTIME, "time" },
+	{ CLONE_NEWUSER, "user" },
+	{ CLONE_NEWUTS, "uts" },
+};
+
+struct holder_info {
+	pid_t pid;
+	uid_t uid;
+	gid_t gid;
+};
+
 static void usage(void)
 {
 	die("Usage:\n"
@@ -41,12 +68,16 @@ static void usage(void)
 	    "    terminate.\n");
 }
 
-static int connect_ctl(const char * sockpath, bool wait)
+static int connect_ctl(const char *sockpath, bool wait,
+		       struct holder_info *info,
+		       struct ucred *peercred)
 {
 	int fd = socket(AF_UNIX, SOCK_STREAM, PF_UNIX);
 	struct sockaddr_un addr = {
 		.sun_family = AF_UNIX,
 	};
+	struct holder_info discard;
+	ssize_t len;
 	int rc;
 
 	if (fd < 0)
@@ -61,6 +92,28 @@ static int connect_ctl(const char * sockpath, bool wait)
 			die("connect() to %s: %s\n", sockpath, strerror(errno));
 	} while (rc < 0);
 
+	if (!info)
+		info = &discard;
+
+	/* Always read the info structure, even if we don't need it,
+	 * so that the holder doesn't get a broken pipe error
+	 */
+	len = read(fd, info, sizeof(*info));
+	if (len < 0)
+		die("read() on control socket %s: %s\n", sockpath, strerror(errno));
+	if ((size_t)len < sizeof(*info))
+		die("short read() on control socket %s\n", sockpath);
+
+	if (peercred) {
+		socklen_t optlen = sizeof(*peercred);
+
+		rc = getsockopt(fd, SOL_SOCKET, SO_PEERCRED,
+				peercred, &optlen);
+		if (rc < 0)
+			die("getsockopet(SO_PEERCRED) %s: %s\n",
+			    sockpath, strerror(errno));
+	}
+
 	return fd;
 }
 
@@ -71,6 +124,7 @@ static void cmd_hold(int argc, char *argv[])
 		.sun_family = AF_UNIX,
 	};
 	const char *sockpath = argv[1];
+	struct holder_info info;
 	int rc;
 
 	if (argc != 2)
@@ -89,8 +143,10 @@ static void cmd_hold(int argc, char *argv[])
 	if (rc < 0)
 		die("listen() on %s: %s\n", sockpath, strerror(errno));
 
-	printf("nstool hold: local PID=%d  local UID=%u  local GID=%u\n",
-	       getpid(), getuid(), getgid());
+	info.pid = getpid();
+	info.uid = getuid();
+	info.gid = getgid();
+
 	do {
 		int afd = accept(fd, NULL, NULL);
 		char buf;
@@ -98,6 +154,12 @@ static void cmd_hold(int argc, char *argv[])
 		if (afd < 0)
 			die("accept(): %s\n", strerror(errno));
 
+		rc = write(afd, &info, sizeof(info));
+		if (rc < 0)
+			die("write(): %s\n", strerror(errno));
+		if ((size_t)rc < sizeof(info))
+			die("short write() on control socket\n");
+
 		rc = read(afd, &buf, sizeof(buf));
 		if (rc < 0)
 			die("read(): %s\n", strerror(errno));
@@ -106,6 +168,68 @@ static void cmd_hold(int argc, char *argv[])
 	unlink(sockpath);
 }
 
+static ssize_t getlink(char *buf, size_t bufsiz, const char *fmt, ...)
+{
+	char linkpath[PATH_MAX];
+	ssize_t linklen;
+	va_list ap;
+
+	va_start(ap, fmt);
+	if (vsnprintf(linkpath, sizeof(linkpath), fmt, ap) >= PATH_MAX)
+		die("Truncated path \"%s\"\n", linkpath);
+	va_end(ap);
+
+	linklen = readlink(linkpath, buf, bufsiz);
+	if (linklen < 0)
+		die("readlink() on %s: %s\n", linkpath, strerror(errno));
+	if ((size_t)linklen >= bufsiz)
+		die("Target of symbolic link %s is too long\n", linkpath);
+
+	return linklen;
+}
+
+static int detect_namespaces(pid_t pid)
+{
+	int i;
+	int flags = 0;
+
+	for (i = 0; i < ARRAY_SIZE(nstypes); i++) {
+		const struct ns_type *nst = &nstypes[i];
+		char selflink[PATH_MAX], pidlink[PATH_MAX];
+		ssize_t selflen, pidlen;
+
+		selflen = getlink(selflink, sizeof(selflink),
+				  "/proc/self/ns/%s", nst->name);
+		pidlen = getlink(pidlink, sizeof(pidlink),
+				 "/proc/%d/ns/%s", pid, nst->name);
+
+		if ((selflen != pidlen) || memcmp(selflink, pidlink, selflen))
+			flags |= nst->flag;
+	}
+
+	return flags;
+}
+
+static void print_nstypes(int flags)
+{
+	bool first = true;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(nstypes); i++) {
+		const struct ns_type *nst = &nstypes[i];
+
+		if (!(flags & nst->flag))
+			continue;
+
+		printf("%s%s", first ? "" : ", " , nst->name);
+		first = false;
+		flags &= ~nst->flag;
+	}
+
+	if (flags)
+		printf("%s0x%x", first ? "" : ", ", flags);
+}
+
 static void cmd_info(int argc, char *argv[])
 {
 	const struct option options[] = {
@@ -114,11 +238,11 @@ static void cmd_info(int argc, char *argv[])
 		{ 0 },
 	};
 	bool pidonly = false, waitforsock = false;
-	struct ucred peercred;
-	socklen_t optlen = sizeof(peercred);
 	const char *optstring = "pw";
+	struct holder_info info;
+	struct ucred peercred;
 	const char *sockpath;
-	int fd, rc, opt;
+	int fd, opt;
 
 	do {
 		opt = getopt_long(argc, argv, optstring, options, NULL);
@@ -144,23 +268,28 @@ static void cmd_info(int argc, char *argv[])
 
 	sockpath = argv[optind];
 
-	fd = connect_ctl(sockpath, waitforsock);
-
-	rc = getsockopt(fd, SOL_SOCKET, SO_PEERCRED,
-			&peercred, &optlen);
-	if (rc < 0)
-		die("getsockopet(SO_PEERCRED) %s: %s\n",
-		    sockpath, strerror(errno));
+	fd = connect_ctl(sockpath, waitforsock, &info, &peercred);
 
 	close(fd);
 
 	if (pidonly) {
 		printf("%d\n", peercred.pid);
 	} else {
+		int flags = detect_namespaces(peercred.pid);
+
+		printf("Namespaces: ");
+		print_nstypes(flags);
+		printf("\n");
+
 		printf("As seen from calling context:\n");
 		printf("\tPID:\t%d\n", peercred.pid);
 		printf("\tUID:\t%u\n", peercred.uid);
 		printf("\tGID:\t%u\n", peercred.gid);
+
+		printf("As seen from holding context:\n");
+		printf("\tPID:\t%d\n", info.pid);
+		printf("\tUID:\t%u\n", info.uid);
+		printf("\tGID:\t%u\n", info.gid);
 	}
 }
 
@@ -173,7 +302,7 @@ static void cmd_stop(int argc, char *argv[])
 	if (argc != 2)
 		usage();
 
-	fd = connect_ctl(sockpath, false);
+	fd = connect_ctl(sockpath, false, NULL, NULL);
 
 	rc = write(fd, &buf, sizeof(buf));
 	if (rc < 0)
-- 
@@ -15,8 +15,13 @@
 #include <errno.h>
 #include <unistd.h>
 #include <getopt.h>
+#include <stdarg.h>
+#include <limits.h>
 #include <sys/socket.h>
 #include <linux/un.h>
+#include <sched.h>
+
+#define	ARRAY_SIZE(a)	((int)(sizeof(a) / sizeof((a)[0])))
 
 #define die(...)				\
 	do {					\
@@ -24,6 +29,28 @@
 		exit(1);			\
 	} while (0)
 
+struct ns_type {
+	int flag;
+	const char *name;
+};
+
+const struct ns_type nstypes[] = {
+	{ CLONE_NEWCGROUP, "cgroup" },
+	{ CLONE_NEWIPC, "ipc" },
+	{ CLONE_NEWNET, "net" },
+	{ CLONE_NEWNS, "mnt" },
+	{ CLONE_NEWPID, "pid" },
+	{ CLONE_NEWTIME, "time" },
+	{ CLONE_NEWUSER, "user" },
+	{ CLONE_NEWUTS, "uts" },
+};
+
+struct holder_info {
+	pid_t pid;
+	uid_t uid;
+	gid_t gid;
+};
+
 static void usage(void)
 {
 	die("Usage:\n"
@@ -41,12 +68,16 @@ static void usage(void)
 	    "    terminate.\n");
 }
 
-static int connect_ctl(const char * sockpath, bool wait)
+static int connect_ctl(const char *sockpath, bool wait,
+		       struct holder_info *info,
+		       struct ucred *peercred)
 {
 	int fd = socket(AF_UNIX, SOCK_STREAM, PF_UNIX);
 	struct sockaddr_un addr = {
 		.sun_family = AF_UNIX,
 	};
+	struct holder_info discard;
+	ssize_t len;
 	int rc;
 
 	if (fd < 0)
@@ -61,6 +92,28 @@ static int connect_ctl(const char * sockpath, bool wait)
 			die("connect() to %s: %s\n", sockpath, strerror(errno));
 	} while (rc < 0);
 
+	if (!info)
+		info = &discard;
+
+	/* Always read the info structure, even if we don't need it,
+	 * so that the holder doesn't get a broken pipe error
+	 */
+	len = read(fd, info, sizeof(*info));
+	if (len < 0)
+		die("read() on control socket %s: %s\n", sockpath, strerror(errno));
+	if ((size_t)len < sizeof(*info))
+		die("short read() on control socket %s\n", sockpath);
+
+	if (peercred) {
+		socklen_t optlen = sizeof(*peercred);
+
+		rc = getsockopt(fd, SOL_SOCKET, SO_PEERCRED,
+				peercred, &optlen);
+		if (rc < 0)
+			die("getsockopet(SO_PEERCRED) %s: %s\n",
+			    sockpath, strerror(errno));
+	}
+
 	return fd;
 }
 
@@ -71,6 +124,7 @@ static void cmd_hold(int argc, char *argv[])
 		.sun_family = AF_UNIX,
 	};
 	const char *sockpath = argv[1];
+	struct holder_info info;
 	int rc;
 
 	if (argc != 2)
@@ -89,8 +143,10 @@ static void cmd_hold(int argc, char *argv[])
 	if (rc < 0)
 		die("listen() on %s: %s\n", sockpath, strerror(errno));
 
-	printf("nstool hold: local PID=%d  local UID=%u  local GID=%u\n",
-	       getpid(), getuid(), getgid());
+	info.pid = getpid();
+	info.uid = getuid();
+	info.gid = getgid();
+
 	do {
 		int afd = accept(fd, NULL, NULL);
 		char buf;
@@ -98,6 +154,12 @@ static void cmd_hold(int argc, char *argv[])
 		if (afd < 0)
 			die("accept(): %s\n", strerror(errno));
 
+		rc = write(afd, &info, sizeof(info));
+		if (rc < 0)
+			die("write(): %s\n", strerror(errno));
+		if ((size_t)rc < sizeof(info))
+			die("short write() on control socket\n");
+
 		rc = read(afd, &buf, sizeof(buf));
 		if (rc < 0)
 			die("read(): %s\n", strerror(errno));
@@ -106,6 +168,68 @@ static void cmd_hold(int argc, char *argv[])
 	unlink(sockpath);
 }
 
+static ssize_t getlink(char *buf, size_t bufsiz, const char *fmt, ...)
+{
+	char linkpath[PATH_MAX];
+	ssize_t linklen;
+	va_list ap;
+
+	va_start(ap, fmt);
+	if (vsnprintf(linkpath, sizeof(linkpath), fmt, ap) >= PATH_MAX)
+		die("Truncated path \"%s\"\n", linkpath);
+	va_end(ap);
+
+	linklen = readlink(linkpath, buf, bufsiz);
+	if (linklen < 0)
+		die("readlink() on %s: %s\n", linkpath, strerror(errno));
+	if ((size_t)linklen >= bufsiz)
+		die("Target of symbolic link %s is too long\n", linkpath);
+
+	return linklen;
+}
+
+static int detect_namespaces(pid_t pid)
+{
+	int i;
+	int flags = 0;
+
+	for (i = 0; i < ARRAY_SIZE(nstypes); i++) {
+		const struct ns_type *nst = &nstypes[i];
+		char selflink[PATH_MAX], pidlink[PATH_MAX];
+		ssize_t selflen, pidlen;
+
+		selflen = getlink(selflink, sizeof(selflink),
+				  "/proc/self/ns/%s", nst->name);
+		pidlen = getlink(pidlink, sizeof(pidlink),
+				 "/proc/%d/ns/%s", pid, nst->name);
+
+		if ((selflen != pidlen) || memcmp(selflink, pidlink, selflen))
+			flags |= nst->flag;
+	}
+
+	return flags;
+}
+
+static void print_nstypes(int flags)
+{
+	bool first = true;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(nstypes); i++) {
+		const struct ns_type *nst = &nstypes[i];
+
+		if (!(flags & nst->flag))
+			continue;
+
+		printf("%s%s", first ? "" : ", " , nst->name);
+		first = false;
+		flags &= ~nst->flag;
+	}
+
+	if (flags)
+		printf("%s0x%x", first ? "" : ", ", flags);
+}
+
 static void cmd_info(int argc, char *argv[])
 {
 	const struct option options[] = {
@@ -114,11 +238,11 @@ static void cmd_info(int argc, char *argv[])
 		{ 0 },
 	};
 	bool pidonly = false, waitforsock = false;
-	struct ucred peercred;
-	socklen_t optlen = sizeof(peercred);
 	const char *optstring = "pw";
+	struct holder_info info;
+	struct ucred peercred;
 	const char *sockpath;
-	int fd, rc, opt;
+	int fd, opt;
 
 	do {
 		opt = getopt_long(argc, argv, optstring, options, NULL);
@@ -144,23 +268,28 @@ static void cmd_info(int argc, char *argv[])
 
 	sockpath = argv[optind];
 
-	fd = connect_ctl(sockpath, waitforsock);
-
-	rc = getsockopt(fd, SOL_SOCKET, SO_PEERCRED,
-			&peercred, &optlen);
-	if (rc < 0)
-		die("getsockopet(SO_PEERCRED) %s: %s\n",
-		    sockpath, strerror(errno));
+	fd = connect_ctl(sockpath, waitforsock, &info, &peercred);
 
 	close(fd);
 
 	if (pidonly) {
 		printf("%d\n", peercred.pid);
 	} else {
+		int flags = detect_namespaces(peercred.pid);
+
+		printf("Namespaces: ");
+		print_nstypes(flags);
+		printf("\n");
+
 		printf("As seen from calling context:\n");
 		printf("\tPID:\t%d\n", peercred.pid);
 		printf("\tUID:\t%u\n", peercred.uid);
 		printf("\tGID:\t%u\n", peercred.gid);
+
+		printf("As seen from holding context:\n");
+		printf("\tPID:\t%d\n", info.pid);
+		printf("\tUID:\t%u\n", info.uid);
+		printf("\tGID:\t%u\n", info.gid);
 	}
 }
 
@@ -173,7 +302,7 @@ static void cmd_stop(int argc, char *argv[])
 	if (argc != 2)
 		usage();
 
-	fd = connect_ctl(sockpath, false);
+	fd = connect_ctl(sockpath, false, NULL, NULL);
 
 	rc = write(fd, &buf, sizeof(buf));
 	if (rc < 0)
-- 
2.39.2


  parent reply	other threads:[~2023-04-06  3:28 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-04-06  3:28 [PATCH v2 00/14] Improved tool for testing across multiple namespaces David Gibson
2023-04-06  3:28 ` [PATCH v2 01/14] nstool: Rename nsholder to nstool David Gibson
2023-04-06  3:28 ` [PATCH v2 02/14] nstool: Reverse parameters " David Gibson
2023-04-06  3:28 ` [PATCH v2 03/14] nstool: Move description of its operation modes from comment to usage David Gibson
2023-04-06  3:28 ` [PATCH v2 04/14] nstool: Split some command line parsing and socket setup to subcommands David Gibson
2023-04-06  3:28 ` [PATCH v2 05/14] nstool: Replace "pid" subcommand with "info" subcommand David Gibson
2023-04-06  3:28 ` David Gibson [this message]
2023-04-06  3:28 ` [PATCH v2 07/14] nstool: Add magic number to advertized information David Gibson
2023-04-06  3:28 ` [PATCH v2 08/14] nstool: Helpers to iterate through namespace types David Gibson
2023-04-06  3:28 ` [PATCH v2 09/14] nstool: Add nstool exec command to execute commands in an nstool namespace David Gibson
2023-04-06  3:28 ` [PATCH v2 10/14] nstool: Add --keep-caps option to nstool exec David Gibson
2023-04-06  3:28 ` [PATCH v2 11/14] test: Initialise ${TRACE} properly David Gibson
2023-04-06  3:28 ` [PATCH v2 12/14] test: Use "nstool exec" to slightly simplify tests David Gibson
2023-04-06  3:28 ` [PATCH v2 13/14] nstool: Advertise the holder's cwd (in its mountns) across the socket David Gibson
2023-04-06  3:28 ` [PATCH v2 14/14] nstool: Enter holder's cwd when changing mount ns with nstool exec David Gibson
2023-04-07 23:12 ` [PATCH v2 00/14] Improved tool for testing across multiple namespaces Stefano Brivio

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230406032819.707441-7-david@gibson.dropbear.id.au \
    --to=david@gibson.dropbear.id.au \
    --cc=passt-dev@passt.top \
    --cc=sbrivio@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).