Fil-C on Nix

Filnix wiki

the formal campaign

rocq/glibc/ZsysWrite.v

A contract at libpizlo's boundary: zsys_write, the pizlonated write(2) that user glibc's write calls (docs/zsys.md, docs/glibc-ir.md). The contract zsys_write_contract is trusted; its native counterexample probe is rocq/glibc/zsys-write.c.

At the pin, libpizlo's filc_native_zsys_write(fd, buf, size) (libpas/src/libpas/filc_runtime.c) is three steps:

  • check_fd(fd): a Fil-C assertion that fd is not libpizlo's own log descriptor, which exists only when libpizlo logs to a file;
  • filc_check_read(buf, size): the capability check, a Fil-C panic when buf cannot be read for size bytes;
  • FILC_SYSCALL(write(fd, ptr, size)): leave the Fil-C world, make the system call, come back, and on failure hand errno to filc_set_errno, which calls user glibc's errno handler, Fil-C code that stores the number in the thread's errno.

The contract's world, fd_world, keeps what no Fil-C code of the module owns: where that errno store lands and which descriptor is libpizlo's log. The errno handler is not proved here; the contract names its effect, a store into the 4-byte errno_span, as the call's only writable root, so every other byte of memory is kept by the machine's effects rule.

The world is trusted to be the real one: errno_ptr is the calling thread's user glibc errno (thread_local int errno, csu/errno.c), already allocated (Fil-C allocates a thread-local on first use, and the contract allows no allocation); glibc's errno_handler is registered (__libc_start_main does it; filc_set_errno panics without one); FILC_DUMP_ERRNOS is unset, so a failure logs nothing; and log_fd is libpizlo's log descriptor. The observation reuses ExternalCalls.v's StreamBytes event, read at this layer as bytes the kernel accepted for the descriptor, not bytes stdio buffered.

Over a Logic instance, fd_wi interprets the world: the errno cell writable and apart from every object the program owns, and the program's descriptors not the log's. wp_zsys_write is the contract's rule for a caller owning the buffer's pieces.

From iris.proofmode Require Import proofmode
From iris.base_logic.lib Require Import gen_heap
From iris.program_logic Require Import language weakestpre
From Stdlib Require Import String ZArith List Lia
From Nanofil Require Import Term Syntax Values Memory Runtime Ops Semantics Run Machine ExternalCalls ExternalMachine Pieces Heap ContractFrame Language Logic
Import ListNotations
Open Scope Z_scope

The world

What a descriptor call needs that no Fil-C code of the module owns: the thread's errno, where user glibc's errno handler stores a failure's number, and libpizlo's log descriptor, if it logs to a file.

55Record fd_world: Type ≔ mkFdWorld
errno_ptr
 : ptr
log_fd
 : optionZ

The 4 bytes of the world's errno.

61Definition errno_spanw : fd_world : span ≔

The k bytes of memory m from p's address.

64Definition bytes_atm : mem p : ptr k : Z : listZ ≔
mapλi ⇒ m_bytesm ptr_addrp + Z.of_nati seq0 Z.to_natk

The contract

zsys_write returns a long (class L), or its result is discarded, and takes an int, a pointer and a size_t.

71Definition zsys_write_abir : call_result args : listarg : Prop ≔
r = CVoid ∨ ∃d, r = CResultd L ∧ ∃fd p n, args = ArgW fd; ArgP p; ArgL n

The precondition: the buffer is readable for its n bytes through a live object's capability (readable_bytes, the check filc_check_read makes), fd is not libpizlo's log descriptor (the check check_fd makes), and errno is a live, writable, aligned int (errno_cell).

79Definition zsys_write_prexs : listword m : mem w : fd_world : Prop ≔
∃fd p n, xs = WIntfd; WPtrp; WIntn ∧ readable_bytesm p n ∧ log_fdw ≠ Somefd ∧ errno_cellm errno_ptrw

A finite prefix: nothing born, the world kept, errno still a writable int, and either nothing observed or the first k bytes of the buffer, k at most n, accepted for fd.

86Definition zsys_write_prefixxs : listword m : mem w : fd_world born : listZ trace : listevent m′ : mem w′ : fd_world : Prop ≔
born = [ ] ∧ w′ = w ∧ errno_cellm′ errno_ptrw ∧ trace = [ ] ∨ ∃fd p n k, xs = WIntfd; WPtrp; WIntn ∧ 0 ≤ k ≤ n ∧ trace = StreamBytesfd bytes_atm p k

The postcondition: as the prefix, and either the kernel accepted the first k bytes, k at most n and below 2^31 (Linux writes at most MAX_RW_COUNT at once), and the call returns k; or it accepted none and the call returns -1, 2^64 - 1 as a long's bits. Only a failure stores errno (FILC_SYSCALL calls the handler only on a nonzero native errno); the contract allows the store either way.

