git: 6c744621c720 - main - truss: add AF_NETLINK syscall argument decoding support

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Wed, 24 Jun 2026 14:50:22 UTC
The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=6c744621c72062edf176ecf28b92ad10e8264454

commit 6c744621c72062edf176ecf28b92ad10e8264454
Author:     Ishan Agrawal <iagrawal9990@gmail.com>
AuthorDate: 2026-06-24 05:20:15 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2026-06-24 14:50:04 +0000

    truss: add AF_NETLINK syscall argument decoding support
    
    Track AF_NETLINK socket file descriptors in procinfo using fd_domain
    entries and use them to detect Netlink traffic across later syscalls.
    
    Add Netlink payload decoding support for sendto(), recvfrom(), and
    sendmsg() paths by integrating sysdecode_netlink() into BinString and
    Msghdr argument handling, with fallback to existing binary/iovec
    printing when decoding fails.
    
    Reviewed by:    kp
    Signed-off-by:  Ishan Agrawal <iagrawal9990@gmail.com>
    Sponsored by:   Google LLC (GSoC 2026)
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/2295
---
 usr.bin/truss/setup.c    | 40 ++++++++++++++++++++++++++--
 usr.bin/truss/syscall.h  |  2 +-
 usr.bin/truss/syscalls.c | 68 ++++++++++++++++++++++++++----------------------
 usr.bin/truss/truss.h    |  9 +++++++
 4 files changed, 85 insertions(+), 34 deletions(-)

diff --git a/usr.bin/truss/setup.c b/usr.bin/truss/setup.c
index d5fa1ed50ce1..ecc4a1792144 100644
--- a/usr.bin/truss/setup.c
+++ b/usr.bin/truss/setup.c
@@ -340,6 +340,7 @@ new_proc(struct trussinfo *info, pid_t pid, lwpid_t lwpid)
 	np->pid = pid;
 	np->abi = find_abi(pid);
 	LIST_INIT(&np->threadlist);
+	LIST_INIT(&np->fdlist);
 	LIST_INSERT_HEAD(&info->proclist, np, entries);
 
 	if (lwpid != 0)
@@ -352,10 +353,16 @@ static void
 free_proc(struct procinfo *p)
 {
 	struct threadinfo *t, *t2;
+	struct fd_domain *f, *f2;
 
 	LIST_FOREACH_SAFE(t, &p->threadlist, entries, t2) {
 		free(t);
 	}
+
+	LIST_FOREACH_SAFE(f, &p->fdlist, entries, f2) {
+		free(f);
+	}
+
 	LIST_REMOVE(p, entries);
 	free(p);
 }
@@ -492,7 +499,7 @@ enter_syscall(struct trussinfo *info, struct threadinfo *t,
 #endif
 		if (!(sc->decode.args[i].type & OUT)) {
 			t->cs.s_args[i] = print_arg(&sc->decode.args[i],
-			    t->cs.args, NULL, info);
+			    t->cs.args, NULL, info, &sc->decode);
 		}
 	}
 #if DEBUG
@@ -562,12 +569,41 @@ exit_syscall(struct trussinfo *info, struct ptrace_lwpinfo *pl)
 				    (long)t->cs.args[sc->decode.args[i].offset]);
 			} else {
 				temp = print_arg(&sc->decode.args[i],
-				    t->cs.args, psr.sr_retval, info);
+				    t->cs.args, psr.sr_retval, info,
+				    &sc->decode);
 			}
 			t->cs.s_args[i] = temp;
 		}
 	}
 
