test: cover sigTest

This commit is contained in:
dualfroz committed 2026-09-12 16:36:04 +02:00
1 parent 4e46511f2a
commit 0419e31b12
1 file changed
+159
+159
View File
@@ -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, &regs, &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, &regs));
KT_EQ(t, memcmp(&regs, &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, &regs));
KT_EQ(t, memcmp(&regs, &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, &regs, &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, &regs, &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, &regs, &signum), sigOutcomeHandler);