98Definition zsys_write_postxs : listword m : mem w : fd_world born : listZ trace : listevent ending : call_end m′ : mem w′ : fd_world : Prop ≔
born = [ ] ∧ w′ = w ∧ errno_cellm′ errno_ptrw ∧ ∃fd p n, xs = WIntfd; WPtrp; WIntn ∧ ∃k, 0 ≤ k ≤ n ∧ k < 2 ^ 31 ∧ trace = StreamBytesfd bytes_atm p k ∧ ending = CallReturnWIntk ∨ trace = [ ] ∧ ending = CallReturnWInt2 ^ 64 − 1

The capability checks a call outside the precondition reaches: the buffer's null or out-of-bounds capability, and argument or return size mismatch. A log descriptor fails libpizlo's own assertion, which is no capability reason.

110Definition zsys_write_panicwhy : reason : Prop ≔

The trusted contract of zsys_write: a long, zsys_write_abi, zsys_write_pre, no capability sources (it returns an integer), errno's 4 bytes as its one writable root, no releases, no allocation, zsys_write_prefix, zsys_write_post, no resource ending, and zsys_write_panic.

118Definition zsys_write_contract: contractfd_world ≔
mkContractfd_world RClassL zsys_write_abi zsys_write_pre λ_ _ _ ⇒ [ ] λ_ _ w ⇒ errno_spanw λ_ _ _ ⇒ [ ] λ_ _ _ _ _ ⇒ False zsys_write_prefix zsys_write_post λ_ ⇒ False zsys_write_panic

Boundary checks

A call that returns anything but -1 accepted exactly that many of the buffer's first bytes for fd.

128Lemma zsys_write_success_bytes
∀ xs m w born trace m′ w′ k
zsys_write_postxs m w born trace CallReturnWIntk m′ w′
k ≠ 2 ^ 64 − 1
∃fd p n, xs = WIntfd; WPtrp; WIntn ∧ 0 ≤ k ≤ n ∧ trace = StreamBytesfd bytes_atm p k
Proof 6 steps Qed
intros xs m w born trace m′ w′ k _ & _ & _ & fd & p & n & Hx & k′ & Hk & _ & Ht & He | _ & He Hn
–injection He as →
exists fd, p, n
auto
–injection He as →
contradiction

A failed call observed nothing.

139Lemma zsys_write_failure_silent
∀ xs m w born trace m′ w′
zsys_write_postxs m w born trace CallReturnWInt2 ^ 64 − 1 m′ w′
trace = [ ]
Proof 4 steps Qed
intros xs m w born trace m′ w′ _ & _ & _ & fd & p & n & _ & k & Hk & Hb & _ & He | Ht & _
–injection He as ←
lia
–exact Ht

Over a Logic instance

The world interpretation, the rule and its lemmas over the runtime R and the Logic instance LG.

151Module ZsysWriteOn R : Runtime LG0 : LogicSigR
152Module LG ≔ LG0
153Module L ≔ LG.L
Import L LG

The world interpretation of a program that writes to the descriptors fds: errno is a writable int apart from every live object owning a piece of h, and no descriptor of fds is libpizlo's log.

160Definition fd_wifds : listZ m : mem w : fd_world h : gmappiece pval : Prop ≔
errno_cellm errno_ptrw ∧ roots_aparterrno_spanw h m ∧ Forallλfd ⇒ log_fdw ≠ Somefd fds

A safepoint keeps fd_wi: no region changes (collector_live), and writability and apartness read only regions.

166Instance fd_wi_safepointfds : SafepointStablefd_wifds
Proof 20 steps Qed
split
intros s m s′ m′ w h Hsp Hw & Ha & Hf
pose proof R.collector_live_ _ _ _ Hsp as E
split ; | split ; | exact Hf
–destruct Hw as Hz & Hs & base & reg & Hc & Hr & Hl & Hacc & Hal
split ; exact Hz |
split ; | exact Hal
split ; exact Hs |
exists base, reg
unfold access_res in *
rewrite E
auto
–intros sp a Hin Hs l & v & reg & Hl & Hm & E′ Hlive & Hb
apply Hasp a Hin Hs
rewrite E in E'
exists l, v, reg
split ; exact Hl |
split ; exact Hm |
split ; | exact Hb
split ; exact E' | exact Hlive

A zsys_write call that returns returns one long: a count no more than its size, or -1.

180Lemma zsys_write_returns
∀ r args xs s m w trace values s′ m′ w′
M.E.invokezsys_write_contract r args xs s m w trace CallReturnvalues s′ m′ w′
∃fd p n k, xs = WIntfd; WPtrp; WIntn ∧ values = WIntk ∧ 0 ≤ k ≤ n ∧ k < 2 ^ 31 ∨ k = 2 ^ 64 − 1
Proof 5 steps Qed
intros Hi
remember CallReturnvalues as en eqn: Een
destruct Hi as ? ? ? ? ? ? _ _ _ Hp | ? ? ? ? ? ? _ _ _ _ _ ; | discriminate Een
subst
destruct Hp as _ & _ & _ & fd & p & n & Hx & k & Hk & Hb & _ & He | _ & He ; injection He as → ; exists fd, p, n ; eexists ; auto

