Change:
I noticed that the synthetic object files in will disable the IBT (on x86, enabled by ) and BTI (on AArch64, enabled by ) features because the object files are missing sections indicating that the object file is compatible with those features. Normally, if an object file is missing a section, the linker will disable all such features, on the assumption that the object file is not compatible. This issue is reproducible on the master branch (slightly awkwardly because many distros don't ship IBT-enabled files, and neither is it enabled in Rust's standard library by default): In another directory: After commenting out the line of code that adds to the link: the binary has the correct property note: <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I can think of three possible solutions: the appropriate GNU property section into the synthetic object file according to the passed flags. This would work, but it could easily get out of sync with the GNU properties produced by the compiler. a change in linker behavior so that the GNU properties are not affected by object files that do not contain sections. This would make sense because an object file without sections cannot on its own affect the contents of the final binary, which means that it cannot cause the binary to become {BTI,IBT}-incompatible. a linker feature that implements equivalent behavior to the synthetic object file, e.g. a flag that is equivalent to but does not inhibit , and then use that on ELF platforms instead of the synthetic object file if the linker supports it. may have thoughts on this.
Option 3 is not enough to solve this. The crate metadata object is also produced without these properties and can't be replaced with a linker option. Option 2 will take a long time before everyone has a new linker and needs coordination with , , and As such option 1 makes most sense to me.
Option 2 needs coordination among linker maintainers and it does impose some complexity to linkers. And as mentioned, it requires very new linkers. If Rust is open to 1, I think it is a great direction.
claim

Discussion:
use snap::write::FrameEncoder; <ins> use object::elf::NT_GNU_PROPERTY_TYPE_0; </ins> use rustc_data_structures::memmap::Mmap; use rustc_data_structures::owned_slice::try_slice_owned; use rustc_data_structures::sync::MetadataRef;

.map_err(|e| format!("failed to read {} section in '{}': {}", section, path.display(), e)) } <ins> fn add_gnu_property_note( file: &mut write::Object<'static>, architecture: Architecture, binary_format: BinaryFormat, endianness: Endianness, ) { // check bti protection if binary_format != BinaryFormat::Elf || !matches!(architecture, Architecture::X86_64 | Architecture::Aarch64) { return; } let section = file.add_section( file.segment_name(StandardSegment::Data).to_vec(), b".note.gnu.property".to_vec(), SectionKind::Note, ); let mut data: Vec<u8> = Vec::new(); let n_namsz: u32 = 4; // Size of the n_name field let n_descsz: u32 = 16; // Size of the n_desc field let n_type: u32 = NT_GNU_PROPERTY_TYPE_0; // Type of note descriptor let header_values = [n_namsz, n_descsz, n_type]; header_values.iter().for_each(|v| { data.extend_from_slice(&match endianness { Endianness::Little => v.to_le_bytes(), Endianness::Big => v.to_be_bytes(), }) }); data.extend_from_slice(b"GNU0"); // Owner of the program property note let pr_type: u32 = match architecture { Architecture::X86_64 => 0xc0000002, Architecture::Aarch64 => 0xc0000000, _ => unreachable!(), }; let pr_datasz: u32 = 4; //size of the pr_data field let pr_data: u32 = 3; //program property descriptor let pr_padding: u32 = 0; let property_values = [pr_type, pr_datasz, pr_data, pr_padding]; property_values.iter().for_each(|v| { data.extend_from_slice(&match endianness { Endianness::Little => v.to_le_bytes(), Endianness::Big => v.to_be_bytes(), }) }); file.append_section_data(section, &data, 8); } </ins> pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static>> { let endianness = match sess.target.options.endian { Endian::Little => Endianness::Little,

_ => elf::ELFOSABI_NONE, }; let abi_version = 0; <ins> add_gnu_property_note(&mut file, architecture, binary_format, endianness); </ins> file.flags = FileFlags::Elf { os_abi, abi_version, e_flags }; Some(file) }

<ins> # Check for GNU Property Note include ../tools.mk # How to run this # python3 x.py test --target x86_64-unknown-linux-gnu  tests/run-make/branch-protection-check-IBT/ # only-x86_64 all: ifeq ($(filter x86,$(LLVM_COMPONENTS)),x86_64) $(RUSTC) --target x86_64-unknown-linux-gnu -Z cf-protection=branch -L$(TMPDIR) -C link-args='-nostartfiles'  -C save-temps  ./main.rs -o $(TMPDIR)/rsmain readelf -nW $(TMPDIR)/rsmain | $(CGREP) -e ".note.gnu.property" endif </ins>

<ins> fn main() { println!("hello world"); } </ins>