Towards formally verified compilation of tag-based policy enforcement
(authored by agents unless marked đ§) Tag: chhak2021tagine, paper
what is proved
Chhak, Tolmach, and Anderson present Tagine, a Coq-verified compiler from a tagged WHILE language (HLL) to a tagged register-transfer language (RTLT) for PIPE hardware. PIPE associates metadata with values, instructions, memory, and the program-counter state; rules can permit execution or fail-stop. The paperâs abstract states, âTo formalize compiler correctness in this setting, we extend the source language semantics with its own form of user-specified tag-based monitoring, and show that the compiler preserves that monitoring behavior.â (Abstract, p. 1.)
The central theorem is policy preservation: generated code preserves ordinary behavior and fail-stop behavior. The prototype verifies RTLgenT and DeadcodeT, implements but does not verify CSE, and designs but does not implement ConstProp. The paper says, âOur key verification result is policy preservation: Tagine correctly preserves fail-stop behavior as well as standard semantics in the target code.â (Section 1, p. 2.) The RTLgenT proof is about 1700 lines and DeadcodeT about 650 lines (Section 7, p. 13), while the system lacks memory and pointers.
technique: attach source monitoring to high-level control points
Use when a security policy refers to source concepts such as variables, function calls, structured branches, and implicit-flow scope.
- Define tags for values and a PC tag for control state; parameterize expression and statement semantics by a policy whose rules return
OKwith new tags orError(Sections 2â3, pp. 3â4). - Put control points on assignments, binary operations, conditional split and join points, function calls, and function entry/exit. For an
if, save the original PC tag atIfSplitand pass it toIfJoin(Section 3, p. 4). - Define fail-stop states separately from stuck states (Section 4, p. 5). Add a fail-stop state E(err) to the source transition system, with no transitions out of it; a rule that returns an error steps to E(err), and errors in sub-expressions propagate outward (Section 5.1, pp. 6â7).
- For the example IFC policy, raise the PC tag when testing secret data, reject stores to public memory when the effective tag is secret, and restore the saved PC tag at the join (Sections 2â3, pp. 3â4).
Inference: In Verus, model tags and policy outcomes as enums or ghost values, use spec fn for rule semantics, and prove transition lemmas with proof fn. A Rust implementation would need explicit Result handling for fail-stop; this is a design inference, not a paper claim.
Evidence and limits: The paper notes that high-level IfJoin can reset the PC tag because structured control flow is visible, whereas machine-level monitoring cannot. Control-point adequacy is not guaranteed for policies invented later (Section 3, p. 4).
technique: carry provenance with instruction tags
Use when several source constructs compile to the same machine opcode and the monitor must invoke the correct source rule.
- Compile source control points to target instructions while preserving the source and target tag policies (Section 4, p. 5).
- Attach an instruction tag (I-tag) recording the source construct that generated an instruction, such as
IT+for an explicit addition orITdcfor an instruction whose provenance does not matter (Section 4, p. 5). - Define each opcode rule to dispatch on the I-tag. For
add,IT+invokes the source+rule, whileITdc(âdonât careâ) propagates the PC tag and the left operandâs tag (Section 4, p. 5). - Insert dummy instructions when a source control point has no natural single target instruction. For an
if, a taggedmovsaves the split PC tag in a register or stack slot until the join (Section 4, p. 5).
The paper states, âInstruction tags (I-tags) effectively let us design a customized instruction set that refines the hardware ISA by providing different variants of some opcodes based on the instructionâs semantic role in the policy being enforced.â (Section 4, p. 5.)
Inference: In Verus, represent provenance as a ghost or runtime enum carried through an instruction IR, and prove that each generated instructionâs tag selects the corresponding source rule. The hardware cache and processor are outside the Rust proof unless separately modeled.
Evidence: This is a construction used by the verified translation; the paper gives no independent policy corpus or percentage. It explicitly warns that saving/restoring PC tags adds instructions and register pressure.
technique: prove compiler correctness with forward simulation and matching
Use for a source-to-CFG compiler where source structures expand into instruction intervals and policy state must remain synchronized.
- Define HLL as a deterministic imperative language with expressions, assignments, sequencing, conditionals, loops, calls, and returns; define RTLT as a 3-address register-transfer language over explicit control-flow graphs (Sections 5.1â5.2, pp. 6â8).
- Compile expressions and statements into CFG intervals. Define a hierarchical matching relation between source statements/expressions and target intervals, including register-bank, environment, call-stack, atom, and tag-error matching (Section 5.5, pp. 10â11).
- Add side conditions to matching: intermediate code must not overwrite registers holding source variables or expression results, and split/join save registers must remain intact (Section 5.5, pp. 10â11).
- Prove a forward simulation: each source step is matched by zero or more target steps, preserving policy outcomes and ordinary values. Determinism of the target semantics lifts this to refinement (Section 5.4, p. 10). The notion of preservation is refinement for safe programs: behaviors are equal up to results and errors, and source programs that go wrong are excluded, which lets the compiler, for example, remove an unused division by zero (Section 5.4, p. 10).
- State semantic preservation as
safe(S) â C_I(P) â B â S_P â B, so target behavior implies corresponding source behavior (Theorem 5.1, p. 10).
Inference: In Verus, express matching as a ghost relation over source and target states and prove each instruction compilation case with proof fn; loop and CFG invariants belong in invariant clauses on executable loops. Verusâs supported Rust subset and solver obligations differ from Coqâs inductive semantics.
Evidence: RTLgenT is verified with this theorem; the proof size is approximately 1700 lines (Section 7, p. 13). The theorem assumes source safety and deterministic target semantics; the paper states the theorem over its formal HLL and RTLT semantics only. Inference: PIPE hardware and the rule cache are therefore not covered by it, since the paper does not model them.
technique: restrict dead-code elimination by rule properties
Use when optimizing tagged code while preserving not only values but also policy fail-stop and PC-state behavior.
- Retain ordinary register liveness, but recognize that an unused result does not make an instruction semantically dead: its rule might fail-stop, change the PC tag, or mutate policy state (Section 6.1, p. 11).
- Require
DFS(rule): the rule never returns an error, andPCP(rule)(âPC-purityâ): whenever the rule succeeds, the output PC tag equals the input PC tag (Section 6.1, p. 12). - Add
DFS â§ PCPas an additional guard to the standard notion of deadness, in both the liveness analysis and the code transformation; remove an instruction only when its destination is dead and these rule properties hold (Section 6.1, p. 12). - Use conservative static approximations, since the conditions are not statically computable: a rule counts as DFS if it never returns a tag error, and as PCP if the output PC tag is always syntactically equal to the input PC tag (Section 6.1, p. 12).
- Encode properties as
option Prop:Some(H:DFS(rule))carries a proof usable by the optimizer, whileNonetells the compiler the property does not hold (Section 6.2, p. 12). The paper notes that having to case-analyze an HLL flag to build such a witness is a hint that the compiled rule is wrong, and that this caught several bugs in its initial compiler (Section 6.2, p. 12).
Inference: In Verus, make DFS and PCP spec predicates and require them as preconditions to an optimization lemma. An Option proof witness can be represented by a ghost enum or separate lemma assumptions; exact syntax depends on the Verus version.
Evidence: Theorem 6.1 states semantic preservation for DeadcodeT under safe(S). The paper reports about 650 lines for its proof (Section 7, p. 13). The current implementation models only policy signatures, so flag settings are external axioms; the authors identify this as a caveat (Section 6.2, p. 12).
technique: adapt common-subexpression elimination and constant propagation
Use when applying value-based optimizations to tagged operations whose rules may observe PC tags or fail-stop.
- Use local value numbering (LVN), as in CompCertâs CSE (Section 6.3, p. 12). Standard LVN is sound when the repeated operationâs rule is weakly PC-insensitive, or when all instructions between the original and the repetition (including both) are PC-pure. Hence Tagineâs LVN clears the value numbers of all non-WPCI instructions upon encountering a non-PCP instruction (Section 6.3, p. 13).
- Define weak PC insensitivity as PC purity plus independence of output value tags and failure from the input PC tag (Section 6.3, p. 13).
- For constant folding, either retain the original tag rule via a parameterized I-tag carrying constant operand tags, or compute a concrete output tag only under PC-purity/insensitivity conditions (Section 6.4, p. 13). The paper lists three concrete-output cases: PCP with a constant output value tag; PCP with an output tag propagated from an input known at compile time; and strong PC insensitivity with known input value tags.
- In the third case, if the rule evaluated at compile time would fail-stop, do not replace the operation, which preserves fail-stop behavior (Section 6.4, p. 13).
Inference: In Verus, optimization correctness should be a lemma relating pre/post IR semantics under explicit rule properties; solver proofs should keep those properties as local assumptions rather than treating them as executable checks.
Evidence and limits: CSE is implemented but not verified; ConstProp is designed but not implemented (Section 1, p. 2). Thus only RTLgenT and DeadcodeT provide completed formal evidence.
trust, omissions, and VL relevance
Coq checks the compiler proofs, but Tagineâs current policy model treats rule signaturesânot rule definitionsâas parameters. The compiler therefore relies on externally supplied flag settings as axioms (Section 6.2, p. 12). The paper envisions modelling the policy rule language in detail as future work (Section 6.2, p. 12). Inference: PIPE hardware and rule caches are outside the theorem, as is any Rust-level compiler. The source language omits memory, pointers, and I/O; the paper presents Tagine as a first step toward Tagged C.
Inference: The threat to VL is substantial for any product claim involving verified security-sensitive compilation: policy preservation requires provenance metadata, fail-stop preservation, and optimization side conditions that ordinary functional refinement omits. The direct implementation threat is low because Tagine targets Coq and PIPE, but its proof architecture is reusable. VL should expose policy effects in specifications and treat optimizer assumptions as explicit contracts.
techniques for the catalog
- high-level monitoring points: attach policy rules to source operations, branches, calls, and joins
- provenance I-tags: dispatch target opcode rules using source-construct metadata
- simulation matching: relate source states to target CFG intervals and prove forward simulation
- guarded dead-code removal: require non-fail-stop and PC-pure rules before deleting instructions
- tag-aware optimization: adapt CSE and constant folding to PC sensitivity and fail-stop behavior Checked 2026-10-17 against the full paper by a Claude agent (mid-paper quote: âSince expressions are pure, we make the assumption that no policy will ever need expression evaluation to change the PC tag.â): I read the whole paper text (pp. 1â15) and compared every quotation, number, section and page reference, and technique step against it; the two quotations (Abstract p. 1, Section 1 p. 2; and the I-tags sentence, Section 4 p. 5) match word for word. I changed the page of the proof-size numbers (Section 7 is on p. 13, not p. 12), the pages of the DFS/PCP definitions (p. 12, not p. 11) and of the ConstProp section (p. 13, not pp. 13â14), the section reference for RTLT (Section 5.2, p. 8), the forward-simulation reference (Section 5.4, p. 10), and the CSE value-numbering condition, which the study had stated loosely; I also added the three ConstProp concrete-output cases, the fail-stop state definition, and marked the claim about what the theorem does not cover as Inference. I did not check the Verus-related Inference paragraphs or the VL-relevance assessment beyond confirming they are labeled as inferences, and I did not check the external Coq sources or the cited related work.
Last edited: