Skip to content

Make -Zcf-protection work on RISC-V - #165215

Open
shuffle2 wants to merge 1 commit into
rust-lang:mainfrom
shuffle2:riscv-cfi-protection
Open

shuffle2 wants to merge 1 commit into
rust-lang:mainfrom
shuffle2:riscv-cfi-protection

Conversation

@shuffle2

@shuffle2 shuffle2 commented Oct 31, 2026 •

Copy link
Copy Markdown

-Zcf-protection=branch (or full) currently crashes rustc when targeting RISC-V:

rustc --target riscv64gc-unknown-none-elf --crate-type lib \
    -C target-feature=+experimental-zicfilp -Zcf-protection=full lib.rs

LLVM's RISC-V backend expects a cf-branch-label-scheme module flag whenever cf-protection-branch is set, and segfaults in RISCVAsmPrinter::emitNoteGnuProperty without it. Clang always emits the two together.

-Zcf-protection=return doesn't crash, but it also doesn't do anything: the Zicfiss shadow stack is enabled per function through the hw-shadow-stack attribute, and the cf-protection-return module flag only sets the bit in .note.gnu.property.

This PR emits the label scheme flag on RISC-V and adds hw-shadow-stack for return/full, matching what Clang does for -fcf-protection. Other targets are unaffected.

