Syscalls and the Sandbox
The loader and the verifier proved a program’s SBF bytecode is structurally safe before it ever runs: bounded jumps, no forbidden instructions, a return that returns. But structural safety is only half the story. A verified program could still, in principle, ask to read a file, open a socket, or check the wall clock — and the instant it does, our one global state machine falls apart, because those answers differ from one validator’s machine to the next.
This page is about the second wall: the sandbox the program runs inside, and the single narrow door out of it — the syscall whitelist. Together they are what let thousands of independent machines run the same untrusted code and agree, byte for byte, on the result. That agreement is the whole game; everything here exists to protect it.
The box: what a program cannot reach
Section titled “The box: what a program cannot reach”When the runtime hands control to a verified program, it drops that program into an environment stripped of almost everything a normal process takes for granted. Inside the box there is no operating system underneath you — there is only the SBF virtual machine, your instructions, and a small region of memory the runtime prepared.
Concretely, a Solana program has none of these:
- No filesystem. There is no
open, noread, no path. The program’s entire world is the accounts the transaction declared (recall from the account model that state lives in named accounts, passed in explicitly). - No network. No sockets, no DNS, no HTTP. A program cannot phone an oracle, hit an API, or read a price feed itself — anything external must already be on-chain, written by some earlier transaction.
- No threads, no async, no timers. Execution is a single straight line. There is nothing to schedule, nothing to sleep on, no concurrency inside one program (the parallelism lives one level up, in the scheduler across transactions, not inside a program).
- No wall-clock time. There is no
now(). A program cannot ask “what time is it?” of the host, because two hosts would answer differently. - No OS randomness. No
/dev/urandom, nogetrandom. Randomness is nondeterministic by definition, so the door to it simply isn’t there.
┌────────────────────────── the sandbox ──────────────────────────┐ │ │ │ SBF VM ─────► your program's instructions │ │ │ │ memory: [ stack | heap | serialized accounts + instruction ] │ │ │ │ NOT here: filesystem · network · threads · clock · RNG · OS │ │ │ └──────────────────────────────────────────────────────────────┬──┘ │ the ONLY door out ─────────────► syscallsThe mental model that matters: anything nondeterministic is not “restricted,” it is simply absent. You are not being asked to avoid the clock; there is no clock to reach. The box is designed so that the dangerous capabilities were never plumbed in.
Why “absent” beats “forbidden”
Section titled “Why “absent” beats “forbidden””There is a real engineering choice hiding here. The runtime could have exposed the OS and then blocked the risky calls with checks. It doesn’t, because a blocklist is a promise you have to keep perfectly forever — one missed call, one new libc function, and determinism silently breaks on some machines but not others. An allowlist inverts the burden: the default is nothing gets out, and every capability that does is a deliberate, reviewed, deterministic decision. The safe state is the state you get by doing nothing.
The door: a fixed whitelist of syscalls
Section titled “The door: a fixed whitelist of syscalls”A program isn’t useless in the box — it still needs to log, hash, talk to other programs, and read the chain’s shared globals. It does all of that through syscalls: a fixed, runtime-provided set of functions the SBF program is allowed to call across the sandbox wall. Each syscall is registered by the runtime; call anything not on the list and the verifier or the loader rejects it before execution.
The categories worth knowing:
| Category | Example syscalls | What it gives you |
|---|---|---|
| Logging | sol_log, sol_log_64, sol_log_pubkey | Emit messages to the transaction log (the only “output” a program has). |
| Hashing | sol_sha256, sol_keccak256, sol_blake3 | Deterministic cryptographic hashing without shipping your own. |
| Memory | sol_memcpy, sol_memmove, sol_memset, sol_memcmp | Fast, bounds-aware bulk memory ops the VM can meter. |
| Invoke | sol_invoke_signed (CPI) | Call another program — the subject of cross-program invocation. |
| Sysvars | sol_get_clock_sysvar, sol_get_rent_sysvar, … | Read deterministic chain globals (see sysvars). |
| Addresses | sol_create_program_address, sol_try_find_program_address | Derive PDAs — program-owned addresses off the ed25519 curve. |
Notice what is not in that table: nothing that reaches the host OS, the network, or a source of entropy. Every door in the wall opens onto something the runtime can reproduce identically on every validator. The syscall list is, quite literally, the definition of what “the outside” a program is allowed to see — and it was chosen so that “the outside” is the same everywhere.
program (SBF) runtime (host) ───────────── ────────────── call sol_sha256(ptr,len,out) ───► compute SHA-256 (deterministic) ◄─── write 32 bytes back into VM memory call sol_get_clock_sysvar() ───► return the CONSENSUS clock, not the host clock call open("/etc/passwd") ───► ✗ not registered → rejected, never runsThe clock is the perfect illustration
Section titled “The clock is the perfect illustration”A program can ask what time it is — but not by asking the host. It calls sol_get_clock_sysvar, and what comes back is the Clock sysvar: a slot number, an epoch, and a unix_timestamp that the validators agreed on as part of consensus, not the reading of any one machine’s system clock. Same value on every node, every replay. The nondeterministic version (“what does this box think the time is?”) doesn’t exist; the deterministic version (“what time did the network agree on?”) is a syscall. That substitution — ban the divergent thing, offer a consensus-computed replacement — is the pattern behind the whole sandbox. We give it a full page in Sysvars: Clock, Rent, and Deterministic Globals.
Determinism is the requirement everything else serves
Section titled “Determinism is the requirement everything else serves”Step back and ask why the box is shaped this way. Solana is one global state machine replicated across thousands of validators. For the chain to have a single agreed-upon state, every validator that runs the same transaction against the same prior state must compute the identical result — the same account bytes, the same success/failure, down to the last byte. If two honest validators can disagree about the outcome of a transaction, the chain forks and consensus is impossible.
So the design rule is brutal and simple:
Any input that could differ between two machines is either banned outright or funneled through a syscall that returns a consensus value.
Run down the list and it’s the same rule every time:
- Time differs per machine → no host clock; read the consensus
Clocksysvar instead. - Randomness differs by definition → no OS RNG at all; if you need unpredictability, derive it deterministically from on-chain data (a recent blockhash, an account’s contents) and accept that it is deterministic pseudo-randomness, not true entropy.
- Network and filesystem differ per machine → absent entirely; external facts must be written on-chain first by some transaction.
- Floating-point can differ in the last bit across CPUs/compilers → SBF programs avoid relying on divergent float behavior; deterministic integer math is the safe path, and any floating-point that could diverge across implementations is off the table.
Determinism isn’t a nice property the sandbox has. Determinism is the specification, and the sandbox plus the syscall whitelist is the mechanism that enforces it. Remove either and “run untrusted code on thousands of machines and agree” stops being safe.
Under the hood — how the wall is actually built
Section titled “Under the hood — how the wall is actually built”The wall isn’t a metaphor; it’s two concrete mechanisms working together.
First, memory isolation. The SBF VM gives a program a virtual address space it cannot escape. Loads and stores are checked against the regions the runtime mapped in — the program’s stack, its heap, and the serialized accounts and instruction data. A pointer that walks off the end of an account doesn’t read another program’s memory or the host’s; it faults, and the transaction fails. The program literally cannot address anything the runtime didn’t hand it.
Second, the syscall registry. Every syscall the program can name must be registered in the runtime’s function table at load time. The verifier we met on the previous page checks that every external call in the bytecode resolves to a registered syscall; an unresolved or unregistered call is a verification failure, so the program never begins executing. There is no runtime “permission check” to bypass, because an un-whitelisted call cannot even be loaded.
Put together: you can only touch memory the runtime gave you, and you can only leave the box through a door the runtime built. Both are enforced before or during execution by the VM itself, not by trusting the program to behave.
Every door has a price tag
Section titled “Every door has a price tag”One last thread ties this page to the next. Crossing the sandbox wall is not free — each syscall has a compute-unit cost, and every SBF instruction the VM executes burns compute units too. Hashing a large buffer with sol_sha256 costs more than a single log line; a cross-program invocation carries its own charge. The sandbox boundary is therefore also a metering boundary: the runtime knows exactly what a program did, because the program could only act through instructions the VM counts and doors the runtime priced.
That is not a coincidence. Because the outside world is reachable only through a finite, known set of syscalls — and the inside is only a metered VM — the runtime can put a bounded price on everything a program can do, and cut it off when the budget runs out. Determinism buys you meterability. We spend the whole of Compute Units and Limits on how that budget is set and enforced.
sandbox boundary == metering boundary ───────────────────────────────────────── every SBF instruction → costs compute units every syscall crossing → costs compute units budget exhausted → transaction halts, fails deterministicallyThe architect’s lens
Section titled “The architect’s lens”- Why does it exist? Because a replicated state machine is only “one” machine if every replica computes identical results; the sandbox and syscall whitelist exist to make identical results the only possible outcome by removing every source of divergence.
- What problem does it solve? It lets thousands of mutually distrusting validators run the same untrusted, user-uploaded code and still agree on the resulting state — untrusted code that literally cannot reach the OS, the network, the clock, or entropy.
- What are the trade-offs? You give up everything a normal program leans on: no I/O, no true randomness, no host time, no floats-you-can-trust-to-diverge. Any external fact has to be brought on-chain first, which makes oracles and VRFs first-class design concerns rather than a library call.
- When should I avoid it? Never, on Solana — you cannot opt out; it is the execution environment. The real question is what to keep off-chain: heavy computation, private data, and anything needing real I/O belong in off-chain services that hand results to the chain.
- What breaks if I remove it? Everything. Allow one program to read the host clock or open a socket and two honest validators can compute different results for the same transaction — the chain forks, consensus fails, and the “single global state machine” this whole book is chasing ceases to exist.
Check your understanding
Section titled “Check your understanding”- A verified SBF program is already structurally safe. Why is that not enough — what second guarantee does the sandbox add, and why does the chain need it?
- Solana bans host randomness and host wall-clock time, but a program can still learn the time. How, and why is that version acceptable when the host clock is not?
- The runtime uses an allowlist of syscalls rather than a blocklist of forbidden operations. Explain the safety argument for that choice.
- Name three things inside the syscall whitelist and three capabilities deliberately left outside the box. What single property do all the outside-the-box items share?
- In what sense is the sandbox boundary also a metering boundary, and how does that connect to the next page on compute units?
Show answers
- Structural safety (bounded jumps, no illegal instructions) only guarantees the program runs without corrupting the VM. It says nothing about determinism: a structurally valid program could still ask for the clock, the network, or randomness — inputs that differ per machine. The sandbox adds the guarantee that no such nondeterministic input is reachable, so every validator computes the identical result, which is what keeps the replicated state machine from forking.
- The program calls
sol_get_clock_sysvarand reads theClocksysvar, whoseunix_timestamp/slot/epoch are values the validators agreed on in consensus — identical on every node and every replay. That’s acceptable because it’s deterministic; the host system clock is not, because two machines would return different readings and produce diverging results. - An allowlist makes the default “nothing escapes the box,” so any new capability requires a deliberate, reviewed, deterministic addition. A blocklist makes the default “everything escapes unless we remembered to block it” — one missed or newly-added OS call silently breaks determinism on some machines. The allowlist puts the safe outcome as the state you get by doing nothing.
- Inside (any three): logging (
sol_log), hashing (sol_sha256/sol_keccak256), memory ops (sol_memcpy), cross-program invocation (sol_invoke_signed), reading sysvars, PDA derivation. Outside (any three): filesystem, network, threads, host wall-clock time, OS randomness. Every outside item is a source of nondeterminism — its value or behavior could differ between two validators. - The only way a program can act is through metered SBF instructions and a finite set of priced syscalls, so the runtime can attach a compute-unit cost to everything a program does and stop it when the budget runs out. Because the outside is reachable only through known, countable doors, execution is boundable — which is exactly the budget the next page, Compute Units and Limits, formalizes.