+	/*
+	 * Track successfully created sockets so later syscalls using the
+	 * returned file descriptor can be identified by socket domain.
+	 */
+	if (strcmp(sc->name, "socket") == 0 &&
+	    psr.sr_error == 0) {
+
+		struct fd_domain *f = calloc(1, sizeof(*f));
+
+		f->fd = (int)psr.sr_retval[0];
+		f->domain = (int)t->cs.args[0];
+
+		LIST_INSERT_HEAD(&p->fdlist, f, entries);
+	}
+
+	if (strcmp(sc->name, "close") == 0 &&
+	    psr.sr_error == 0) {
+
+		struct fd_domain *f, *f2;
+
+		LIST_FOREACH_SAFE(f, &p->fdlist, entries, f2) {
+			if (f->fd == (int)t->cs.args[0]) {
+				LIST_REMOVE(f, entries);
+				free(f);
+			}
+		}
+	}
+
 	print_syscall_ret(info, psr.sr_error, psr.sr_retval);
 	free_syscall(t);
 
diff --git a/usr.bin/truss/syscall.h b/usr.bin/truss/syscall.h
index 47d973326dfb..2f98636173b7 100644
--- a/usr.bin/truss/syscall.h
+++ b/usr.bin/truss/syscall.h
@@ -229,7 +229,7 @@ struct syscall {
 
 struct syscall *get_syscall(struct threadinfo *, u_int, u_int);
 char *print_arg(struct syscall_arg *, syscallarg_t *, syscallarg_t *,
-    struct trussinfo *);
+    struct trussinfo *, struct syscall_decode *);
 
 /*
  * Linux Socket defines
diff --git a/usr.bin/truss/syscalls.c b/usr.bin/truss/syscalls.c
index abb9d18d6783..dbc621124071 100644
--- a/usr.bin/truss/syscalls.c
+++ b/usr.bin/truss/syscalls.c
@@ -1569,39 +1569,40 @@ user_ptr32_to_psaddr(int32_t user_pointer)
 	return ((psaddr_t)(uintptr_t)user_pointer);
 }
 
+/*
+ * Check whether a file descriptor belongs to an AF_NETLINK socket
+ * for the current traced process.
+ */
+static bool
+is_netlink(struct trussinfo *trussinfo, int num_fd)
+{
+	struct procinfo *p = trussinfo->curthread->proc;
+	struct fd_domain *f;
+
+	LIST_FOREACH(f, &p->fdlist, entries) {
+		if (f->fd == num_fd && f->domain == AF_NETLINK)
+			return (true);
+	}
+	return (false);
+}
+
 #define NETLINK_MAX_DECODE 4096
 
 /*
- * Reads the first IOV and attempts to print it as Netlink using libsysdecode.
- * Returns true if successful, false if fallback to standard print is needed.
+ * Copy a potential Netlink message buffer from the traced process
+ * and attempt to decode and print it.
  */
 static bool
