D165 - The command socket takes a session secret, generated per startup and displayed
docs/PROTOCOL.md has always been accurate about what
this is: an unauthenticated socket whose call verb invokes
an arbitrary address with six integer arguments. On a development
machine that is tolerable. On a console it is not, and
the reason is structural: there is no shell on it to tunnel through, the
driver is on another machine, so the module binds every interface and
anything else on that network can connect and drive it.
Per startup, not per build
A secret compiled into the module is shared by everyone who has that module, which is the opposite of a secret. This one is generated when the probe starts listening, lasts that run, and is replaced by a restart.
That forces the entropy problem onto the target rather than the build
machine, and the two backends can honestly offer very different things -
so it is a backend call. net_posix.c reads
/dev/urandom. net_target.c has no such thing
and mixes what a console actually has: the low bits of
differences between successive sceKernelReadTsc
reads, across work whose length varies with the pool so the
samples do not settle into a pattern, plus a stack address for whatever
layout randomisation is applied, plus process time.
The jitter is the only real entropy in that list. The absolute TSC at startup is roughly uptime times frequency and can be estimated; the differences depend on cache state and interrupts and cannot. Mixed with splitmix64's finaliser, chosen because a weak mixer would let the structure in a counter survive into the output.
It is called best-effort in the source and not described as cryptographic anywhere, which is the honest position: tens of bits, not 128.
Displayed, because a console has no other channel
The HUD already draws the port so a driver can be pointed at it - the
"read the address off the screen" this protocol assumes - and the secret
goes beside it as KEY. A new OBS_SYS_SECRET
field, absent when there is none, because "there is no secret"
is something an operator needs to see: it means anything on the network
can drive the probe.
Where the check
goes, and why not beside greeted
At the top of the hello handler, before the reply. That
reply names every capability the build has, and an unauthenticated peer
should not learn it. Returning there never sets greeted, so
the pre-existing rule refuses every other verb with
not-negotiated and one check protects the whole
surface:
CMD|1|hello|1|deadbeef… OBS|refused|1|unauthorised
CMD|2|report OBS|refused|2|not-negotiated
The comparison is constant-time. An ordinary one returns at the first differing byte, which hands a timing adversary the secret a character at a time.
Contract impact, which is small but not nil
The secret is a fourth field appended to
hello, which docs/OUTPUT.md already
permits - new fields go on the end of a line. A driver written before
this works unchanged against a probe that generated no secret, and gets
a clear refusal rather than a parse error against one that did.
obscene-tool drive needed no change at all: commands are
caller-written strings, so
--command 'hello|1|<secret>' was already
expressible.
unauthorised is a new refusal reason,
added to the closed list in PROTOCOL.md. That is the one
part reaching CLIENT.md, which is shared with the sibling
project, so it goes on the bridge rather than changing under them.
What was considered and rejected
SSH inside the probe. It cannot be freestanding -
curve25519, a stream cipher, SHA-2, bignums, a key-exchange state
machine and key storage, with libssh and
wolfSSH both wanting malloc and libc against principle 8.
Worse, a bug in it would be indistinguishable from a platform bug, which
is the exact failure make host exists to prevent (principle
4), reintroduced inside the guest where every line can end the run and
lose the report. And the console has nowhere to keep a host key.
A build-time secret. Rejected on the sharing argument above; it is what the first version of this decision proposed and it was wrong.
Nothing, on the grounds that a token is theatre. That was the previous position and it holds only against an adversary on the path - who reads the secret off a cleartext wire and never guesses. It does not hold against the threat actually being defended, which is another device on the same network. Those are different adversaries and the old text conflated them.