git: 09dfe066f00c - main - kernel: copyout extended errors to userspace and add exterrctl(2) to control it

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Sat, 31 May 2025 19:52:50 UTC
The branch main has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=09dfe066f00c927e88c23265387d432e6d9f0c5e

commit 09dfe066f00c927e88c23265387d432e6d9f0c5e
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2025-05-23 05:01:39 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2025-05-31 19:52:41 +0000

    kernel: copyout extended errors to userspace and add exterrctl(2) to control it
    
    Reviewed by:    brooks
    Sponsored by:   The FreeBSD Foundation
    MFC after:      2 weeks
    Differential revision:  https://reviews.freebsd.org/D50483
---
 sys/kern/kern_exec.c     |  1 +
 sys/kern/kern_fork.c     |  1 +
 sys/kern/subr_syscall.c  |  4 +++
 sys/kern/sys_generic.c   | 73 +++++++++++++++++++++++++++++++++++++++++++++++-
 sys/kern/syscalls.master |  8 +++++-
 sys/sys/exterrvar.h      | 13 +++++++++
 sys/sys/proc.h           |  2 ++
 sys/sys/uio.h            |  1 +
 8 files changed, 101 insertions(+), 2 deletions(-)

diff --git a/sys/kern/kern_exec.c b/sys/kern/kern_exec.c
index a943ec339e75..cf067527237e 100644
--- a/sys/kern/kern_exec.c
+++ b/sys/kern/kern_exec.c
@@ -809,6 +809,7 @@ interpret:
 	 * it that it now has its own resources back
 	 */
 	p->p_flag |= P_EXEC;
+	td->td_pflags2 &= ~TDP2_UEXTERR;
 	if ((p->p_flag2 & P2_NOTRACE_EXEC) == 0)
 		p->p_flag2 &= ~P2_NOTRACE;
 	if ((p->p_flag2 & P2_STKGAP_DISABLE_EXEC) == 0)
diff --git a/sys/kern/kern_fork.c b/sys/kern/kern_fork.c
index 494f06cc0621..2ab9b363f8b5 100644
--- a/sys/kern/kern_fork.c
+++ b/sys/kern/kern_fork.c
@@ -609,6 +609,7 @@ do_fork(struct thread *td, struct fork_req *fr, struct proc *p2, struct thread *
 	 */
 	p2->p_flag |= p1->p_flag & P_SUGID;
 	td2->td_pflags |= td->td_pflags & (TDP_ALTSTACK | TDP_SIGFASTBLOCK);
+	td2->td_pflags2 |= td->td_pflags2 & TDP2_UEXTERR;
 	SESS_LOCK(p1->p_session);
 	if (p1->p_session->s_ttyvp != NULL && p1->p_flag & P_CONTROLT)
 		p2->p_flag |= P_CONTROLT;
diff --git a/sys/kern/subr_syscall.c b/sys/kern/subr_syscall.c
index 16fa47c5605a..d5b3b62f0821 100644
--- a/sys/kern/subr_syscall.c
+++ b/sys/kern/subr_syscall.c
@@ -74,6 +74,8 @@ syscallenter(struct thread *td)
 			td->td_dbgflags |= TDB_SCE;
 		PROC_UNLOCK(p);
 	}
+	if ((td->td_pflags2 & TDP2_UEXTERR) != 0)
+		td->td_pflags2 &= ~TDP2_EXTERR;
 	error = (p->p_sysent->sv_fetch_syscall_args)(td);
 	se = sa->callp;
 #ifdef KTRACE
@@ -207,6 +209,8 @@ syscallenter(struct thread *td)
 		PROC_UNLOCK(p);
 	}
 	(p->p_sysent->sv_set_syscall_retval)(td, error);
+	if (error != 0 && (td->td_pflags2 & TDP2_UEXTERR) != 0)
+		exterr_copyout(td);
 }
 
 static inline void
diff --git a/sys/kern/sys_generic.c b/sys/kern/sys_generic.c
index dd9c28e81388..91bf3e93fa7c 100644
--- a/sys/kern/sys_generic.c
+++ b/sys/kern/sys_generic.c
@@ -34,10 +34,10 @@
  * SUCH DAMAGE.
  */
 
-#include <sys/cdefs.h>
 #include "opt_capsicum.h"
 #include "opt_ktrace.h"
 
+#define	EXTERR_CATEGORY	EXTERR_CAT_FILEDESC
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/sysproto.h>
@@ -46,6 +46,7 @@
 #include <sys/filio.h>
 #include <sys/fcntl.h>
 #include <sys/file.h>
+#include <sys/exterrvar.h>
 #include <sys/lock.h>
 #include <sys/proc.h>
 #include <sys/signalvar.h>
@@ -2200,3 +2201,73 @@ file_kcmp_generic(struct file *fp1, struct file *fp2, struct thread *td)
 		return (3);
 	return (kcmp_cmp((uintptr_t)fp1->f_data, (uintptr_t)fp2->f_data));
 }
