D382 - ftpsrv wants to be root, and that is a wall worth having
decided - 2026-08-29
With klogsrv listening (D381), the measured list said
ftpsrv was two functions away:
sscanf and strftime. Both are written now, and
ftpsrv imports nothing this project does not implement.
It does not serve. It gets further than that: it runs, prints its own diagnostics in its own words, and stops on something orbistoun has no answer for.
main-prospero.c:49:malloc: error 0 (orbistoun has no message table)
Unable to change AuthID
It is trying to become root. The
KERNEL_ADDRESS_* globals a payload keeps -
ALLPROC, ROOTVNODE, PRISON0,
SECURITY_FLAGS - are kernel addresses a loader supplies on
real hardware, and ftpsrv uses them to raise its own
privileges before it serves a single file. There is no kernel here to
have them, and the honest answer is the one it got.
That is a stated wall rather than a mystery, which
is the whole difference. klogsrv listens because a log
server needs a socket; ftpsrv needs a filesystem it is
allowed to read, and asks for it the way a jailbroken console lets
it.
The two functions
sscanf is printf run backwards and inherits
the same limit: six argument registers, two spent on the string and the
format, so four conversions can be assigned and a fifth cannot. A format
needing more is refused entirely rather than partly
performed - the count is contractually "how many succeeded", so a caller
told four believes those four are good.
strftime needs struct tm, which is nine
ints and then a long, from
include/time.h. A conversion it cannot render stops the
whole call: a half-rendered timestamp is a wrong date rather than a
short one, and a caller printing it cannot tell.
An enumeration of processes has a true answer
Both payloads ask sysctl for kern.proc.proc
- looking for an earlier copy of themselves. klogsrv takes
the refusal and carries on; ftpsrv exits,
which is a correct program handling a call that failed for a reason it
cannot interpret.
Nothing here has a process table, and that is not a gap to paper over
- it is the answer. The call succeeds and reports a zero-length result,
which a caller reads as none, and no process it could be
looking for is running. It also avoids struct kinfo_proc
entirely, which is one of the structures whose layout moved between the
release this project harvests and the one the target forked from
(D374).
And a check that was too clever, withdrawn
D380 made %s follow a pointer only if it was inside a
range the run had published. Those ranges are the
guest's - its image and its stack - and a
%s argument is very often a pointer into memory this
project handed the guest: a strerror buffer, a
getifaddrs block. So the check refused them, and
ftpsrv printed a perfectly good error message with
(unmapped) where the reason should have been.
It is back to the narrow rule, which is what was actually needed: null, the null page and all-ones are not addresses any program computed; everything else is followed and faults as the machine would. The wrong version of that check was visible in a guest's own output for about an hour, which is the cheapest possible way to find out it was wrong, and only because a payload had got far enough to print.