# Target-specific hardening flags for release binaries. # # These apply to local builds too, not just CI, so the binary a developer tests # is the same one that ships. # # Scope note, because it matters for what these flags actually buy: # # /guard:cf Marks the PE CFG-compatible and initialises the guard dispatch # table, with load-time checks. rustc does NOT instrument Rust's # own indirect call sites for CFG, so this is not equivalent to # Clang's -fsanitize=cfi, which instruments every call site. What # it does block is the attack that overwrites the guard dispatch # table itself, plus a class of ROP shapes. Treat it as partial. # # /CETCOMPAT Enables Intel CET shadow-stack + indirect-branch tracking, but # only for code the compiler emits the CET instructions for. Also # partial for the same reason. # # /DYNAMICBASE + /HIGHENTROPYVA ASLR with 64-bit address randomization. # /HIGHENTROPYVA needs /DYNAMICBASE. # /NXCOMPAT DEP: non-executable stack and heap. # # For the strongest results, build the FFI trampolines and the C parts of a # program with a C compiler that instruments them, and link that object in. # `rakc bindgen` exists for the declaration half of this. # # RUSTFLAGS in the environment overrides this file entirely, so CI can still # add flags without editing it. # --------------------------------------------------------------------------- # Windows / MSVC # --------------------------------------------------------------------------- [target.x86_64-pc-windows-msvc] rustflags = [ # Control Flow Guard: CFG-compatible image + guarded dispatch table. "-C", "link-arg=/guard:cf", # Control-flow Enforcement Technology: shadow stack, indirect branch tracking. "-C", "link-arg=/CETCOMPAT", # ASLR, and 64-bit address space randomization. "-C", "link-arg=/DYNAMICBASE", "-C", "link-arg=/HIGHENTROPYVA", # Non-executable stack and heap (DEP). Do NOT also pass /NXCOMPAT:NO here: # that explicitly turns this back off, and the PE ends up without # IMAGE_DLLCHARACTERISTICS_NX_COMPAT. "-C", "link-arg=/NXCOMPAT", ] # --------------------------------------------------------------------------- # Linux / glibc # --------------------------------------------------------------------------- [target.x86_64-unknown-linux-gnu] rustflags = [ # Full RELRO: the GOT becomes read-only after startup, so a GOT overwrite # primitive cannot redirect a later call through it. `-z now` resolves # every symbol eagerly, which is what makes the read-only GOT possible. "-C", "link-arg=-Wl,-z,relro", "-C", "link-arg=-Wl,-z,now", # Non-executable stack. "-C", "link-arg=-Wl,-z,noexecstack", # Keep frame pointers. Costs about 1% and makes `rakc profile` and any # post-mortem walk of a stack possible at all. "-C", "force-frame-pointers=yes", ] # No stack canary on Linux, and that is not a mistake in this file. rustc's # x86_64-unknown-linux-gnu target does not enable -fstack-protector, and # `-C target-feature=+stack-protector` is rejected outright ("not a recognized # feature for this target"). Verified against a real 7.8 MB rakc build: the # binary contains no __stack_chk_fail. Getting a canary into Rust code needs # nightly `-Z stack-protector`. # # So dist/verify_hardening.py reports the canary as an advisory finding rather # than a required one. Demanding a check the stable toolchain cannot satisfy # would mean either a permanently red CI or a check everyone learns to ignore. # The C parts of a program can still get one by building them with GCC's # -fstack-protector and linking that object in. # --------------------------------------------------------------------------- # Linux / musl (static, for the single-file `rakc build` output) # --------------------------------------------------------------------------- [target.x86_64-unknown-linux-musl] rustflags = [ "-C", "link-arg=-Wl,-z,relro", "-C", "link-arg=-Wl,-z,noexecstack", "-C", "force-frame-pointers=yes", ]