test: cover sigTest
This commit is contained in:
1 parent
4e46511f2a
commit
0419e31b12
1 file changed
+159
+159
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user