tOS Keyboard Driver — Unconditional Interrupt Re-enable Allows Nested-Interrupt Reentrancy
In keyboard_poll() of keyboard.c, an unconditional sti at the end of the function re-enabled maskable interrupts regardless of the caller's context. keyboard_poll() is reachable from keyboard_getchar(), which a ring3 .t program can block on via SYS_READ (entered through the int $0x80 interrupt gate, which clears IF for the duration of the syscall by design). The unconditional sti silently re-enabled interrupts in the middle of that syscall handler, allowing the 100Hz PIT timer (int $32) to preempt mid-handler — a nested hardware interrupt occurring while the CPU was already inside another interrupt's own trap-gate handler. This corrupted ring3 register and stack state on return to user mode, producing a reliable General Protection Fault the moment a userspace program performed its first blocking keyboard read. Any ring3 program using tos_read()/tos_readline() on stdin could trigger this deterministically, resulting in denial of service (full system halt via the kernel's own unrecoverable panic handler).
While automatically auditing tOS kernel's interrupt handling code, ArtficalAI tAI 4.2 found an unconditional sti instruction at the end of keyboard_poll() in keyboard.c, operating completely independently of the caller's context. Combined with the fact that this function is reachable during blocking syscalls issued from ring3, this turned into a serious issue.
keyboard_poll() re-enabled maskable interrupts via an unconditional sti at the end of the function, regardless of the caller's context. keyboard_poll() is reachable from keyboard_getchar(), which a ring3 .t program can block on via SYS_READ, entered through the int $0x80 interrupt gate, which clears IF for the duration of the syscall by design. The unconditional sti silently re-enabled interrupts in the middle of that syscall handler, allowing the 100Hz PIT timer (int $32) to preempt mid-handler — a nested hardware interrupt occurring while the CPU was already inside another interrupt's own trap-gate handler. This corrupted ring3 register and stack state on return to user mode, producing a reliable General Protection Fault the moment a userspace program performed its first blocking keyboard read.
Any ring3 program using tos_read()/tos_readline() on stdin could trigger this deterministically. The moment the program performed a blocking keyboard read, the PIT timer interrupt would preempt in the middle of keyboard_poll()'s syscall handler, corrupting ring3 register and stack state and causing a General Protection Fault on return to user mode. The result was a full system halt via the kernel's own unrecoverable panic handler — denial of service.
keyboard_poll() never saved the incoming EFLAGS.IF bit and instead issued an unconditional sti at the end of the function, re-enabling interrupts even in contexts where the caller had deliberately kept them disabled (such as inside the int $0x80 syscall handler), leading to nested-interrupt reentrancy.
keyboard_poll() now saves the incoming EFLAGS.IF bit with pushfl/popfl and only restores interrupts to that original state, instead of unconditionally issuing sti. A related deadlock introduced by an intermediate version of the fix — a bare hlt with interrupts held off could hang the machine before a key was ever pressed — is resolved by using a pause busy-wait instead, which does not depend on interrupts to resume.
CWE-667, CWE-362, CWE-696
ArtficalAI tAI 4.2