-print_netlink(FILE *fp, struct trussinfo *trussinfo, struct msghdr *msg)
+print_netlink(FILE *fp, struct trussinfo *trussinfo, void *msg, size_t len)
 {
-	struct sockaddr_storage ss;
-	struct iovec iov;
-	struct ptrace_io_desc piod;
 	char *buf;
 	pid_t pid = trussinfo->curthread->proc->pid;
 	bool success = false;
 
-	/* Only decode AF_NETLINK sockets. */
-	if (msg->msg_name == NULL || msg->msg_namelen < offsetof(struct sockaddr, sa_data)
-		|| msg->msg_iovlen == 0 || msg->msg_iov == NULL)
+	if (msg == NULL || len == 0)
 		return (false);
 
-	if (get_struct(pid, (uintptr_t)msg->msg_name, &ss,
-	    MIN(sizeof(ss), msg->msg_namelen)) == -1)
-		return (false);
-
-	if (ss.ss_family != AF_NETLINK)
-		return (false);
-
-	if (get_struct(pid, (uintptr_t)msg->msg_iov, &iov, sizeof(iov)) == -1)
-		return (false);
-
-	/* Cap read size to avoid unbounded allocations. */
-	size_t read_len = MIN(iov.iov_len, NETLINK_MAX_DECODE);
+	size_t read_len = MIN(len, NETLINK_MAX_DECODE);
 	if (read_len == 0)
 		return (false);
 
@@ -1609,18 +1610,11 @@ print_netlink(FILE *fp, struct trussinfo *trussinfo, struct msghdr *msg)
 	if (buf == NULL)
 		return (false);
 
-	/* Snapshot User Memory using PTRACE. */
-	piod.piod_op = PIOD_READ_D;
-	piod.piod_offs = iov.iov_base;
-	piod.piod_addr = buf;
-	piod.piod_len = read_len;
-
-	if (ptrace(PT_IO, pid, (caddr_t)&piod, 0) == -1) {
+	if (get_struct(pid, (uintptr_t)msg, buf, read_len) == -1) {
 		free(buf);
 		return (false);
 	}
 
-	/* Delegate Decoding to libsysdecode. */
 	if (sysdecode_netlink(fp, buf, read_len)) {
 		success = true;
 	}
@@ -1637,7 +1631,7 @@ print_netlink(FILE *fp, struct trussinfo *trussinfo, struct msghdr *msg)
  */
 char *
 print_arg(struct syscall_arg *sc, syscallarg_t *args, syscallarg_t *retval,
-    struct trussinfo *trussinfo)
+    struct trussinfo *trussinfo, struct syscall_decode *decode)
 {
 	FILE *fp;
 	char *tmp;
@@ -1695,6 +1689,13 @@ print_arg(struct syscall_arg *sc, syscallarg_t *args, syscallarg_t *retval,
 		break;
 	}
 	case BinString: {
+		if ((strcmp(decode->name, "sendto") == 0 ||
+		    strcmp(decode->name, "recvfrom") == 0) &&
+		    is_netlink(trussinfo, (int)args[0]))
+			if (print_netlink(fp, trussinfo,
+			    (void *)args[sc->offset],
+			    (size_t)args[sc->offset + 1]))
+				break;
 		/*
 		 * Binary block of data that might have printable characters.
 		 * XXX If type|OUT, assume that the length is the syscall's
@@ -2758,6 +2759,7 @@ print_arg(struct syscall_arg *sc, syscallarg_t *args, syscallarg_t *retval,
 	}
 	case Msghdr: {
 		struct msghdr msghdr;
+		struct iovec iov;
 
 		if (get_struct(pid, args[sc->offset],
 		    &msghdr, sizeof(struct msghdr)) == -1) {
@@ -2768,7 +2770,11 @@ print_arg(struct syscall_arg *sc, syscallarg_t *args, syscallarg_t *retval,
 		print_sockaddr(fp, trussinfo, (uintptr_t)msghdr.msg_name, msghdr.msg_namelen);
 		fprintf(fp, ",%d,", msghdr.msg_namelen);
 		/* Attempt Netlink decode; fallback to standard iovec if it fails. */
-		if (!print_netlink(fp, trussinfo, &msghdr)) {
+		if ((!is_netlink(trussinfo, (int)args[0])) ||
+		    (get_struct(pid, (uintptr_t)msghdr.msg_iov,
+		    &iov, sizeof(iov)) == -1) ||
+		    (!print_netlink(fp, trussinfo, (void *)iov.iov_base,
+		    (size_t)iov.iov_len))) {
 			print_iovec(fp, trussinfo, (uintptr_t)msghdr.msg_iov,
 			    msghdr.msg_iovlen);
 		}
diff --git a/usr.bin/truss/truss.h b/usr.bin/truss/truss.h
index e895ca27c26e..737ebc184806 100644
--- a/usr.bin/truss/truss.h
+++ b/usr.bin/truss/truss.h
@@ -94,12 +94,21 @@ struct threadinfo
 	struct timespec after;
 };
 
+struct fd_domain
+{
+	LIST_ENTRY(fd_domain) entries;
+
+	int fd;
+	int domain;
+};
+
 struct procinfo {
 	LIST_ENTRY(procinfo) entries;
 	pid_t pid;
 	struct procabi *abi;
 
 	LIST_HEAD(, threadinfo) threadlist;
+	LIST_HEAD(, fd_domain) fdlist;
 };
 
 struct trussinfo