Support -z force-bti and -z bti-report

-z force-bti turns on the BTI bit of the output's
GNU_PROPERTY_AARCH64_FEATURE_1_AND, and -z bti-report diagnoses input
files lacking it. As in lld, -z force-bti implies -z bti-report=warning.
CetReportKind is renamed ReportKind because -z bti-report shares it.
This commit is contained in:
Rui Ueyama committed 2026-10-09 10:49:20 +09:00
1 parent 5252daa5d8
commit eec9e37a95
6 files changed
+112 -10

No files matched your search

+18
View File
@@ -853,6 +853,24 @@ point symbol in a script.
`GNU_PROPERTY_X86_FEATURE_1_SHSTK` bit in `.note.gnu.property` section to
determine whether or not an object file was compiled with `-fcf-protection`.
* `-z bti-report`=[ `warning` | `error` | `none` ]:
Branch Target Identification (BTI) is an ARM64 feature to protect programs
from control hijacking attacks by requiring indirect branches to land on
`bti` instructions. You can tell the compiler to use the feature by
specifying the `-mbranch-protection=bti` or `-mbranch-protection=standard`
flag. An output file is marked as BTI-enabled only if all object files are.
`-z bti-report` flag is used to make sure that all object files were
compiled with BTI. If `warning` or `error` are given, `mold` prints out a
warning or an error message if an object file lacks
`GNU_PROPERTY_AARCH64_FEATURE_1_BTI` bit in its `.note.gnu.property`
section.
* `-z force-bti`:
Turn on `GNU_PROPERTY_AARCH64_FEATURE_1_BTI` bit in `.note.gnu.property`
section even if some object files were not compiled with BTI, and generate
a PLT with BTI landing pads. This option implies `-z bti-report=warning`.
* `-z now`, `-z lazy`:
By default, functions referring to other ELF modules are resolved by the
dynamic linker when they are called for the first time. `-z now` marks an
+7
View File
@@ -101,6 +101,13 @@ pub fn construct<E: Target>(ctx: &mut Context<E>) {
*map.entry(GNU_PROPERTY_X86_ISA_1_NEEDED).or_insert(0) |= ctx.args.z_x86_64_isa_level;
}
if E::FAMILY == Family::Arm64 {
let features = map.entry(GNU_PROPERTY_AARCH64_FEATURE_1_AND).or_insert(0);
if ctx.args.z_force_bti {
*features |= GNU_PROPERTY_AARCH64_FEATURE_1_BTI;
}
}
ctx.note_property.as_mut().unwrap().contents =
map.into_iter().filter(|&(_, v)| v != 0).collect();
}
+23 -6
View File
@@ -286,7 +286,7 @@ pub enum SeparateCodeKind {
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum CetReportKind {
pub enum ReportKind {
#[default]
None,
Warning,
@@ -429,7 +429,8 @@ pub struct ReaderJob {
pub struct Args {
pub bsymbolic: BsymbolicKind,
pub build_id: BuildId,
pub z_cet_report: CetReportKind,
pub z_cet_report: ReportKind,
pub z_bti_report: ReportKind,
pub undefined_glob: Glob,
pub unique: Glob,
pub z_separate_code: SeparateCodeKind,
@@ -494,6 +495,7 @@ pub struct Args {
pub z_dynamic_undefined_weak: bool,
pub z_execstack: bool,
pub z_execstack_if_needed: bool,
pub z_force_bti: bool,
pub z_global: bool,
pub z_ibt: bool,
pub z_initfirst: bool,
@@ -568,7 +570,8 @@ impl Default for Args {
Self {
bsymbolic: BsymbolicKind::None,
build_id: BuildId::default(),
z_cet_report: CetReportKind::None,
z_cet_report: ReportKind::None,
z_bti_report: ReportKind::None,
undefined_glob: Glob::new(),
unique: Glob::new(),
z_separate_code: SeparateCodeKind::NoSeparateCode,
@@ -633,6 +636,7 @@ impl Default for Args {
z_dynamic_undefined_weak: true,
z_execstack: false,
z_execstack_if_needed: false,
z_force_bti: false,
z_global: false,
z_ibt: false,
z_initfirst: false,
@@ -1221,6 +1225,7 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[Cow<'_, OsStr>]) -> Pars
let mut report_undefined: Option<bool> = None;
let mut z_relro: Option<bool> = None;
let mut z_dynamic_undefined_weak: Option<bool> = None;
let mut z_bti_report: Option<ReportKind> = None;
let mut separate_debug_file: Option<PathBuf> = None;
// An explicit seed survives intervening --reverse-sections options.
let mut shuffle_sections_seed: Option<u64> = None;
@@ -1621,11 +1626,17 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[Cow<'_, OsStr>]) -> Pars
} else if let Some(value) = cursor.read_z_switch("now", "lazy") {
a.z_now = value;
} else if cursor.read_z_flag("cet-report=none") {
a.z_cet_report = CetReportKind::None;
a.z_cet_report = ReportKind::None;
} else if cursor.read_z_flag("cet-report=warning") {
a.z_cet_report = CetReportKind::Warning;
a.z_cet_report = ReportKind::Warning;
} else if cursor.read_z_flag("cet-report=error") {
a.z_cet_report = CetReportKind::Error;
a.z_cet_report = ReportKind::Error;
} else if cursor.read_z_flag("bti-report=none") {
z_bti_report = Some(ReportKind::None);
} else if cursor.read_z_flag("bti-report=warning") {
z_bti_report = Some(ReportKind::Warning);
} else if cursor.read_z_flag("bti-report=error") {
z_bti_report = Some(ReportKind::Error);
} else if cursor.read_z_flag("execstack") {
a.z_execstack = true;
} else if cursor.read_z_flag("execstack-if-needed") {
@@ -1666,6 +1677,8 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[Cow<'_, OsStr>]) -> Pars
} else if cursor.read_z_flag("ibt") {
a.z_ibt = true;
} else if cursor.read_z_flag("ibtplt") {
} else if cursor.read_z_flag("force-bti") {
a.z_force_bti = true;
} else if cursor.read_z_flag("muldefs") {
a.allow_multiple_definition = true;
} else if let Some(value) =
@@ -2037,6 +2050,10 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[Cow<'_, OsStr>]) -> Pars
// `-z dynamic-undefined-weak` is enabled by default for DSOs.
a.z_dynamic_undefined_weak = z_dynamic_undefined_weak.unwrap_or(a.shared);
// `-z force-bti` implies `-z bti-report=warning`.
a.z_bti_report =
z_bti_report.unwrap_or(if a.z_force_bti { ReportKind::Warning } else { ReportKind::None });
// --section-order implies `-z norelro`
a.z_relro = z_relro.unwrap_or(a.section_order.is_empty());
if a.nmagic || a.omagic {
+5 -1
View File
@@ -316,9 +316,13 @@ pub fn link<E: Target>(cmdline: Cmdline) -> LinkResult {
// or ctx.dsos.
// Handle `-z cet-report`.
if ctx.args.z_cet_report != cmdline::CetReportKind::None {
if ctx.args.z_cet_report != cmdline::ReportKind::None {
passes::check_cet_errors(&ctx);
}
// Handle `-z bti-report`.
if E::FAMILY == Family::Arm64 && ctx.args.z_bti_report != cmdline::ReportKind::None {
passes::check_arm64_feature_errors(&ctx);
}
// Handle `-z execstack-if-needed`.
if ctx.args.z_execstack_if_needed && ctx.objs.iter().any(|f| f.needs_executable_stack) {
ctx.args.z_execstack = true;
+25 -3
View File
@@ -31,8 +31,8 @@ use crate::chunks::{
self, ChunkHeader, ChunkId, OutputPhdr, OutputSectionId, compressed, copyrel, dynsym, reloc,
};
use crate::cmdline::{
BsymbolicKind, BuildId, CetReportKind, DebugCompression, DefsymValue, ReportOutput,
SectionOrder, SeparateCodeKind, ShuffleSections, UnresolvedKind,
BsymbolicKind, BuildId, DebugCompression, DefsymValue, ReportKind, ReportOutput, SectionOrder,
SeparateCodeKind, ShuffleSections, UnresolvedKind,
};
use crate::context::Context;
use crate::elf::*;
@@ -1524,7 +1524,7 @@ pub fn apply_section_align<E: Target>(ctx: &mut Context<E>) {
}
pub fn check_cet_errors<E: Target>(ctx: &Context<E>) {
let warning = ctx.args.z_cet_report == CetReportKind::Warning;
let warning = ctx.args.z_cet_report == ReportKind::Warning;
let has_feature = |file: &ObjectFile<E>, feature: u32| {
file.gnu_properties.get(&GNU_PROPERTY_X86_FEATURE_1_AND).is_some_and(|v| v & feature != 0)
};
@@ -1546,6 +1546,28 @@ pub fn check_cet_errors<E: Target>(ctx: &Context<E>) {
}
}
pub fn check_arm64_feature_errors<E: Target>(ctx: &Context<E>) {
for file in &ctx.objs {
if ctx.is_internal(file.id())
|| file
.gnu_properties
.get(&GNU_PROPERTY_AARCH64_FEATURE_1_AND)
.is_some_and(|v| v & GNU_PROPERTY_AARCH64_FEATURE_1_BTI != 0)
{
continue;
}
match ctx.args.z_bti_report {
ReportKind::None => {}
ReportKind::Warning => {
warn!("{file}: -z bti-report=warning: missing GNU_PROPERTY_AARCH64_FEATURE_1_BTI")
}
ReportKind::Error => {
error!("{file}: -z bti-report=error: missing GNU_PROPERTY_AARCH64_FEATURE_1_BTI")
}
}
}
}
pub fn print_dependencies<E: Target>(ctx: &Context<E>) {
ReportOutput::Stdout.with_writer("--print-dependencies", |out| {
writeln!(
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
. $(dirname $0)/common.inc
test_cflags -mbranch-protection=bti || skip
cat <<EOF | $CC -fPIC -mbranch-protection=none -c -o $t/a.o -xc -
void foo() {}
EOF
cat <<EOF | $CC -fPIC -mbranch-protection=bti -c -o $t/b.o -xc -
void foo();
void bar() { foo(); }
EOF
./mold -shared -o $t/c.so $t/a.o $t/b.o
readelf -n $t/c.so | not grep 'AArch64 feature'
$OBJDUMP -d -j .plt $t/c.so | grep -A1 '<_PROCEDURE_LINKAGE_TABLE_>:' | not grep -w bti
./mold -shared -o $t/c.so $t/a.o $t/b.o -z force-bti 2> $t/log
readelf -n $t/c.so | grep 'AArch64 feature: BTI'
readelf --dynamic $t/c.so | grep AARCH64_BTI_PLT
$OBJDUMP -d -j .plt $t/c.so | grep -A1 '<_PROCEDURE_LINKAGE_TABLE_>:' | grep -w bti
grep 'a.o: -z bti-report=warning: missing GNU_PROPERTY_AARCH64_FEATURE_1_BTI' $t/log
not grep b.o: $t/log
./mold -shared -o $t/c.so $t/a.o $t/b.o -z force-bti -z bti-report=none 2> $t/log
not grep a.o: $t/log
./mold -shared -o $t/c.so $t/a.o $t/b.o -z bti-report=warning 2> $t/log
readelf -n $t/c.so | not grep 'AArch64 feature'
grep 'a.o: -z bti-report=warning: missing GNU_PROPERTY_AARCH64_FEATURE_1_BTI' $t/log
not ./mold -shared -o $t/c.so $t/a.o $t/b.o -z bti-report=error 2> $t/log
grep 'a.o: -z bti-report=error: missing GNU_PROPERTY_AARCH64_FEATURE_1_BTI' $t/log