The label scheme is always unlabeled. An earlier version of this had a -Zcf-branch-label-scheme=unlabeled|func-sig option to mirror Clang's -mcf-branch-label-scheme, but I dropped it since func-sig isn't implemented in LLVM yet (the backend errors out with "the complete func-sig label scheme feature is not implemented yet", see llvm/llvm-project#111661), so the only non-default value couldn't compile anything. Clang recently made unlabeled its default as well (llvm/llvm-project#223297). The option can be added later if LLVM grows support.

With LLVM 22 the new module flag is just ignored, so there's no version check.
return/full now require the zimop target feature, since the shadow stack instructions are Zimop encodings. With LLVM 23 and older, branch now also enables Zicfilp for code generation, because those versions only do direct tail calls through the landing-pad-exempt t2 with it (was an llvm bug; fixed in llvm/llvm-project#220657).

Dynamically linked code also needs a landing-pad-aware PLT from lld (llvm/llvm-project#145461). That's not in LLVM 23 and will be a separate backport (has been backported, submodule still needs to be updated).

This also fixes a sentence in the sanitizer docs that said ShadowCallStack uses the Zicfiss ssp register when available. It always uses gp; the hardware shadow stack only comes from hw-shadow-stack.

Tracking issue: #93754

@rustbot

rustbot commented Oct 31, 2026

Copy link
Copy Markdown
Collaborator

Some changes occurred in src/doc/unstable-book/src/compiler-flags/sanitizer.md

cc @rust-lang/project-exploit-mitigations, @rcvalle

Some changes occurred in src/doc/unstable-book/src/compiler-flags/cf-protection.md

cc @rust-lang/project-exploit-mitigations, @rcvalle

@rustbot rustbot added A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. PG-exploit-mitigations Project group: Exploit mitigations S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 31, 2026
@rustbot rustbot added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Oct 31, 2026
@rustbot

rustbot commented Oct 31, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the pull request, and welcome! The Rust Project has assigned @JonathanBrouwer (or someone else) to review your changes, you should hear from them (or someone else) within the next two weeks.

Please see the contribution instructions and our LLM policy for more information.

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 77 candidates
  • Random selection from 21 candidates

@JonathanBrouwer

Copy link
Copy Markdown
Member

@rustbot reroll
Not comfortable enough with this code to review it

@nnethercote

Copy link
Copy Markdown
Contributor

A better reviewer:

r? @rcvalle

@rustbot rustbot assigned rcvalle and unassigned nnethercote Nov 2, 2026
LLVM's RISC-V backend expects a `cf-branch-label-scheme` module flag
whenever `cf-protection-branch` is set, and crashes without one. Emit it
with `unlabeled`, which is the only scheme LLVM implements.

The Zicfiss shadow stack is enabled per function through the
`hw-shadow-stack` attribute rather than by the module flag, so add that
for `-Zcf-protection=return` and `full`. Both match what Clang does for
`-fcf-protection` on RISC-V.

The shadow stack instructions are encoded as Zimop instructions, so they
are illegal on processors without Zimop. Without the feature, LLVM silently
skips the shadow stack, but the `cf-protection-return` module flag still
marks the object as shadow stack compatible. Reject this, like Clang
does.

Before LLVM 24, the RISC-V backend tail calls through `t1` unless
Zicfilp is enabled, and only `t2` is exempt from landing pad checks. A
direct tail call to a function without a landing pad then faults once
landing pads are enforced. With these versions, enable Zicfilp for code
generation whenever `-Zcf-protection` covers branches. Landing pads are
hints, so the code still runs without Zicfilp.
@shuffle2
shuffle2 force-pushed the riscv-cfi-protection branch from 583aaa3 to db44b6e Compare November 5, 2026 21:57

@wesleywiser wesleywiser left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems reasonable to me but I'd like to hear from the RISC-V folks.

@rustbot ping risc-v

View changes since this review

@rustbot rustbot added the O-riscv Target: RISC-V architecture label Nov 12, 2026
@rustbot

rustbot commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

Hey RISC-V Group! This issue has been identified as a good "RISC-V candidate".
In case it's useful, here are some instructions for tackling these sorts of
issues. Maybe take a look?
Thanks! <3

cc @denisvasilik @kraj @sweiglbosker @TechnoPorg

@kraj

kraj commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

LGTM in general, it seems to be matching Clang's behaviour

Before LLVM 24, +experimental-zicfilp is added to the whole module, It likely also ends up in the ELF .riscv.attributes arch string. I wonder if causes problems when linking with GNU ld against objects without that attribute.

Will it miss Compiler-generated functions ? attribute is added in llfn_attrs_from_instance, so functions rustc generates itself e.g. allocator shims etc may not get hw-shadow-stack.

Will LTO work with Clang that predates the unlabeled default (llvm#223297)

@shuffle2

Copy link
Copy Markdown
Author

Before LLVM 24, +experimental-zicfilp is added to the whole module, It likely also ends up in the ELF .riscv.attributes arch string. I wonder if causes problems when linking with GNU ld against objects without that attribute.

Yes, zicfilp1p0 ends up in Tag_RISCV_arch. I tried linking such objects against GCC-built objects without Zicfilp using GNU ld 2.31, 2.42 and 2.44 and lld: all of them link fine and merge the arch string as a union. The attribute is accurate, since the code contains lpad.
A side effect is that with LLVM 23 the feature is in all function's target-features, so in cross-language LTO a C caller built without Zicfilp can't inline Rust functions. A workaround would be adding _zicfilp to the C side's -march (not needed for LLVM >= 24).

Will it miss Compiler-generated functions ? attribute is added in llfn_attrs_from_instance, so functions rustc generates itself e.g. allocator shims etc may not get hw-shadow-stack.

Generally it covers compiler-generated functions (e.g. allocator shims), with 2 exceptions:

  • the C main entry wrapper (rustc_codegen_ssa/src/base.rs!create_entry_fn).
  • __rust_try (when panic=unwind).
    These are actually gaps in clang as well (hw-shadow-stack is not added if FunctionDecl is not present i.e. synthesized function), and need to be considered separately. Frankly I was going to defer these as I don't think they're applicable to my target (embedded), however I could add one or both to this PR or a separate PR if wanted.

Will LTO work with Clang that predates the unlabeled default (llvm#223297)

By default it will not work. One would need to pass -mcf-branch-label-scheme=unlabeled to clang <= 22.
clang 23 does not accept func-sig, so one would need to specify unlabeled in that case anyway. So, not sure if this is worth noting.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. O-riscv Target: RISC-V architecture PG-exploit-mitigations Project group: Exploit mitigations S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants