Fil-C on Nix

Filnix wiki

the formal campaign

rocq/glibc/StrlenLogic.v

A triple for glibc's own __strlen, as nanofil compiles user glibc's string/strlen.c (docs/glibc-ir.md): for any string, not one literal, over any registry and world, so a program linked with glibc's strlen uses it as a rule at its call.

glibc's strlen reads whole aligned 8-byte words: first the word holding the string's first byte, its bytes before the string masked off by a shift, then each next word until one holds a zero byte (StrlenBits.v). Every word it reads is a whole word of the string's object, so the triple's precondition is about words, not bytes:

  • the capability names the object b, read-write or read-only, and b is 8-aligned;
  • the caller owns the life of b and the words ws from the word that holds the first byte, through the word that holds the NUL, every byte of each, including bytes past the NUL (words_ps);
  • those words lie inside b's region.

This is the whole-word rounding glibc's strlen depends on. A string whose NUL is the last byte of an object that ends mid-word does not meet it, and on such an object glibc's strlen would read past the end. Fil-C rounds every object up to a whole word, as filcc gives a 13-byte literal a 16-byte object, and the model's layout rounds data to 8 (docs/glibc-ir.md, Findings). The result is the index of the first zero byte from the string's start (first_zero).

loop_spec is the loop block, for any number of words, by induction on them; wp_glibc_strlen is the entry block, whose NUL is in the first word or the loop finds it.

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 Lia
From Nanofil Require Import Term Syntax Values Memory Runtime Ops Semantics Run Machine Panics StepRules Symbolic ExternalCalls ExternalMachine Transport Pieces Heap Language Logic
From Glibc Require Import LenCode GlibcLogic StrlenBits
Open Scope Z_scope
Open Scope list_scope

glibc's __strlen, as the linked fixture of len.c holds it.

43Definition glibc_strlen: func ≔
Eval vm_compute in match find_directTAtom"__strlen" match decode_modulelen_term with Somemd ⇒ md | None ⇒ [ ] end with SomeDefinedf ⇒ f | _ ⇒ mkFuncTAtom"__strlen" TAtom"export" RClassL [ ] [ ] end

The pieces of the 64-bit words ws from offset o of the object b.

51Fixpoint words_psb o : Z ws : listZ : listpiece × pval ≔
match ws with
[ ]
⇒
[ ]
w :: ws
⇒
cellb o w ++ words_psb o + 8 ws

The bytes of the words ws, little-endian, in order.

55Definition wbytesws : listZ : listZ ≔
flat_mapλw ⇒ mapbyte_ofw idx8 ws

The first zero of the bytes bs is at n.

58Definition first_zerobs : listZ n : nat : Prop ≔
bs !! n = Some0 ∧ ∀u, u < n%nat → bs !! u ≠ Some0

The bytes of the words w :: ws are w's eight, then ws's.

62Lemma wbytes_cons
∀ w ws
wbytesw :: ws = mapbyte_ofw idx8 ++ wbytesws
Proof 1 step Qed
reflexivity

Byte i below 8 of w :: ws is w's byte i.

66Lemma wbytes_head
∀ w ws i : nat
i < 8%nat
wbytesw :: ws !! i = Somebyte_ofw Z.of_nati
Proof 4 steps Qed
intros Hi
rewrite wbytes_cons lookup_app_l ; | rewrite length_map ; cbn ; lia
do 8 destruct i as | i ; reflexivity |
lia

Byte 8 + i of w :: ws is byte i of ws.

73Lemma wbytes_tail
∀ w ws i : nat
wbytesw :: ws !! 8 + i%nat = wbytesws !! i
Proof 5 steps Qed
rewrite wbytes_cons lookup_app_r ; | rewrite length_map ; cbn ; lia
rewrite length_map
f_equal
cbn
lia

Where the first zero of the bytes of w :: ws from k, k below 8, is at n, and k + n is inside w: byte k + n of w is zero and its bytes from k before it are not.

79Lemma first_zero_in
∀ w ws k n
k < 8%nat
first_zerodropk wbytesw :: ws n
k + n < 8%nat
byte_ofw Z.of_natk + n = 0 ∧ ∀i, k ≤ i < k + n%nat → byte_ofw Z.of_nati ≠ 0
Proof 9 steps Qed
intros Hk Hz Hlt Hn
rewrite lookup_drop wbytes_head in Hz ; | lia
split ; congruence |
intros i Hi E
apply Hlti − k%nat ; lia |
rewrite lookup_drop wbytes_head ; | lia
replace k + i − k%nat with i by lia
rewrite E
reflexivity

