Skip to content

[IR] Verify "zeroize-stack" and !sensitive, and pin their round-trips - #5

Draft
claude[bot] wants to merge 1 commit into
zeroize-inliningfrom
zeroize-verifier
Draft

[IR] Verify "zeroize-stack" and !sensitive, and pin their round-trips#5
claude[bot] wants to merge 1 commit into
zeroize-inliningfrom
zeroize-verifier

Conversation

@claude

@claude claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Requested by Francesco Bertolaccini · Slack thread

Fifth of the wave-1 stack, based on the zeroize-inlining branch. Addresses trailofbits/vspells-ct-internal-notes#15.

The three constructs added earlier in this stack were accepted in whatever shape they were written. A malformed one was therefore not a diagnostic but a guarantee quietly reduced: an attribute naming no mode, or a mark on something that is not a stack object, reads as a request that nothing ever honors.

Verifier rules

"zeroize-stack" selects how much of the frame is cleared, so it has to name a mode, and the attribute with no value names none. Both spellings of that, bare "zeroize-stack" and "zeroize-stack"="", are the same attribute once parsed, and both are now rejected.

An unrecognized mode is deliberately still accepted. LangRef, as added in the first PR of this stack, gives it the meaning of "used", the widest mode, so that a mode string a consumer has not learned yet widens what is cleared rather than narrows it. A verifier error there would contradict that and give up the fail-safe for nothing, so the contradiction is resolved the other way: the LangRef sentence is amended to say the value is required and may not be empty, which is exactly what the check enforces. Code and docs now agree, and the fallback survives.

!sensitive marks a stack object and its presence is the whole signal, so it is rejected where there is no stack object to mark (a non-alloca instruction, a function, a global) and rejected when the node carries a payload while its contents are reserved. Ignoring operands today would let IR that relies on their being ignored fix their meaning before anything wants to give them one.

llvm.zeroize needs no rule of its own, and none is added. Intrinsic::isSignatureValid already rejects a wrong return type, a wrong argument count, and a non-pointer or non-integer argument, and UpgradeIntrinsicFunction remangles the name from the signature on load, so a check here would only restate the generic machinery.

Bitcode round-trips

The failure worth catching is a mode string or an attachment that parses and is then lost or rewritten on reload: that downgrades the guarantee without anything failing. The new tests therefore check the reloaded text against what was written, and pin the second trip as a fixed point of the first, rather than only checking that reloading succeeds. llvm/test/Bitcode/zeroize-stack-attribute.ll includes an unrecognized mode, which has to come back verbatim rather than normalized to "used".

Each new test was confirmed load-bearing by breaking the implementation once and checking it fails. Notably, teaching the bitcode writer to normalize an unrecognized mode to "used" leaves the pre-existing llvm/test/Assembler/zeroize-stack.ll passing and fails only the new bitcode test, which is the silent downgrade these tests exist for.

Testing

llvm/test/Verifier (417) and llvm/test/Bitcode (318) both clean, along with the five existing tests from this stack. llvm/test/Assembler, llvm/test/Transforms/Inline, llvm/test/Transforms/DeadStoreElimination and llvm/test/Linker show no regressions; the seven failures there are all missing-tool artifacts of this build directory (llvm-nm, llc_dwarf) and are unrelated.

AI tool use

This pull request contains AI-generated content. It was prepared with the assistance of Claude Code; the contributor has reviewed the generated code and text, is the author of the contribution, and is accountable for it, per the LLVM AI Tool Use Policy.


Generated by Claude Code

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

The three constructs added for stack zeroization were accepted in whatever
shape they were written. A malformed one was therefore not a diagnostic but a
guarantee quietly reduced: an attribute naming no mode, or a mark on something
that is not a stack object, reads as a request that nothing ever honors.

Reject the shapes that are genuinely malformed. "zeroize-stack" selects how
much of the frame is cleared, so it has to name a mode, and the attribute with
no value names none. An unrecognized mode is deliberately still accepted:
LangRef gives it the meaning of "used", the widest mode, so that a mode string
a consumer has not learned yet widens what is cleared rather than narrows it.
A verifier error there would contradict that and give up the fail-safe for
nothing. Amend the LangRef sentence to say the value is required and may not be
empty, which is what the check enforces, so the two now agree.

!sensitive marks a stack object and its presence is the whole signal, so reject
it where there is no stack object to mark, on a non-alloca instruction, on a
function, on a global, and reject a payload while the contents of the node are
reserved. Ignoring operands today would let IR that relies on their being
ignored fix their meaning before anything wants to give them one.

llvm.zeroize needs no rule of its own. Intrinsic::isSignatureValid already
rejects a wrong return type, a wrong argument count, and a non-pointer or
non-integer argument, and the name is remangled from the signature on load, so
a check here would only restate the generic machinery.

Add bitcode round-trip tests for all three. The failure worth catching is a
mode string or an attachment that parses and is then lost or rewritten on
reload, which downgrades the guarantee without failing, so the tests check the
reloaded text against what was written, including an unrecognized mode, which
has to survive verbatim, rather than only that reloading succeeds.
@claude
claude Bot force-pushed the zeroize-inlining branch from 055238d to a36416f Compare August 11, 2026 19:57
@claude
claude Bot force-pushed the zeroize-verifier branch from 2cc8417 to 53f69dc Compare August 11, 2026 19:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants