diff --git a/src/sigTest.c b/src/sigTest.c index ab0889d..29c3c87 100644 --- a/src/sigTest.c +++ b/src/sigTest.c @@ -936,3 +936,162 @@ KTEST(sigConsumePendingNestedHandlerNeverInheritsOuterRetryState) KT_TRUE(t, threadDiscard(id)); } +KTEST(sigOnSigReturnRestoresExactPreSignalContext) +{ + uint32 id = threadCreate("sigReturnTarget", noopThreadEntry); + KT_NE(t, id, threadInvalidId); + Thread* thread = threadAt(id); + Registers regs = makeUserRegs(); + Registers before = regs; + int signum = -1; + + sigSetHandler(thread, sigUsr1, 0x40009000ull, 0, 0); + sigProcMask(thread, sigMaskBlock, (1ull << sigUsr2)); + uint64 blockedBefore = thread->sigBlocked; + sigRaise(id, sigUsr1); + KT_EQ(t, sigConsumePending(thread, ®s, &signum), sigOutcomeHandler); + + /* regs now holds whatever the trampoline/handler would have left it + as by the time it traps into sysSigReturn -- simulate that instead + of actually running user code, since this file has no ring 3. */ + regs.rax = 0xDEADBEEFull; + regs.rdi = 0xFEEDFACEull; + + KT_TRUE(t, sigOnSigReturn(thread, ®s)); + KT_EQ(t, memcmp(®s, &before, sizeof(before)), 0); + KT_EQU(t, thread->sigHandlerDepth, 0u); + KT_EQU(t, thread->sigBlocked, blockedBefore); + + /* Misuse: calling sigreturn while NOT actually inside a handler must be + a safe no-op, not a crash or a silent corruption of an unrelated + trapframe. */ + Registers untouched = regs; + KT_FALSE(t, sigOnSigReturn(thread, ®s)); + KT_EQ(t, memcmp(®s, &untouched, sizeof(untouched)), 0); + + KT_TRUE(t, threadDiscard(id)); +} + +/* A table slot recycled by threadDiscard/threadReap must never let a dead + process's signal state leak into whatever unrelated process reuses its + id next. Today that is guaranteed twice over -- threadReap/threadDiscard + already memset() the whole Thread struct before any slot is handed back + to allocSlot, and sigResetState does it again explicitly from + threadCreate (see sched.h's field comment) -- so this test does not + currently isolate sigResetState's own contribution; it locks in the + observable end-to-end property regardless of which of the two is doing + the work, the same way the console-reader/pipe-fd leak fixes (see + keyboard.c/sched.c history) earned tests for state that lived OUTSIDE + Thread and had no such blanket memset to fall back on. */ +KTEST(threadCreateResetsSignalStateOnRecycledSlot) +{ + uint32 first = threadCreate("sigLeakA", noopThreadEntry); + KT_NE(t, first, threadInvalidId); + Thread* a = threadAt(first); + + sigSetHandler(a, sigUsr1, 0x40009000ull, (1ull << sigUsr2), 0); + sigProcMask(a, sigMaskBlock, (1ull << sigUsr2)); + sigRaise(first, sigUsr1); + Registers regs = makeUserRegs(); + int signum = -1; + /* Drives `a` into sigHandlerDepth==1 (via the installed handler above) + so that field's reset gets exercised too, not just + sigPending/sigBlocked/sigHandlers/sigHandlerMasks. */ + KT_EQ(t, sigConsumePending(a, ®s, &signum), sigOutcomeHandler); + KT_EQU(t, a->sigHandlerDepth, 1u); + + KT_TRUE(t, threadDiscard(first)); + + /* allocSlot's own round-robin cursor (sched.c) no longer guarantees + the very next create reuses THIS exact slot -- drive it around + until it does, discarding every other slot it hands back along the + way, so this test still deterministically exercises a real recycle + of `first` regardless of where the cursor currently sits. Bounded + by threadCapacity(): the cursor sweeps every currently-live index + at most once per full lap, and nothing else in this test holds a + slot open forever, so `first` is guaranteed to come back around + well within that many attempts. */ + uint32 second = threadInvalidId; + for (uint32 attempt = 0; attempt < threadCapacity(); attempt++) { + uint32 candidate = threadCreate("sigLeakB", noopThreadEntry); + KT_NE(t, candidate, threadInvalidId); + if (candidate == first) { + second = candidate; + break; + } + KT_TRUE(t, threadDiscard(candidate)); + } + KT_EQ(t, second, first); + Thread* b = threadAt(second); + + KT_EQU(t, b->sigPending, 0ull); + KT_EQU(t, b->sigBlocked, 0ull); + KT_EQU(t, b->sigHandlerDepth, 0u); + KT_EQU(t, b->sigSavedBlocked[0], 0ull); + for (int signumIndex = 1; signumIndex < sigMaxSignals; signumIndex++) { + KT_EQU(t, b->sigHandlers[signumIndex], sigHandlerDefault); + KT_EQU(t, b->sigHandlerMasks[signumIndex], 0ull); + } + + KT_TRUE(t, threadDiscard(second)); +} + +/* + * sigFlagSigInfo / SigInfo (signalnum.h) -- signal.c's sigConsumePending + * populating the shared sigInfoVirtualAddr page and rewriting rsi/rdx to + * hand a sigFlagSigInfo handler a real (SigInfo*, NULL) pair, deterministic + * counterpart to the live ring-3 round trip in siginfotest.c/ + * siginfotarget.c (ci-test.sh needles "siginfotarget: siginfo-sender-pid-ok" + * / "siginfotest: sigchld-siginfo-accurate-ok"), which cannot itself prove + * the EXACT bytes sigConsumePending computed the way direct access to + * `regs`/the dereferenced pointer here can. + */ + +KTEST(sigConsumePendingPopulatesSigInfoForSigFlagSigInfoHandler) +{ + uint32 id = threadCreate("sigInfoTarget", noopThreadEntry); + KT_NE(t, id, threadInvalidId); + Thread* thread = threadAt(id); + + sigSetHandler(thread, sigUsr1, 0x40009000ull, 0, sigFlagSigInfo); + KT_TRUE(t, sigRaiseInfo(id, sigUsr1, siCodeUser, 4242, 99)); + + Registers regs = makeUserRegs(); + int signum = -1; + KT_EQ(t, sigConsumePending(thread, ®s, &signum), sigOutcomeHandler); + KT_EQ(t, signum, sigUsr1); + + /* rdx is always NULL (no ucontext_t this kernel can expose) -- see + signal.h's sigConsumePending doc comment. */ + KT_EQU(t, regs.rdx, 0ull); + + /* rsi is a real pointer into the shared, kernel-populated page -- not + just "some nonzero value", dereferencing it must show exactly what + sigRaiseInfo attached above. */ + KT_NE(t, regs.rsi, 0ull); + SigInfo* info = (SigInfo*)regs.rsi; + KT_EQ(t, info->siSignum, sigUsr1); + KT_EQ(t, info->siCode, siCodeUser); + KT_EQ(t, info->siPid, 4242); + KT_EQ(t, info->siStatus, 99); + + KT_TRUE(t, threadDiscard(id)); +} + +KTEST(sigConsumePendingLeavesRsiRdxUntouchedWithoutSigFlagSigInfo) +{ + uint32 id = threadCreate("sigNoInfoTarget", noopThreadEntry); + KT_NE(t, id, threadInvalidId); + Thread* thread = threadAt(id); + + /* No sigFlagSigInfo this time -- a plain one-argument handler, exactly + like every signal demo before this session. */ + sigSetHandler(thread, sigUsr1, 0x40009000ull, 0, 0); + KT_TRUE(t, sigRaise(id, sigUsr1)); + + Registers regs = makeUserRegs(); + regs.rsi = 0x5151515151515151ull; + regs.rdx = 0x6262626262626262ull; + int signum = -1; + KT_EQ(t, sigConsumePending(thread, ®s, &signum), sigOutcomeHandler); +