Letting a Model Call Your Code Safely
Run It as Nobody, Owning Nothing
Last timeChoosing Where the Boundary Goes
The identity a submission runs under is the whole list of what it can reach, so hand it one that owns nothing and ask the kernel for only the sixty entry points the job uses.
A service that runs a submitted program against ten inputs and hands back what
it printed has already decided the interesting part before the program starts.
The decision is not which code to accept. It is which identity the code gets to
borrow.
What is actually at risk
The instinct is to look at the submission. Read it, scan it, run a pattern over
it, decide whether it looks hostile. That instinct is answering the wrong
question, and the reason is mechanical rather than philosophical. A program is
not text that does things. It is a sequence of requests to the kernel, and the
kernel does not grant a request because the code meant well. It grants a
request because the identity making it is permitted to.
So the question to settle first is not what this program does. It is what the
identity it will run under can reach. That reach is transitive and almost
nobody draws the whole of it. If the identity may read one file, it may read
every credential that file holds. If a credential opens a database, the reach
includes the database. If the database holds other readers' submissions, the
reach includes those as well, and the submission never had to do anything
inventive to arrive there.
Two of the ten things a hostile submission tries are exactly this and nothing
more. Reading a file that holds credentials, and reading another reader's
submission, are ordinary file reads made by an identity that was allowed to
make them. There is no exploit in either. There is a permission.
Run as nobody
Nobody is a specific thing, and it is three conditions rather than one. An
identity whose numeric id is not zero. An identity that owns no file anywhere
on the host. An identity that belongs to no group which grants access to
anything. The first condition is the one everybody applies. The second and
third are what make the first worth applying.
Running as the privileged identity inside the boundary is the single most
common mistake here, and the usual defence is that the boundary holds anyway.
Sometimes it does. The trouble is that the privileged identity is what every
published escape is written against, because an escape has to begin with a call
that only a privileged identity may make. Dropping the identity costs nothing
at start-up and removes the first step of most of them.
Give each submission a fresh identity where you can. Two readers sharing one
identity share everything either of them may read, which turns the tenth
attempt on the list, leaving a process running after the answer was returned,
into a way of watching the next reader's work.
The lesson stops here
9 more paragraphs to go
You have read the opening. The rest of the argument, the problems that check whether it landed, and the lines worth keeping at the end all come with a plan.
The first lesson of every course in the library reads the whole way through, free, so you can see exactly what the rest of them are.
See the planThe contentsThis is the reading half
Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.
The contents