Where the first zero of the bytes of w :: ws from k is at n, and k + n is past w: w's bytes from k are nonzero, and the first zero of ws's bytes is at k + n - 8.

90Lemma first_zero_past
∀ w ws k n
k < 8%nat
first_zerodropk wbytesw :: ws n
8 ≤ k + n%nat
∀i, k ≤ i < 8%nat → byte_ofw Z.of_nati ≠ 0 ∧ first_zerowbytesws k + n − 8
Proof 19 steps Qed
intros Hk Hz Hlt Hn
split
–intros i Hi E
apply Hlti − k%nat ; lia |
rewrite lookup_drop wbytes_head ; | lia
replace k + i − k%nat with i by lia
rewrite E
reflexivity
–split
+rewrite lookup_drop in Hz
replace k + n%nat with 8 + k + n − 8%nat in Hz by lia
rewrite wbytes_tail in Hz
exact Hz
+intros u Hu E
apply Hlt8 + u − k%nat ; lia |
rewrite lookup_drop
replace k + 8 + u − k%nat with 8 + u%nat by lia
rewrite wbytes_tail
exact E

A temporary's value at class k is what the environment holds for it, taken at k.

105Lemma value_x_tmp
∀ ge e k n x
lookupn e = Somex
Proof 4 steps Qed
intros H
cbn
rewrite H
reflexivity

The triple over the runtime R and the Logic instance LG0.

109Module StrlenOn R : Runtime LG0 : LogicSigR
110Module GL ≔ GlibcLogicOn R LG0
111Module LG ≔ LG0
112Module L ≔ LG.L
Import L LG GL
115Section rules
116Context `{!invGS_genhlc Σ, !gen_heapGSpiece pval Σ}
117Context world : Type registry : name → optioncontractworld wi : mem → world → gmappiece pval → Prop `{!SafepointStablewi} ge : genv

The Iris instance of the registry and wi.

120Instance rules_irisGS: irisGS_genhlc nanofil_langregistry ge Σ ≔
nanofil_irisGSregistry wi ge

The expressions of the language.

122Notation gexpr ≔ language.exprnanofil_langregistry ge

One step whose premises reduce: a pure let, a flight, entering a block, or a goto. The values stay symbolic.

126Ltac st  ≔
first iApply wp_let ; reflexivity | reflexivity | reflexivity | intros ? ; reflexivity | iNext | iApply wp_flight ; reflexivity | reflexivity | reflexivity | iNext | iApply wp_enter ; reflexivity | reflexivity | iNext ; cbn [ b_insts b_jump ] | iApply wp_goto ; iNext

Drop each wrap to 64 or 32 bits that arithmetic shows the identity.

134Ltac unwrap  ≔
repeat match goal with|- context [?x mod 2 ^ 64]⇒rewrite Z.mod_smallx 2 ^ 64 ; | lia|- context [?x mod 2 ^ 32]⇒rewrite Z.mod_smallx 2 ^ 32 ; | liaend

Keep only arithmetic hypotheses, so lia does not read the rest.

141Ltac clear_junk  ≔
repeat match goal withH : ?T |- _⇒lazymatch T with@eqZ _ _⇒failZ.le_ _⇒failZ.lt_ _⇒failandZ.le_ _ _⇒faillt_ _⇒faille_ _⇒fail_⇒clear Hendend

Compute each closed shift factor 2 ^ (n mod m).

151Ltac pow_closed  ≔
repeat match goal with|- context [2 ^ ?n mod ?m]⇒let v ≔ fresh "v" in let Ev ≔ fresh "Ev" in remember 2 ^ n mod m as v eqn: Ev ; vm_compute in Ev ; subst vend

An address or integer fact, by arithmetic.

156Ltac arith  ≔
cbn [ ptr_addr ptr_cap extension ] ; unfold raw_address ; clear_junk ; pow_closed ; unwrap ; first lia | Z.div_mod_to_equations ; lia

A branch whose condition is closed: computed.

160Ltac close_if  ≔
match goal with|- context [edge_labelif ?c then _ else _]⇒first change c with true | change c with falseend;cbn [ edge_label ]

The v4 lookup through the puts of a block, to the frame's e.

166Ltac look H ≔
repeat rewrite lookup_after_put_other ; | reflexivity ; exact H

Name the integer the temporary name holds v, by computation or arithmetic.

169Ltac set_int name v ≔
match goal with|- context [putTAtomname WInt?X _]⇒replace X with v by first reflexivity | arithend

