Hi, In sys/kern/kern_ktrace.c, ktrpsig() stack-allocates a struct ktr_psig and assigns its fields individually, but does not zero the struct first. The struct contains a 4-byte alignment hole at offset 4 (between "int signo" and "sig_t action"), which is then written verbatim to the ktrace output file via ktrwrite().
This is the same class as the April 2026 fixes for shm_internal
(1b900a0, deraadt) and sem_base (76d3556, dgl) -- uninitialised
kernel-stack data exposed to userland through a same-UID syscall path.
Source (current master, sys/kern/kern_ktrace.c line 283):
void
ktrpsig(struct proc *p, int sig, sig_t action, int mask, int code,
siginfo_t *si)
{
struct ktr_header kth;
struct ktr_psig kp; /* not memset before field assignments */
atomic_setbits_int(&p->p_flag, P_INKTR);
ktrinitheader(&kth, p, KTR_PSIG);
kp.signo = (char)sig;
kp.action = action;
kp.mask = mask;
kp.code = code;
kp.si = *si;
KERNEL_LOCK();
ktrwrite(p, &kth, &kp, sizeof(kp));
KERNEL_UNLOCK();
atomic_clearbits_int(&p->p_flag, P_INKTR);
}
struct ktr_psig layout (sys/sys/ktrace.h):
int signo; /* offset 0 */
/* offset 4: 4-byte padding hole (sig_t
alignment) */
sig_t action; /* offset 8 */
int mask; /* offset 16 */
int code; /* offset 20 */
siginfo_t si; /* offset 24 */
The padding hole is never assigned anywhere in the function.
Tested on OpenBSD 7.7 arm64 (GENERIC.MP #361, 22 Apr 2025) in a UTM VM.
A PoC that traces its own process with KTRFAC_PSIG and raises 32 signals
interleaved with various other syscalls produces 32 PSIG records of
which 13 (40%) carry the value 0xffffff80 in the padding hole -- the
upper 32 bits of an arm64 kernel virtual address (TTBR1 region prefix).
The remaining 19 (59%) carry 0x00000000, consistent with the slot being
overwritten by zero by an intervening kernel function on those code
paths.
Verbatim hexdump of a leaking record:
00000080 1e 00 00 00 80 ff ff ff c0 14 9e 95 01 00 00 00
signo=30 PADDING action=0x1959e14c0
SIGUSR1 LEAKED
0xffffff80
I am not claiming this is an exploitable KASLR weakening -- on arm64
the 0xffffff80 prefix is constant across the architecture, so it does
not disclose anything secret. But the structurally-equivalent reference
fixes were treated as worth correcting on hygiene grounds, and I think
the same applies here: the kernel should not be writing uninitialised
stack contents into a user-readable file, even on an opt-in trace
interface. On a different stack history or a different architecture
(amd64 not tested in this session) the leaked bytes could plausibly
carry less innocuous data.
Suggested fix:
--- sys/kern/kern_ktrace.c
+++ sys/kern/kern_ktrace.c
@@ -285,6 +285,7 @@ ktrpsig(struct proc *p, int sig, sig_t action,
struct ktr_header kth;
struct ktr_psig kp;
+ memset(&kp, 0, sizeof(kp));
atomic_setbits_int(&p->p_flag, P_INKTR);
ktrinitheader(&kth, p, KTR_PSIG);
kp.signo = (char)sig;
PoC sources attached:
- POC_ktr_psig_padding.c: minimal reproducer, 16 PSIG records
- POC_ktr_entropy.c: extended PoC, 32 records across 4 signal types
with interleaved syscalls, showing the 40%/59% distribution
Both build with: cc -O0 -o poc poc.c
Honestly noted:
- amd64 not tested by me; padding rules are arch-agnostic on LP64 so
the layout will be the same, but the values landing in the slot may
differ.
- The bug is opt-in (ktrace must be enabled) and same-UID.
- The constant 0xffffff80 prefix is not by itself a KASLR defeat.
Reporting this primarily as a hygiene/consistency fix matching
1b900a0 and 76d3556.
Thanks,
Stuart Thomas
--
*please note, there is no expectation for you to read/reply to my email
outside your normal working hours. *
POC_ktr_psig_padding.c
Description: Binary data
POC_ktr_entropy.c
Description: Binary data