+
+void
+exterr_copyout(struct thread *td)
+{
+	struct uexterror ue;
+	ksiginfo_t ksi;
+	void *uloc;
+	size_t sz;
+	int error;
+
+	MPASS((td->td_pflags2 & TDP2_UEXTERR) != 0);
+
+	uloc = (char *)td->td_exterr_ptr + __offsetof(struct uexterror,
+	    error);
+	if ((td->td_pflags2 & TDP2_EXTERR) == 0) {
+		ue.error = 0;
+		sz = sizeof(ue.error);
+	} else {
+		memset(&ue, 0, sizeof(ue));
+		ue.error = td->td_kexterr.error;
+		ue.cat = td->td_kexterr.cat;
+		ue.src_line = td->td_kexterr.src_line;
+		ue.p1 = td->td_kexterr.p1;
+		ue.p2 = td->td_kexterr.p2;
+		if (td->td_kexterr.msg != NULL)
+			strlcpy(ue.msg, td->td_kexterr.msg, sizeof(ue.msg));
+		sz = sizeof(ue) - __offsetof(struct uexterror, error);
+	}
+	error = copyout(&ue.error, uloc, sz);
+	if (error != 0) {
+		td->td_pflags2 &= ~TDP2_UEXTERR;
+		ksiginfo_init_trap(&ksi);
+		ksi.ksi_signo = SIGSEGV;
+		ksi.ksi_code = SEGV_ACCERR;
+		ksi.ksi_addr = uloc;
+		trapsignal(td, &ksi);
+	}
+}
+
+int
+sys_exterrctl(struct thread *td, struct exterrctl_args *uap)
+{
+	uint32_t ver;
+	int error;
+
+	if ((uap->flags & ~(EXTERRCTLF_FORCE)) != 0)
+		return (EINVAL);
+	switch (uap->op) {
+	case EXTERRCTL_ENABLE:
+		if ((td->td_pflags2 & TDP2_UEXTERR) != 0 &&
+		    (uap->flags & EXTERRCTLF_FORCE) == 0)
+			return (EBUSY);
+		td->td_pflags2 &= ~TDP2_UEXTERR;
+		error = copyin(uap->ptr, &ver, sizeof(ver));
+		if (error != 0)
+			return (error);
+		if (ver != UEXTERROR_VER)
+			return (EINVAL);
+		td->td_pflags2 |= TDP2_UEXTERR;
+		td->td_exterr_ptr = uap->ptr;
+		return (0);
+	case EXTERRCTL_DISABLE:
+		if ((td->td_pflags2 & TDP2_UEXTERR) == 0)
+			return (EINVAL);
+		td->td_pflags2 &= ~TDP2_UEXTERR;
+		return (0);
+	default:
+		return (EINVAL);
+	}
+}
diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master
index 67396a4cabc5..08b557a7a540 100644
--- a/sys/kern/syscalls.master
+++ b/sys/kern/syscalls.master
@@ -3349,5 +3349,11 @@
 		    size_t size
 		);
 	}
-
+592	AUE_NULL	STD {
+		int exterrctl(
+		    u_int op,
+		    u_int flags,
+		    _In_reads_bytes_(4) void *ptr
+		);
+	}
 ; vim: syntax=off
diff --git a/sys/sys/exterrvar.h b/sys/sys/exterrvar.h
index 6e392ff2c18c..5afcd82b136a 100644
--- a/sys/sys/exterrvar.h
+++ b/sys/sys/exterrvar.h
@@ -26,6 +26,13 @@ struct uexterror {
 	char msg[128];
 };
 
+#define	UEXTERROR_VER		0x10010001
+
+#define	EXTERRCTL_ENABLE	1
+#define	EXTERRCTL_DISABLE	2
+
+#define	EXTERRCTLF_FORCE	0x00000001
+
 #ifdef _KERNEL
 
 #ifndef EXTERR_CATEGORY
@@ -53,6 +60,12 @@ struct uexterror {
 #define	SET_ERROR0(eerror, mmsg)	SET_ERROR2(eerror, mmsg, 0, 0)
 #define	SET_ERROR1(eerror, mmsg, pp1)	SET_ERROR2(eerror, mmsg, pp1, 0)
 
+#else	/* _KERNEL */
+
+__BEGIN_DECLS
+int exterrctl(u_int op, u_int flags, void *ptr);
+__END_DECLS
+
 #endif	/* _KERNEL */
 
 #endif
diff --git a/sys/sys/proc.h b/sys/sys/proc.h
index cab487719c31..b48681420028 100644
--- a/sys/sys/proc.h
+++ b/sys/sys/proc.h
@@ -343,6 +343,7 @@ struct thread {
 	void		*td_sigblock_ptr; /* (k) uptr for fast sigblock. */
 	uint32_t	td_sigblock_val;  /* (k) fast sigblock value read at
 					     td_sigblock_ptr on kern entry */
+	void		*td_exterr_ptr;
 #define	td_endcopy td_pcb
 
 /*
@@ -572,6 +573,7 @@ enum {
 #define	TDP2_ACCT	0x00000004 /* Doing accounting */
 #define	TDP2_SAN_QUIET	0x00000008 /* Disable warnings from K(A|M)SAN */
 #define	TDP2_EXTERR	0x00000010 /* Kernel reported ext error */
+#define	TDP2_UEXTERR	0x00000020 /* User set ext error reporting ptr */
 
 /*
  * Reasons that the current thread can not be run yet.
diff --git a/sys/sys/uio.h b/sys/sys/uio.h
index ec4e92d852a6..05c1ed640b63 100644
--- a/sys/sys/uio.h
+++ b/sys/sys/uio.h
@@ -84,6 +84,7 @@ int	copyiniov(const struct iovec *iovp, u_int iovcnt, struct iovec **iov,
 int	copyinuio(const struct iovec *iovp, u_int iovcnt, struct uio **uiop);
 int	copyout_map(struct thread *td, vm_offset_t *addr, size_t sz);
 int	copyout_unmap(struct thread *td, vm_offset_t addr, size_t sz);
+void	exterr_copyout(struct thread *td);
 int	physcopyin(void *src, vm_paddr_t dst, size_t len);
 int	physcopyout(vm_paddr_t src, void *dst, size_t len);
 int	physcopyin_vlist(struct bus_dma_segment *src, off_t offset,