Name the address of the pointer the temporary name holds v.

172Ltac set_ptr name v ≔
match goal with|- context [putTAtomname WPtrmkPtr?X _ _]⇒replace X with v by first reflexivity | arithend

The loop's block, L28: load the next word and test it for a zero.

176Definition blk28: block ≔
Eval vm_compute in match find_blockTAtom"L28" fn_blocksglibc_strlen with Someb ⇒ b | None ⇒ mkBlockTAtom"L28" [ ] [ ] JUnreachable end

The loop, entered with pr.1 the previous word's pointer q and v4 the string's address a, owning the words ws from offset o of the 8-aligned word b + o on, whose first zero byte is t bytes in: strlen returns b + o + t - a, the pieces handed back.

184Lemma loop_spec
∀ b reg c a ws o e from t q Φ
c = Objb ∨ c = Rob
0 ≤ o
8 ≤ b + o
b + o mod 8 = 0
0 ≤ a ≤ b + o
b + o + 8 × Z.of_natList.lengthws ≤ r_endreg
r_endreg ≤ 2 ^ 48
Forallλw ⇒ 0 ≤ w < 2 ^ 64 ws
ptr_addrq = b + o − 8
ptr_capq = c
phis_envge b_phisblk28 from e = SomeputTAtom"pr.1" WPtrq e
lookupTAtom"v4" e = SomeWInta
liveb reg −∗ ptswords_psb o ws −∗ liveb reg −∗ ptswords_psb o ws −∗ ΦRetWIntb + o + Z.of_natt − a −∗ WPERunmkFrameglibc_strlen e [ ] None EnterTAtom"L28" from : gexpr {Φ}
Proof 108 steps Qed
revert o e from t q
induction ws as | w ws IH ; intros o e from t q Hc Ho Hb8 Hal Ha Hend Hreg Hr Hz Hqa Hqc Hphi Hv4
{destruct Hz as Hz _
cbn in Hz
discriminate
cbn [ List.length ] in Hend
rewrite Nat2Z.inj_succ in Hend
iIntros Hl Hps HK
cbn [ words_ps ]
rewrite pts_app
iDestruct Hps as [Hw Hps]
iApply wp_enter ; reflexivity | exact Hphi |
iNext
set_ptr "v32" b + o
iApply wp_check_read with Hl
{reflexivity
{reflexivity
{cbn [ ptr_cap ]
rewrite Hqc
exact Hc
{apply Z.mod_1_r
iNext
iIntros Hl
iApply wp_raw_load_read with Hl Hw ; reflexivity | reflexivity | cbn [ ptr_cap ] ; rewrite Hqc ; exact Hc | arith |
iNext
iIntros Hl Hw
apply Forall_cons_1 in Hr as Hw0 Hr
rewrite Z.mod_smallw 2 ^ 64 Hw0
do 4 st
set_int "iw_4.iw_1.v13" loop_maskw
iApply wp_branch ; reflexivity |
iNext
rewrite Z.mod_smallloop_maskw 2 ^ 64 loop_mask_rangew Zmod_0_l
destruct Nat.lt_ge_casest 8 as Ht | Ht

w holds the NUL, at byte t.

–destruct first_zero_inw ws 0 t ltac:lia Hz ltac:lia as Hzt Hnz
cbn [ Nat.add ] in Hzt
assert Hlow : ∀i, 0 ≤ i < Z.of_natt → byte_ofw i ≠ 0
{intros i Hi
specialize HnzZ.to_nati ltac:lia
rewrite Z2Nat.id in Hnz ; exact Hnz | lia
assert Hb : byte_ofloop_maskw Z.of_natt = 128 by apply loop_mask_at ; lia | exact Hlow | exact Hzt
assert Hm : loop_maskw =? 0 = false
{apply Z.eqb_neq
intros E
rewrite E in Hb
unfold byte_of in Hb
rewrite Z.div_0_l in Hb ; discriminate |
apply Z.pow_nonzero ; lia
rewrite Hm
iApply wp_branch ; reflexivity |
iNext
do 16 st
set_int "iw_5.iw_3.iw_1.iw_1.v22" tailloop_maskw
rewrite tail_firstloop_maskw Z.of_natt loop_mask_rangew loop_mask_01w ltac:lia Hb λi Hi ⇒ loop_mask_beloww Z.of_natt i ltac:lia Hlow Hi
set_int "iw_5.iw_3.iw_1.iw_1.v23" 8 × Z.of_natt + 7
set_int "iw_5.iw_3.iw_1.v27" Z.of_natt
set_int "v41" Z.of_natt
set_ptr "v42" b + o + Z.of_natt
iApply wp_let ; reflexivity | reflexivity | cbn [ map2_option ] ; rewrite value_x_tmp_ _ _ TAtom"v4" WInta ; reflexivity | look Hv4 | intros ? ; reflexivity | iNext
iApply wp_ret ; reflexivity | reflexivity |
iNext
match goal with|- environments.envs_entails_ ΦRetWInt?X⇒replace X with b + o + Z.of_natt − a by arithend
iApply HK with Hl [$Hw $Hps]

w has no zero byte: the next word.

–destruct first_zero_pastw ws 0 t ltac:lia Hz ltac:lia as Hnz Hz′
cbn [ Nat.add ] in Hz'
assert Hm : loop_maskw = 0
{apply zero_word ; apply loop_mask_range |
intros i Hi
apply loop_mask_beloww 8 i ; lia | | lia
intros k Hk
specialize HnzZ.to_natk ltac:lia
rewrite Z2Nat.id in Hnz ; exact Hnz | lia
rewrite Hm
iApply IHo + 8 _ SomeTAtom"L28" t − 8%nat mkPtrb + o ptr_capq with Hl Hps [Hw HK]
+exact Hc
+lia
+lia
+replace b + o + 8 with b + o + 1 × 8 by ring
rewrite Z_mod_plus_full
exact Hal
+lia
+lia
+exact Hreg
+exact Hr
+exact Hz'
+cbn
lia
+exact Hqc
+reflexivity
+look Hv4
+iIntros Hl Hps
replace b + o + 8 + Z.of_natt − 8 − a with b + o + Z.of_natt − a by lia
iApply HK with Hl [$Hw $Hps]

glibc's strlen of p: owning the life of the 8-aligned object b and the words ws of it from the word that holds p's byte, inside its region, whose first zero byte from p is n bytes in, the call returns n and hands the pieces back.

265Lemma wp_glibc_strlen
∀ p b reg ws n Φ
ptr_capp = Objb ∨ ptr_capp = Rob
0 < b
b mod 8 = 0
b ≤ ptr_addrp
ptr_addrp − ptr_addrp mod 8 + 8 × Z.of_natList.lengthws ≤ r_endreg
r_endreg ≤ 2 ^ 48
Forallλw ⇒ 0 ≤ w < 2 ^ 64 ws
first_zerodropZ.to_natptr_addrp mod 8 wbytesws n
liveb reg −∗ ptswords_psb ptr_addrp − ptr_addrp mod 8 − b ws −∗ liveb reg −∗ ptswords_psb ptr_addrp − ptr_addrp mod 8 − b ws −∗ ΦRetWIntZ.of_natn −∗ WPERunmkFrameglibc_strlen TAtom"v0", WPtrp [ ] None EnterTAtom"start" None : gexpr {Φ}
Proof 117 steps Qed
destruct p as a c
intros Hc Hb0 Hb8 Hba Hend Hreg Hr Hz
pose proof Z.mod_pos_bounda 8 ltac:lia as Hk
assert Hal : b ≤ a − a mod 8 by pose proof Z.div_moda 8 ltac:lia ; pose proof Z.div_modb 8 ltac:lia ; nia
destruct ws as | w ws
{destruct Hz as Hz _
rewrite drop_nil in Hz
discriminate
cbn [ List.length ] in Hend
rewrite Nat2Z.inj_succ in Hend
apply Forall_cons_1 in Hr as Hw0 Hr
iIntros Hl Hps HK
cbn [ words_ps ]
rewrite pts_app
iDestruct Hps as [Hw Hps]
do 5 st
match goal with|- context [putTAtom"v10" WPtrmkPtr?X _ _]⇒replace X with a − a mod 8 by cbn [ ptr_addr ] ; rewrite Z.mod_smalla 2 ^ 64 ; rewrite land_align ; arith | lia | liaend
iApply wp_check_read with Hl ; reflexivity | reflexivity | exact Hc | arith | arith | apply Z.mod_1_r |
iNext
iIntros Hl
iApply wp_raw_load_read with Hl Hw ; reflexivity | reflexivity | exact Hc | arith |
iNext
iIntros Hl Hw
rewrite Z.mod_smallw 2 ^ 64 Hw0
do 8 st
set_int "iw_2.v12" first_shiftw a
iApply wp_branch ; reflexivity |
iNext
rewrite Z.mod_smallfirst_shiftw a 2 ^ 64 first_shift_rangew a Zmod_0_l
assert Hfs : ∀i, 0 ≤ i → byte_offirst_shiftw a i = if i + a mod 8 <? 8 then if byte_ofw i + a mod 8 =? 0 then 128 else 0 else 0 by intros i Hi ; apply first_shift_byte ; lia
assert Hk8 : Z.to_nata mod 8 < 8%nat by change 8%nat with Z.to_nat8 ; apply Z2Nat.inj_lt ; lia
destruct Nat.lt_ge_casesZ.to_nata mod 8 + n 8 as Hn | Hn

The NUL is in the first word.

–destruct first_zero_inw ws _ n Hk8 Hz Hn as Hzn Hnz
rewrite Nat2Z.inj_add Z2Nat.id in Hzn ; | lia
assert Hat : byte_offirst_shiftw a Z.of_natn = 128
{rewrite Hfs ; | lia
replace Z.of_natn + a mod 8 <? 8 with true by symmetry ; apply Z.ltb_lt ; lia
rewrite Z.add_commZ.of_natn Hzn
reflexivity
assert Hlow : ∀i, 0 ≤ i < Z.of_natn → byte_offirst_shiftw a i = 0
{intros i Hi
rewrite Hfs ; | lia
replace i + a mod 8 <? 8 with true by symmetry ; apply Z.ltb_lt ; lia
specialize HnzZ.to_nati + a mod 8 ltac:lia
rewrite Z2Nat.id in Hnz ; | lia
apply Z.eqb_neq in Hnz
rewrite Hnz
reflexivity
assert H01 : ∀i, 0 ≤ i < 8 → byte_offirst_shiftw a i = 0 ∨ byte_offirst_shiftw a i = 128
{intros i Hi
rewrite Hfs ; | lia
destruct _ <? 8 ; destruct _ =? 0 | ; auto
assert Hnz0 : first_shiftw a =? 0 = false
{apply Z.eqb_neq
intros E
rewrite E in Hat
unfold byte_of in Hat
rewrite Z.div_0_l in Hat ; discriminate |
apply Z.pow_nonzero ; lia
rewrite Hnz0
iApply wp_branch ; reflexivity |
iNext
rewrite Z.mod_smallfirst_shiftw a 2 ^ 64 first_shift_rangew a Zmod_0_l Hnz0
do 16 st
set_int "iw_3.iw_1.iw_1.v22" tailfirst_shiftw a
rewrite tail_firstfirst_shiftw a Z.of_natn first_shift_rangew a H01 ltac:lia Hat Hlow
set_int "iw_3.iw_1.iw_1.v23" 8 × Z.of_natn + 7
set_int "iw_3.iw_1.v27" Z.of_natn
set_int "v26" Z.of_natn
iApply wp_ret ; reflexivity | reflexivity |
iNext
match goal with|- environments.envs_entails_ ΦRetWInt?X⇒replace X with Z.of_natn by arithend
iApply HK with Hl [$Hw $Hps]

The NUL is in a later word: the loop.

–destruct first_zero_pastw ws _ n Hk8 Hz Hn as Hnz Hz′
assert H0 : first_shiftw a = 0
{apply zero_word ; apply first_shift_range |
intros i Hi
rewrite Hfs ; | lia
destruct i + a mod 8 <? 8 eqn: E ; | reflexivity
apply Z.ltb_lt in E
specialize HnzZ.to_nati + a mod 8 ltac:lia
rewrite Z2Nat.id in Hnz ; | lia
apply Z.eqb_neq in Hnz
rewrite Hnz
reflexivity
rewrite H0
iApply loop_specb reg c a ws a − a mod 8 − b + 8 _ SomeTAtom"start" Z.to_nata mod 8 + n − 8%nat mkPtra − a mod 8 c with Hl Hps [Hw HK]
+exact Hc
+lia
+lia
+replace b + a − a mod 8 − b + 8 with 8 × a / 8 + 1 by pose proof Z.div_moda 8 ; lia
rewrite Z.mul_comm Z_mod_mult
reflexivity
+lia
+lia
+exact Hreg
+exact Hr
+exact Hz'
+cbn
lia
+reflexivity
+reflexivity
+reflexivity
+iIntros Hl Hps
replace b + a − a mod 8 − b + 8 + Z.of_natZ.to_nata mod 8 + n − 8 − a with Z.of_natn by lia
iApply HK with Hl [$Hw $Hps]