After a zsys_write call that returns, from a state interpreted with the ghost heap h, the state is interpreted with h again: the prefix keeps the runtime ready, the frame keeps h agreeing and live objects apart, since the one writable root, errno, is apart from h, and the world and errno's writability are kept.

196Lemma zsys_write_return_frame
∀ fds r args xs s m w trace values s′ m′ w′ h
M.E.invokezsys_write_contract r args xs s m w trace CallReturnvalues s′ m′ w′
R.readys m
fd_wifds m w h
agreesm′ h ∧ live_disjointm′ ∧ R.readys′ m′ ∧ fd_wifds m′ w′ h
Proof 10 steps Qed
intros Hi Ha Hd Hr Hw & Hapart & Hfds
destruct M.E.invoke_prefix_ _ _ _ _ _ _ _ _ _ _ _ _ Hi as born & Hp
destruct M.E.prefix_preserve_ _ _ _ _ _ _ _ _ _ _ Hp Hr as Hr′ _
destruct prefix_agrees_ _ _ _ _ _ _ _ _ _ _ h Hp Hr Hd Ha Hapart ltac:intros ? ? ? as Ha′ & Hd′ & _ & Hapart′
destruct Hp as _ _ & → & Hw′ & _
split ; exact Ha' |
split ; exact Hd' |
split ; exact Hr' |
split ; exact Hw' |
split ; exact Hapart' | exact Hfds
211Section rule
212Context `{!invGS_genhlc Σ, !gen_heapGSpiece pval Σ}
213Context fds : listZ
213Context registry : name → optioncontractfd_world
213Context ge : genv

The Iris instance of the registry and fd_wi.

216Instance zsys_irisGS: irisGS_genhlc nanofil_langregistry ge Σ ≔
nanofil_irisGSregistry fd_wifds ge

The expressions of the language.

220Notation zexpr ≔ language.exprnanofil_langregistry ge

zsys_write's triple, from zsys_write_contract: a call whose arguments read as fd, one of fds, the pointer p and the size n, owning pieces ps that show p readable for n bytes, binds d to a count no more than n, or -1, and the pieces are still owned.

226Lemma wp_zsys_write
∀ f e ls lab d a0 a1 a2 rest j fd p n ps Φ
find_fnge TAtom"zsys_write" = None
lookupTAtom"zsys_write" ge_globalsge = None
map_optionarg_xge e ArgW a0; ArgP a1; ArgL a2 = SomeWIntfd; WPtrp; WIntn
Infd fds
∀m, all_agreem ps → readable_bytesm p n
ptsps −∗ ▷∀k, ⌜0 ≤ k ≤ n ∧ k < 2 ^ 31 ∨ k = 2 ^ 64 − 1⌝ −∗ ptsps −∗ WPERunmkFramef putd WIntk mod 2 ^ 64 e ls None Execlab rest j : zexpr {Φ} −∗ WPERunmkFramef e ls None Execlab CallCResultd L GlobalTAtom"zsys_write" ArgW a0; ArgP a1; ArgL a2 :: rest j : zexpr {Φ}
Proof 27 steps Qed
intros Hreg Hfn Hdata Hx Hin Hrd
iIntros Hps HK
iApply wp_external_ _ _ _ _ _ _ _ _ _ _ _ zsys_write_contract WIntfd; WPtrp; WIntn ptsps with Hps [HK] ; done | done | done | done | exact Hx | | |
–split ; right ; eexists ; reflexivity | do 3 eexists ; reflexivity
–iIntros σ Hσ Hps
iDestruct pts_facts with Hσ Hps as %Ha & _
iDestruct Hσ as h [_ %Hf]
destruct Hf as _ & _ & _ & Hw & _ & Hfd
iPureIntro
exists fd, p, n
split ; reflexivity |
split ; exact Hrd_ Ha |
split ; | exact Hw
rewrite Forall_forall in Hfd
exact Hfdfd Hin
–iNext
iIntros σ trace values s′ m′ w′ Hinv (%h & Hh & %Hf) Hps
destruct Hf as Ha & Hd & Hr & Hwi
destruct zsys_write_return_frame_ _ _ _ _ _ _ _ _ _ _ _ h Hinv Ha Hd Hr Hwi as Ha′ & Hd′ & Hr′ & Hwi′
destruct zsys_write_returns_ _ _ _ _ _ _ _ _ _ _ Hinv as fd′ & p′ & n′ & k & Hx′ & → & Hk
injection Hx' as ← ← ←
iModIntro
iExists _
iSplit ; iPureIntro ; reflexivity |
iSplitL Hh ; iExists h ; iFrame Hh ; iPureIntro ; auto |
iApply HK with [%] Hps
exact Hk