D210 - A semaphore handle is an int, and the type says so now

decided ยท 2026-08-24

obSCEne relayed the sceKernelCreateSema signature from public interface documentation:

sceKernelCreateSema(int *out, const char *name, uint32_t attr, int init, int max, const void *opt)

Two facts, one of which was already right here and one of which was a live bug.

The argument order was right, as a guess

args[1] was read as a name and args[3]/args[4] as the counts, which matches. But the code said so in the words of somebody who did not know: "clamped, not trusted - if the argument order is not what is assumed, these are some other value entirely". It is now a citation rather than a hedge, and the knowledge entry moved from guest-observed to published.

The handle width was wrong, and nothing here could have caught it

The out-parameter is an int. This crate typed the handle MutexHandle = u64 and wrote it with write_unaligned::<u64>, so every sceKernelCreateSema put four bytes of handle into whatever the guest kept next to its semaphore.

The write succeeds. The handle round-trips through our own table. The damage lands wherever that neighbour is read, arbitrarily far away. It is the D171 out-parameter class arriving from the other direction - not failing to write, but writing too much - and it is worse in one respect: a missing write leaves a poison pattern somebody can recognise, and an overrun leaves plausible bytes.

Narrowing the cast would have been the wrong fix

Mutex handles are leaked host addresses, which is fine for a void *. Truncated to four bytes, a 48-bit address collides with every other semaphore sharing its low half - silently, which is the same failure again in a new place.

So SemaphoreHandle is its own type (i32) with its own counter allocator starting at one, and obSCEne's "they are not the same shape" is enforced by the compiler rather than remembered. A test that passed a mutex handle to a semaphore call and asserted it found nothing no longer compiles, which is a better answer than the one it was checking for.

It did not move the wall

PPSA02664 faults at image+0xafc959 exactly as before, with the same last calls. The overrun was real and was not this. Recorded because the pull to file a good fix under "and it fixed the thing" is strongest when the fix is genuinely good.

What the error-code finding settled, and what it did not

obSCEne's probe measured 0x8002_0000 | errno and unheld-unlock-returns-EPERM on PS5PCEM, not on hardware - their caveat, carried into the knowledge entry verbatim rather than summarised. Both are recorded as assumptions and are now in the probe worklist.

One thing it settles on its own: it validates a rule made blind. GuestError placeholders avoid the high bit so they can never be mistaken for firmware values, and there was no evidence real codes used it. 0x80020001 says they do.

A gap in the grading vocabulary, raised rather than filled

published / measured / guest-observed / assumed has no slot for measured somewhere that is not the target. measured means hardware and claiming it would be false; assumed means nobody knows and undersells an observation somebody actually made. It went to assumed, which flattens the distinction obSCEne's own caveat was careful to draw.

Not filled unilaterally: the vocabulary is obSCEne's contract and mirrored here, so a value added on one side only is the drift the shared grading exists to prevent. Raised on the bridge. If one arrives it needs the property the others have - naming something that could contradict it, which a hardware run would.