diff --git a/.github/arm64e/check_objects.py b/.github/arm64e/check_objects.py new file mode 100644 index 0000000000000..ca54058322e88 --- /dev/null +++ b/.github/arm64e/check_objects.py @@ -0,0 +1,117 @@ +"""Checks every object in the given rlibs or archives for code that pointer authentication misses. + +Fails on: +- a function that saves its return address (x30) without signing it with `pacibsp`; +- an indirect call with a plain `blr`; +- an indirect branch with a plain `br`, unless it is a jump-table dispatch; +- a data relocation that stores a function's address unsigned. + +Usage: check_objects.py LLVM_BIN_DIR ARCHIVE... +""" + +import pathlib +import re +import subprocess +import sys +import tempfile + +# Sections whose relocations to code are not function pointers that code calls through. +UNSIGNED_OK_SECTIONS = { + "__text", + "__compact_unwind", + "__eh_frame", + "__gcc_except_tab", + "__mod_init_func", # left unsigned on purpose, like clang's own constructors + "__mod_term_func", + "__init_offsets", + "__thread_vars", # TLV descriptors name `_tlv_bootstrap`; dyld binds them +} + +FUNCTION_HEADER = re.compile(r"^[0-9a-f]+ <(.+)>:$") +INSTRUCTION = re.compile(r"^\s+[0-9a-f]+:\s+(\S+)\s*(.*)$") +SECTION_HEADER = re.compile(r"^RELOCATION RECORDS FOR \[(.+)\]:$") +RELOCATION = re.compile(r"^[0-9a-f]+\s+(\S+)\s+(\S+)$") +SYMBOL = re.compile(r"^[0-9a-f]+\s+[lg ]\s+F\s+__TEXT,__text\s+(\S+)$") + + +def run(tool, *args): + command = [str(tool), *map(str, args)] + return subprocess.run(command, check=True, capture_output=True, text=True).stdout + + +def function_symbols(objdump, obj): + lines = run(objdump, "-t", obj).splitlines() + return {m.group(1) for line in lines if (m := SYMBOL.match(line))} + + +def check_code(objdump, obj, problems): + functions = [] + for line in run(objdump, "-d", "--no-show-raw-insn", obj).splitlines(): + if m := FUNCTION_HEADER.match(line): + functions.append((m.group(1), [])) + elif (m := INSTRUCTION.match(line)) and functions: + functions[-1][1].append((m.group(1), m.group(2))) + for name, insns in functions: + mnemonics = [m for m, _ in insns] + # The registers stored, not the address: `stp x29, x30, [sp, #-0x10]!`. + stored = [ops.split("[")[0] for m, ops in insns if m in ("stp", "str", "stur")] + saves_lr = any("x30" in registers for registers in stored) + signs_lr = "pacibsp" in mnemonics + if saves_lr and not signs_lr: + problems.append(f"{obj.name}: {name} saves x30 without pacibsp") + for i, (m, ops) in enumerate(insns): + if m == "blr": + problems.append(f"{obj.name}: {name} calls through a plain blr {ops}") + elif m == "br" and "adr" not in mnemonics[max(0, i - 6) : i]: + problems.append(f"{obj.name}: {name} branches through a plain br {ops}") + return len(functions) + + +def check_data(objdump, obj, functions, problems): + authenticated = 0 + section = None + for line in run(objdump, "-r", obj).splitlines(): + if m := SECTION_HEADER.match(line): + section = m.group(1) + elif (m := RELOCATION.match(line)) and section is not None: + kind, target = m.groups() + if kind == "ARM64_RELOC_AUTHENTICATED_POINTER": + authenticated += 1 + elif ( + kind == "ARM64_RELOC_UNSIGNED" + and section not in UNSIGNED_OK_SECTIONS + and not section.startswith(("__debug", "__apple")) # DWARF names code addresses + ): + if target in functions or target == "__text": + problems.append(f"{obj.name}: [{section}] stores {target} unsigned") + return authenticated + + +def main(): + llvm_bin = pathlib.Path(sys.argv[1]) + archives = [pathlib.Path(a) for a in sys.argv[2:]] + objdump, ar = llvm_bin / "llvm-objdump", llvm_bin / "llvm-ar" + if not archives: + sys.exit("no archives given") + problems = [] + with tempfile.TemporaryDirectory() as tmp: + objects = [] + for archive in archives: + out = pathlib.Path(tmp) / archive.stem + out.mkdir() + run(ar, "x", f"--output={out}", archive) + objects += sorted(out.glob("*.o")) + functions = set().union(*(function_symbols(objdump, o) for o in objects)) + n_functions = sum(check_code(objdump, o, problems) for o in objects) + n_auth = sum(check_data(objdump, o, functions, problems) for o in objects) + print(f"{len(archives)} archives, {len(objects)} objects, {n_functions} functions, " + f"{n_auth} authenticated pointers in data, {len(problems)} problems") + for problem in problems[:200]: + print(problem) + # The data check needs the function symbols to recognise a relocation to a function. + if not objects or not functions or not n_functions or not n_auth or problems: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/fork-arm64e.yml b/.github/workflows/fork-arm64e.yml new file mode 100644 index 0000000000000..e7e68fcff82fc --- /dev/null +++ b/.github/workflows/fork-arm64e.yml @@ -0,0 +1,658 @@ +name: Fork arm64e validation + +# Validates the fork's arm64e pointer-authentication patch: its own tests, upstream's pauthtest +# tests, tidy, and an arm64e std build. Fork only; not part of upstream. + +on: + workflow_dispatch: + pull_request: + push: + branches: + - rust-stable-with-arm64e + schedule: + - cron: "43 11 * * 2" + +permissions: + contents: read + +concurrency: + group: fork-arm64e-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + arm64e: + if: github.repository == 'cypherair/rust' + # The standard macOS arm64 images (`macos-15`, `macos-26`) are short of capacity: their jobs + # can wait and then be dropped without a runner. `xcode-27`, the macOS 27 image in public + # preview, gets runners. Its label may change when it becomes generally available. + strategy: + fail-fast: false + matrix: + runner: [xcode-27] + name: arm64e validation (${{ matrix.runner }}) + runs-on: ${{ matrix.runner }} + timeout-minutes: 240 + env: + HOST_TRIPLE: aarch64-apple-darwin + defaults: + run: + shell: bash + steps: + - name: Checkout source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 2 + submodules: recursive + + # In CI, bootstrap takes HEAD^1 as the upstream commit and downloads its prebuilt LLVM, as on + # a pull request to rust-lang/rust. Give every run that shape: a merge commit whose first + # parent is the newest upstream (bors) commit in the history, and whose tree is the tip's. + - name: Put the upstream base at HEAD^1 + run: | + set -euo pipefail + email="$(sed -n 's/^git_merge_commit_email=//p' src/stage0)" + tip="$(git rev-parse HEAD)" + base="" + for depth in 2 64 512; do + if [ "$depth" -gt 2 ]; then + git fetch --no-tags --depth="$depth" origin "$tip" + fi + base="$(git log --first-parent --author="$email" -n1 --format=%H "$tip")" + if [ -n "$base" ]; then + break + fi + done + if [ -z "$base" ]; then + echo "no upstream commit within 512 commits of $tip" >&2 + exit 1 + fi + if [ "$(git rev-parse -q --verify HEAD^1 || true)" != "$base" ]; then + merge="$(git -c user.name='fork CI' -c user.email=fork-ci@invalid \ + commit-tree "$tip^{tree}" -p "$base" -p "$tip" -m "Validate $tip on $base")" + git checkout -q --detach "$merge" + fi + git log --oneline -n2 --first-parent HEAD + + - name: Show environment + run: | + mkdir -p /tmp/arm64e-diagnostics + { + echo "github.event_name=${GITHUB_EVENT_NAME}" + echo "runner=${{ matrix.runner }}" + sw_vers + uname -a + sysctl -n kern.osproductversion kern.osversion + sysctl -n machdep.cpu.brand_string 2>/dev/null || true + git show -s --format='HEAD: %H %P %ae %s' HEAD + python3 --version + clang --version + rustc -vV || true + } | tee /tmp/arm64e-diagnostics/runner-fingerprint.txt + + - name: Check arm64e codegen crates + id: check_codegen + run: | + python3 x.py check compiler/rustc_codegen_ssa compiler/rustc_codegen_llvm --stage 1 + + - name: Run arm64e and pauthtest tests + id: targeted_tests + run: | + python3 x.py test --stage 1 --force-rerun --no-fail-fast \ + tests/codegen-llvm/arm64e-apple-ptrauth*.rs \ + tests/assembly-llvm/arm64e-apple-ptrauth-calls.rs \ + tests/assembly-llvm/targets/targets-macho.rs \ + tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs \ + tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs \ + tests/run-make/arm64e-ptrauth-allocator-shim \ + tests/run-make/arm64e-ptrauth-serialized-output \ + tests/codegen-llvm/pauth \ + tests/assembly-llvm/pauth-basic.rs \ + tests/assembly-llvm/targets/targets-elf.rs \ + tests/ui/pointer_authentication \ + tests/ui/statics/crt-static-pauthtest.rs \ + tests/ui/target_modifiers/incompatible_pauth.rs + + # The steps below do not depend on each other's results: a failing test or tidy check must + # not hide the std build or the runtime results. Each failure still fails the job. + - name: Run tidy + id: tidy + if: ${{ !cancelled() }} + run: | + # On the stable channel, as in upstream's CI for stable releases, tidy skips its rustfmt + # check: a stable release pins no stage 0 rustfmt, and bootstrap passes nightly-only + # flags to it. This step builds no compiler, so the channel changes nothing else. + python3 x.py test --stage 1 tidy --set rust.channel=stable + + - name: Build stage1 arm64e std toolchain + id: build_stage1 + if: ${{ !cancelled() && steps.check_codegen.outcome == 'success' }} + # Verify the LLVM IR of the arm64e std. This reaches only what the stage1 compiler builds; + # `rust.verify-llvm-ir` would be compiled into rustc and rebuild it. + env: + RUSTFLAGS_NOT_BOOTSTRAP: -Zverify-llvm-ir + run: | + python3 x.py build library/std --stage 1 --target arm64e-apple-darwin + + - name: Check the arm64e std objects + id: std_objects + if: ${{ !cancelled() && steps.build_stage1.outcome == 'success' }} + run: | + python3 -I .github/arm64e/check_objects.py "build/${HOST_TRIPLE}/ci-llvm/bin" \ + build/${HOST_TRIPLE}/stage1/lib/rustlib/arm64e-apple-darwin/lib/*.rlib + + - name: Prepare arm64e smoke helpers + if: ${{ !cancelled() && steps.build_stage1.outcome == 'success' }} + run: | + mkdir -p /tmp/arm64e-diagnostics + cat > /tmp/arm64e_collect_smoke.py <<'PY' + import argparse + import json + import pathlib + import re + import subprocess + import sys + + + def run_capture(cmd, timeout=None): + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + return { + "cmd": cmd, + "returncode": proc.returncode, + "signal": -proc.returncode if proc.returncode < 0 else None, + "stdout": proc.stdout, + "stderr": proc.stderr, + } + + + def load_text(path): + try: + return pathlib.Path(path).read_text() + except FileNotFoundError: + return None + + + parser = argparse.ArgumentParser() + parser.add_argument("--name", required=True) + parser.add_argument("--rustc", required=True) + parser.add_argument("--source", required=True) + parser.add_argument("--output-base", required=True) + parser.add_argument("--json-out", required=True) + parser.add_argument("--target", default="arm64e-apple-darwin") + parser.add_argument("--emit", default="link") + parser.add_argument("--run-binary", action="store_true") + parser.add_argument("--expected-stdout") + parser.add_argument("--expected-stdout-regex") + parser.add_argument("--asm-pattern") + parser.add_argument("--opt-level", default="2") + args = parser.parse_args() + + output_base = pathlib.Path(args.output_base) + compile_cmd = [ + args.rustc, + args.source, + "--target", + args.target, + f"-Copt-level={args.opt_level}", + "--emit", + args.emit, + "-o", + str(output_base), + ] + payload = { + "name": args.name, + "target": args.target, + "emit": args.emit, + "compile": run_capture(compile_cmd), + "output_base": str(output_base), + "paths": { + "source": args.source, + "binary": str(output_base), + "asm": str(output_base.with_suffix(".s")), + "ir": str(output_base.with_suffix(".ll")), + }, + } + + overall_ok = payload["compile"]["returncode"] == 0 + + if overall_ok: + payload["file"] = run_capture(["/usr/bin/file", str(output_base)]) + else: + payload["file"] = None + + asm_path = output_base.with_suffix(".s") + asm_text = load_text(asm_path) + if asm_text is not None and args.asm_pattern: + payload["asm_matches"] = re.findall(args.asm_pattern, asm_text) + else: + payload["asm_matches"] = [] + + if args.run_binary and overall_ok: + payload["run"] = run_capture([str(output_base)], timeout=30) + if payload["run"]["returncode"] != 0: + overall_ok = False + stdout = payload["run"]["stdout"] + if args.expected_stdout is not None: + if stdout.rstrip("\n") != args.expected_stdout.rstrip("\n"): + payload["stdout_mismatch"] = { + "expected": args.expected_stdout, + "actual": stdout, + } + overall_ok = False + if args.expected_stdout_regex is not None: + if re.fullmatch(args.expected_stdout_regex, stdout.strip()) is None: + payload["stdout_regex_mismatch"] = { + "expected_regex": args.expected_stdout_regex, + "actual": stdout, + } + overall_ok = False + else: + payload["run"] = None + + json_path = pathlib.Path(args.json_out) + json_path.write_text(json.dumps(payload, indent=2, sort_keys=True)) + + print(f"== {args.name} compile ==") + print(f"returncode={payload['compile']['returncode']}") + if payload["compile"]["stdout"]: + print("-- stdout --") + print(payload["compile"]["stdout"], end="") + if payload["compile"]["stderr"]: + print("-- stderr --") + print(payload["compile"]["stderr"], end="") + if payload["file"] is not None: + print("== file ==") + print(payload["file"]["stdout"], end="") + if payload["run"] is not None: + print("== run ==") + print(f"returncode={payload['run']['returncode']}") + if payload["run"]["signal"] is not None: + print(f"signal={payload['run']['signal']}") + print("-- stdout --") + print(payload["run"]["stdout"], end="") + print("-- stderr --") + print(payload["run"]["stderr"], end="") + if payload["asm_matches"]: + print("== asm matches ==") + for match in payload["asm_matches"]: + print(match) + + sys.exit(0 if overall_ok else 1) + PY + + - name: Diagnose arm64e hello runtime + id: hello_diag + if: ${{ !cancelled() && steps.build_stage1.outcome == 'success' }} + run: | + STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" + + cat > /tmp/arm64e-diagnostics/arm64e_hello.rs <<'EOF' + fn main() { + println!("hello-stage1"); + } + EOF + + python3 /tmp/arm64e_collect_smoke.py \ + --name hello \ + --rustc "$STAGE1" \ + --source /tmp/arm64e-diagnostics/arm64e_hello.rs \ + --output-base /tmp/arm64e-diagnostics/arm64e_hello \ + --json-out /tmp/arm64e-diagnostics/arm64e_hello.json \ + --emit link \ + --run-binary \ + --expected-stdout 'hello-stage1' + + - name: Diagnose arm64e thread id runtime + id: thread_id_diag + if: ${{ !cancelled() && steps.build_stage1.outcome == 'success' }} + run: | + STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" + + cat > /tmp/arm64e-diagnostics/arm64e_thread_id.rs <<'EOF' + fn main() { + println!("{:?}", std::thread::current().id()); + } + EOF + + python3 /tmp/arm64e_collect_smoke.py \ + --name thread_id \ + --rustc "$STAGE1" \ + --source /tmp/arm64e-diagnostics/arm64e_thread_id.rs \ + --output-base /tmp/arm64e-diagnostics/arm64e_thread_id \ + --json-out /tmp/arm64e-diagnostics/arm64e_thread_id.json \ + --emit link \ + --run-binary \ + --expected-stdout-regex '^ThreadId\([0-9]+\)$' + + - name: Diagnose arm64e function pointer runtime + id: fnptr_diag + if: ${{ !cancelled() && steps.build_stage1.outcome == 'success' }} + run: | + STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" + + cat > /tmp/arm64e-diagnostics/arm64e_fnptr.rs <<'EOF' + #[inline(never)] + pub extern "C" fn target(x: i32) -> i32 { x + 1 } + + fn main() { + let f = std::hint::black_box(target as extern "C" fn(i32) -> i32); + println!("{}", f(41)); + } + EOF + + python3 /tmp/arm64e_collect_smoke.py \ + --name fnptr \ + --rustc "$STAGE1" \ + --source /tmp/arm64e-diagnostics/arm64e_fnptr.rs \ + --output-base /tmp/arm64e-diagnostics/arm64e_fnptr \ + --json-out /tmp/arm64e-diagnostics/arm64e_fnptr.json \ + --emit llvm-ir,asm,link \ + --run-binary \ + --expected-stdout '42' \ + --asm-pattern 'braaz|blraaz|blr' + + echo "LLVM IR excerpt:" + sed -n '1,40p' /tmp/arm64e-diagnostics/arm64e_fnptr.ll || true + echo + echo "Assembly excerpt:" + sed -n '1,40p' /tmp/arm64e-diagnostics/arm64e_fnptr.s || true + + - name: Diagnose arm64e TLS runtime + id: tls_diag + if: ${{ !cancelled() && steps.build_stage1.outcome == 'success' }} + run: | + STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" + + cat > /tmp/arm64e-diagnostics/arm64e_tls_smoke.rs <<'EOF' + use std::cell::RefCell; + + fn main() { + thread_local! { + static S: RefCell = RefCell::default(); + } + + S.with(|x| *x.borrow_mut() = "pika pika".to_string()); + S.with(|x| println!("{}", x.borrow())); + } + EOF + + python3 /tmp/arm64e_collect_smoke.py \ + --name tls \ + --rustc "$STAGE1" \ + --source /tmp/arm64e-diagnostics/arm64e_tls_smoke.rs \ + --output-base /tmp/arm64e-diagnostics/arm64e_tls_smoke \ + --json-out /tmp/arm64e-diagnostics/arm64e_tls_smoke.json \ + --emit llvm-ir,asm,link \ + --run-binary \ + --expected-stdout 'pika pika' \ + --asm-pattern 'braaz|blraaz|blr' + + echo "LLVM IR excerpt:" + sed -n '1,40p' /tmp/arm64e-diagnostics/arm64e_tls_smoke.ll || true + echo + echo "Assembly excerpt:" + sed -n '1,60p' /tmp/arm64e-diagnostics/arm64e_tls_smoke.s || true + + - name: Diagnose arm64e pointer-authentication scope runtime + id: scope_diag + if: ${{ !cancelled() && steps.build_stage1.outcome == 'success' }} + run: | + STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" + + cat > /tmp/arm64e-diagnostics/arm64e_scope.rs <<'EOF' + use std::hint::black_box; + use std::sync::atomic::{AtomicBool, Ordering}; + + static CONSTRUCTOR_RAN: AtomicBool = AtomicBool::new(false); + + extern "C" fn constructor() { + CONSTRUCTOR_RAN.store(true, Ordering::SeqCst); + } + + #[used] + #[unsafe(link_section = "__DATA,__mod_init_func,mod_init_funcs")] + static CONSTRUCTOR: extern "C" fn() = constructor; + + trait Shape { + fn area(&self) -> u32; + } + + struct Square(u32); + + impl Shape for Square { + fn area(&self) -> u32 { + self.0 * self.0 + } + } + + fn twice(f: fn(u32) -> u32, x: u32) -> u32 { + f(f(x)) + } + + fn main() { + let shape = black_box(Box::new(Square(3)) as Box); + let add_one = black_box((|x| x + 1) as fn(u32) -> u32); + let signed = (add_one as usize) >> 47 != 0; + let area = std::thread::spawn(move || shape.area()).join().unwrap(); + println!( + "constructor={} area={} twice={} signed={}", + CONSTRUCTOR_RAN.load(Ordering::SeqCst), + area, + twice(add_one, 40), + signed, + ); + } + EOF + + for opt in 2 0; do + python3 /tmp/arm64e_collect_smoke.py \ + --name "scope_O${opt}" \ + --rustc "$STAGE1" \ + --opt-level "$opt" \ + --source /tmp/arm64e-diagnostics/arm64e_scope.rs \ + --output-base "/tmp/arm64e-diagnostics/arm64e_scope_O${opt}" \ + --json-out "/tmp/arm64e-diagnostics/arm64e_scope_O${opt}.json" \ + --emit llvm-ir,asm,link \ + --run-binary \ + --expected-stdout 'constructor=true area=9 twice=42 signed=true' \ + --asm-pattern 'braaz|blraaz' + done + + - name: Diagnose arm64e unwind runtime + id: unwind_diag + if: ${{ !cancelled() && steps.build_stage1.outcome == 'success' }} + run: | + STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" + + cat > /tmp/arm64e-diagnostics/arm64e_unwind.rs <<'EOF' + use std::hint::black_box; + use std::panic::{AssertUnwindSafe, catch_unwind}; + use std::sync::atomic::{AtomicU32, Ordering}; + + static DROPS: AtomicU32 = AtomicU32::new(0); + + struct CountsDrop; + + impl Drop for CountsDrop { + fn drop(&mut self) { + DROPS.fetch_add(1, Ordering::SeqCst); + } + } + + trait Task { + fn run(&self); + } + + struct Panics; + + impl Task for Panics { + fn run(&self) { + // Unwinding runs this frame's cleanup landing pad first. + let _guard = CountsDrop; + panic!("through dyn"); + } + } + + fn main() { + let main = catch_unwind(|| panic!("on the main thread")).is_err(); + let thread = std::thread::spawn(|| catch_unwind(|| panic!("on a thread")).is_err()) + .join() + .unwrap(); + let task = black_box(Box::new(Panics) as Box); + let dyn_call = catch_unwind(AssertUnwindSafe(|| task.run())).is_err(); + let join = std::thread::spawn(|| panic!("out of a thread")).join().is_err(); + let drops = DROPS.load(Ordering::SeqCst); + println!("main={main} thread={thread} dyn={dyn_call} join={join} drops={drops}"); + } + EOF + + for opt in 2 0; do + python3 /tmp/arm64e_collect_smoke.py \ + --name "unwind_O${opt}" \ + --rustc "$STAGE1" \ + --opt-level "$opt" \ + --source /tmp/arm64e-diagnostics/arm64e_unwind.rs \ + --output-base "/tmp/arm64e-diagnostics/arm64e_unwind_O${opt}" \ + --json-out "/tmp/arm64e-diagnostics/arm64e_unwind_O${opt}.json" \ + --emit link \ + --run-binary \ + --expected-stdout 'main=true thread=true dyn=true join=true drops=1' + done + + # The cases above pass on a machine that runs arm64e with pointer authentication off. This + # one does not: a call through the raw, unsigned address of a function must kill the process. + - name: Check that the runner enforces pointer authentication + id: enforce_diag + if: ${{ !cancelled() && steps.build_stage1.outcome == 'success' }} + run: | + STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" + + cat > /tmp/arm64e-diagnostics/arm64e_enforce.rs <<'EOF' + use std::hint::black_box; + use std::os::unix::process::ExitStatusExt; + use std::process::Command; + + #[inline(never)] + fn target() -> u32 { + 7 + } + + /// Removes the signature from a code pointer. + fn strip(signed: usize) -> usize { + let mut addr = signed; + unsafe { std::arch::asm!("xpaci {0}", inout(reg) addr, options(nomem, nostack)) }; + addr + } + + fn main() { + let f = black_box(target as fn() -> u32); + let raw = strip(f as usize); + if std::env::args().nth(1).as_deref() == Some("raw") { + // A valid address with no signature, called through an authenticated call. + let unsigned: fn() -> u32 = unsafe { std::mem::transmute(raw) }; + println!("returned {}", black_box(unsigned)()); + return; + } + let status = Command::new(std::env::current_exe().unwrap()).arg("raw").status(); + let raw_call = match status.unwrap() { + s if s.signal().is_some() => format!("signal {}", s.signal().unwrap()), + s => format!("exit {s}"), + }; + println!("signed={} valid_call={} raw_call={raw_call}", f as usize != raw, f()); + } + EOF + + python3 /tmp/arm64e_collect_smoke.py \ + --name enforce \ + --rustc "$STAGE1" \ + --source /tmp/arm64e-diagnostics/arm64e_enforce.rs \ + --output-base /tmp/arm64e-diagnostics/arm64e_enforce \ + --json-out /tmp/arm64e-diagnostics/arm64e_enforce.json \ + --emit link \ + --run-binary \ + --expected-stdout-regex '^signed=true valid_call=7 raw_call=signal [0-9]+$' + + - name: Upload arm64e diagnostics + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: arm64e-diagnostics-${{ matrix.runner }} + if-no-files-found: ignore + path: /tmp/arm64e-diagnostics + + - name: Summarize arm64e validation + if: always() + run: | + python3 - <<'PY' >> "$GITHUB_STEP_SUMMARY" + import json + import pathlib + + root = pathlib.Path("/tmp/arm64e-diagnostics") + cases = [ + ("hello", "arm64e_hello.json"), + ("thread_id", "arm64e_thread_id.json"), + ("fnptr", "arm64e_fnptr.json"), + ("tls", "arm64e_tls_smoke.json"), + ("scope -O", "arm64e_scope_O2.json"), + ("scope -O0", "arm64e_scope_O0.json"), + ("unwind -O", "arm64e_unwind_O2.json"), + ("unwind -O0", "arm64e_unwind_O0.json"), + ("enforce", "arm64e_enforce.json"), + ] + + def sanitize(text): + if text is None: + return "" + text = text.strip() + if not text: + return "" + return text.replace("|", "\\|").replace("\n", "
") + + print("## arm64e validation (${{ matrix.runner }})") + print() + print("- check_codegen: ${{ steps.check_codegen.outcome }}") + print("- targeted_tests: ${{ steps.targeted_tests.outcome }}") + print("- tidy: ${{ steps.tidy.outcome }}") + print("- build_stage1: ${{ steps.build_stage1.outcome }}") + print("- std_objects: ${{ steps.std_objects.outcome }}") + print("- hello_diag: ${{ steps.hello_diag.outcome }}") + print("- thread_id_diag: ${{ steps.thread_id_diag.outcome }}") + print("- fnptr_diag: ${{ steps.fnptr_diag.outcome }}") + print("- tls_diag: ${{ steps.tls_diag.outcome }}") + print("- scope_diag: ${{ steps.scope_diag.outcome }}") + print("- unwind_diag: ${{ steps.unwind_diag.outcome }}") + print("- enforce_diag: ${{ steps.enforce_diag.outcome }}") + print() + print("| case | compile | run | rc/signal | file | stdout | asm hits |") + print("| --- | --- | --- | --- | --- | --- | --- |") + for case_name, json_name in cases: + path = root / json_name + if not path.exists(): + print(f"| {case_name} | missing | missing | | | | |") + continue + data = json.loads(path.read_text()) + compile_status = "ok" if data["compile"]["returncode"] == 0 else "fail" + run = data.get("run") + if run is None: + run_status = "not-run" + rc_signal = "" + stdout = "" + else: + run_status = "ok" if run["returncode"] == 0 else "fail" + rc_signal = f"rc={run['returncode']}" + if run.get("signal") is not None: + rc_signal += f" sig={run['signal']}" + stdout = sanitize(run.get("stdout")) + file_output = sanitize((data.get("file") or {}).get("stdout")) + asm_hits = sanitize(",".join(data.get("asm_matches") or [])) + print( + f"| {case_name} | {compile_status} | {run_status} | {sanitize(rc_signal)} | " + f"{file_output} | {stdout} | {asm_hits} |" + ) + print() + print("Every step is blocking. A runner that cannot run arm64e fails the runtime cases.") + PY + + { + echo "Smoke diagnostic JSON files:" + ls -1 /tmp/arm64e-diagnostics/*.json + } diff --git a/compiler/rustc_codegen_llvm/src/base.rs b/compiler/rustc_codegen_llvm/src/base.rs index 6e7c31f7743d7..9b497e69b0123 100644 --- a/compiler/rustc_codegen_llvm/src/base.rs +++ b/compiler/rustc_codegen_llvm/src/base.rs @@ -25,7 +25,7 @@ use rustc_middle::mono::Visibility; use rustc_middle::ty::TyCtxt; use rustc_session::config::{DebugInfo, Offload}; use rustc_span::Symbol; -use rustc_target::spec::SanitizerSet; +use rustc_target::spec::{BinaryFormat, SanitizerSet}; use crate::builder::Builder; use crate::builder::gpu_offload::OffloadGlobals; @@ -163,7 +163,10 @@ pub(crate) fn compile_codegen_unit( cx.add_objc_module_flags(); } - if cx.sess().pointer_authentication() { + // These flags describe the ELF PAuth ABI; LLVM reads them only for ELF objects. + if cx.sess().pointer_authentication() + && cx.sess().target.binary_format == BinaryFormat::Elf + { let cfg = cx.sess().pointer_auth_config.as_ref().unwrap(); let aarch64_elf_pauthabi_version = diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index abc71f450a515..b72ce45b4f6d9 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -23,6 +23,7 @@ use rustc_middle::ty::layout::{ }; use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; use rustc_sanitizers::{cfi, kcfi}; +use rustc_session::PointerAuthScope; use rustc_session::config::OptLevel; use rustc_span::Span; use rustc_target::callconv::{FnAbi, PassMode}; @@ -2184,23 +2185,36 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { if self.sess().pointer_authentication_functions().is_none() { return None; } - // Pointer authentication support is currently limited to extern "C" calls; filter out other - // ABIs. - if fn_abi?.conv != CanonAbi::C { - return None; - } - // Filter out LLVM intrinsics. - if llvm::get_value_name(llfn).starts_with(b"llvm.") { - return None; + match self.sess().pointer_authentication_scope()? { + // Every function pointer is signed and no direct callee is, so exactly the indirect + // calls authenticate their callee, whatever their ABI. + PointerAuthScope::AllFunctions => { + if !llvm::is_indirect_callee(llfn) { + return None; + } + } + PointerAuthScope::ExternC => { + // Pointer authentication support is currently limited to extern "C" calls; filter + // out other ABIs. + if fn_abi?.conv != CanonAbi::C { + return None; + } + // Filter out LLVM intrinsics. + if llvm::get_value_name(llfn).starts_with(b"llvm.") { + return None; + } + + // FIXME(jchlanda) Operand bundles should only be attached to indirect function + // calls. However, function pointer signing is currently performed in + // `get_fn_addr`, which causes the logic to be applied too broadly, including to + // function values (not just pointers). As a result, direct calls using signed + // function values must also receive operand bundles. + // Once this is resolved, we should analyze each call and skip direct calls. See + // the discussion in the rust-lang issue: + // + } } - // FIXME(jchlanda) Operand bundles should only be attached to indirect function calls. - // However, function pointer signing is currently performed in `get_fn_addr`, which causes - // the logic to be applied too broadly, including to function values (not just pointers). - // As a result, direct calls using signed function values must also receive operand - // bundles. - // Once this is resolved, we should analyze each call and skip direct calls. See the - // discussion in the rust-lang issue: let key: u32 = 0; let discriminator: u64 = 0; Some(llvm::OperandBundleBox::new( diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs index 7a05bf718663c..a23ec74a35ef0 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -15,7 +15,7 @@ use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; use rustc_middle::mir::interpret::{GlobalAlloc, PointerArithmetic, Scalar}; use rustc_middle::ty::{Instance, TyCtxt}; -use rustc_session::{PointerAuthAddressDiscriminator, PointerAuthSchema}; +use rustc_session::{PointerAuthAddressDiscriminator, PointerAuthSchema, PointerAuthScope}; use rustc_span::bug; use tracing::debug; @@ -36,23 +36,32 @@ pub(crate) fn maybe_sign_fn_ptr<'ll, 'tcx>( return llfn; } - // Only free functions or methods - let def_id = instance.def_id(); - if !matches!(cx.tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) { - return llfn; - } - // Only C ABI - let abi = cx.tcx.fn_sig(def_id).skip_binder().abi(); - if !matches!(abi, ExternAbi::C { .. } | ExternAbi::System { .. }) { + let Some(scope) = cx.tcx.sess.pointer_authentication_scope() else { return llfn; + }; + match scope { + // Callers pass a schema only where the address becomes a value: sign every function. + PointerAuthScope::AllFunctions => {} + PointerAuthScope::ExternC => { + // Only free functions or methods + let def_id = instance.def_id(); + if !matches!(cx.tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) { + return llfn; + } + // Only C ABI + let abi = cx.tcx.fn_sig(def_id).skip_binder().abi(); + if !matches!(abi, ExternAbi::C { .. } | ExternAbi::System { .. }) { + return llfn; + } + if Some(def_id) == cx.tcx.lang_items().eh_personality() { + return llfn; + } + } } // Ignore LLVM intrinsics if llvm::get_value_name(llfn).starts_with(b"llvm.") { return llfn; } - if Some(def_id) == cx.tcx.lang_items().eh_personality() { - return llfn; - } let addr_diversity = match schema.is_address_discriminated { PointerAuthAddressDiscriminator::HardwareAddress(true) => Some(llfn), @@ -400,6 +409,20 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { }) } + // This replaces the trait default in `rustc_codegen_ssa/src/traits/consts.rs` for every + // target. Outside `AllFunctions` it must do what the default does. + fn scalar_to_backend(&self, cv: Scalar, layout: abi::Scalar, llty: &'ll Type) -> &'ll Value { + // A function address in a constant operand is a value: sign it if every function pointer + // is signed. + let schema = match self.tcx.sess.pointer_authentication_scope() { + Some(PointerAuthScope::AllFunctions) => { + self.tcx.sess.pointer_authentication_functions() + } + Some(PointerAuthScope::ExternC) | None => None, + }; + self.scalar_to_backend_with_pac(cv, layout, llty, schema) + } + fn scalar_to_backend_with_pac( &self, cv: Scalar, diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs index bbb1141a92e89..3c59678b4ab74 100644 --- a/compiler/rustc_codegen_llvm/src/consts.rs +++ b/compiler/rustc_codegen_llvm/src/consts.rs @@ -15,8 +15,9 @@ use rustc_middle::mir::interpret::{ use rustc_middle::mono::MonoItem; use rustc_middle::ty::layout::{HasTypingEnv, LayoutOf}; use rustc_middle::ty::{self, Instance}; +use rustc_session::PointerAuthScope; use rustc_span::{Symbol, bug, span_bug}; -use rustc_target::spec::Arch; +use rustc_target::spec::{Arch, BinaryFormat}; use tracing::{debug, instrument, trace}; use crate::common::CodegenCx; @@ -167,7 +168,11 @@ fn codegen_static_initializer<'ll, 'tcx>( .link_section .map(|link_section| { let s = link_section.as_str(); - if s.starts_with(".init_array") || s.starts_with(".fini_array") { + if s.starts_with(".init_array") + || s.starts_with(".fini_array") + || (cx.sess().target.binary_format == BinaryFormat::MachO + && is_macho_init_term_section(s)) + { IsInitOrFini::Yes } else { IsInitOrFini::No @@ -177,6 +182,16 @@ fn codegen_static_initializer<'ll, 'tcx>( Ok((const_alloc_to_llvm(cx, alloc.inner(), IsStatic::Yes, is_in_init_fini), alloc)) } +/// Whether a Mach-O section specifier, `segment,section[,type[,attributes]]`, names a section of +/// initializer or terminator pointers. +fn is_macho_init_term_section(specifier: &str) -> bool { + let mut parts = specifier.split(',').map(str::trim).skip(1); + let name = parts.next(); + let section_type = parts.next(); + matches!(name, Some("__mod_init_func" | "__mod_term_func")) + || matches!(section_type, Some("mod_init_funcs" | "mod_term_funcs")) +} + fn set_global_alignment<'ll>(cx: &CodegenCx<'ll, '_>, gv: &'ll Value, mut align: Align) { // The target may require greater alignment for globals than the type does. // Note: GCC and Clang also allow `__attribute__((aligned))` on variables, @@ -213,7 +228,9 @@ fn check_and_apply_linkage<'ll, 'tcx>( let fn_abi = cx.fn_abi_of_fn_ptr(fn_sig, ty::List::empty()); // Decide if the initializer needs to be signed if cx.sess().pointer_authentication() - && matches!(fn_sig.abi(), ExternAbi::C { .. } | ExternAbi::System { .. }) + && (cx.sess().pointer_authentication_scope() + == Some(PointerAuthScope::AllFunctions) + || matches!(fn_sig.abi(), ExternAbi::C { .. } | ExternAbi::System { .. })) { should_sign = true; } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 0ce3bfe07c413..92518f81df9b0 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -1046,6 +1046,7 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { }; let tcx = self.tcx; + // Unwind tables name the personality; it is never a signed value. let llfn = match tcx.lang_items().eh_personality() { Some(def_id) if name.is_none() => self.get_fn_addr( ty::Instance::expect_resolve( @@ -1055,7 +1056,7 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { ty::List::empty(), DUMMY_SP, ), - tcx.sess.pointer_authentication_functions(), + None, ), _ => { let name = name.unwrap_or("rust_eh_personality"); diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 655a79e341bd1..e2d6afe1a1234 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -1069,6 +1069,7 @@ unsafe extern "C" { pub(crate) fn LLVMGetAggregateElement(ConstantVal: &Value, Idx: c_uint) -> Option<&Value>; pub(crate) fn LLVMGetConstOpcode(ConstantVal: &Value) -> Opcode; pub(crate) fn LLVMIsAConstantExpr(Val: &Value) -> Option<&Value>; + pub(crate) safe fn LLVMIsAInlineAsm(Val: &Value) -> Option<&Value>; // Operations on global variables, functions, and aliases (globals) pub(crate) fn LLVMIsDeclaration(Global: &Value) -> Bool; diff --git a/compiler/rustc_codegen_llvm/src/llvm/mod.rs b/compiler/rustc_codegen_llvm/src/llvm/mod.rs index 89e4d60656d34..3a8fcbe1b2046 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/mod.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/mod.rs @@ -510,6 +510,14 @@ pub(crate) fn add_alias<'ll>( unsafe { LLVMAddAlias2(module, ty, address_space.0, aliasee, name.as_ptr()) } } +/// Whether a call to `callee` goes through a pointer: the callee is neither a function symbol +/// nor inline assembly. +pub(crate) fn is_indirect_callee(callee: &Value) -> bool { + let callee = unsafe { LLVMRustStripPointerCasts(callee) }; + let not_a_function_symbol = unsafe { LLVMRustIsNonGVFunctionPointerTy(callee) }; + not_a_function_symbol && LLVMIsAInlineAsm(callee).is_none() +} + /// Safe wrapper for `LLVMRustConstPtrAuth`. pub(crate) fn const_ptr_auth<'ll>( ptr: &'ll Value, diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 8743d703940ec..79461ff990c21 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -578,7 +578,7 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( DUMMY_SP, ); let start_fn = - cx.get_fn_addr(start_instance, cx.sess().pointer_authentication_functions()); + cx.get_fn_addr(start_instance, cx.sess().pointer_authentication_direct_callees()); let i8_ty = cx.type_i8(); let arg_sigpipe = bx.const_u8(sigpipe); diff --git a/compiler/rustc_codegen_ssa/src/common.rs b/compiler/rustc_codegen_ssa/src/common.rs index 4b54235cf71bc..3c961c77f4368 100644 --- a/compiler/rustc_codegen_ssa/src/common.rs +++ b/compiler/rustc_codegen_ssa/src/common.rs @@ -119,7 +119,7 @@ pub(crate) fn build_langcall<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( let instance = ty::Instance::mono(tcx, def_id); ( bx.fn_abi_of_instance(instance, ty::List::empty()), - bx.get_fn_addr(instance, tcx.sess.pointer_authentication_functions()), + bx.get_fn_addr(instance, tcx.sess.pointer_authentication_direct_callees()), instance, ) } diff --git a/compiler/rustc_codegen_ssa/src/diagnostics.rs b/compiler/rustc_codegen_ssa/src/diagnostics.rs index 09faa71468bf7..341d80c17e1c0 100644 --- a/compiler/rustc_codegen_ssa/src/diagnostics.rs +++ b/compiler/rustc_codegen_ssa/src/diagnostics.rs @@ -1198,6 +1198,14 @@ pub(crate) struct Aarch64SoftfloatNeon; )] pub(crate) struct X86SoftfloatSse; +#[derive(Diagnostic)] +#[diag( + "`-Ctarget-feature` cannot disable `{$feature}` on arm64e Apple targets, whose ABI requires pointer authentication" +)] +pub(crate) struct Arm64eApplePtrauthDisableForbidden<'a> { + pub feature: &'a str, +} + #[derive(Diagnostic)] #[diag("ignoring feature with missing prefix in `-Ctarget-feature`: `{$feature}`")] #[note("features must begin with a `+` to enable or `-` to disable it")] diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index f99009a0f4243..d09195adea2e8 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -714,7 +714,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } _ => ( false, - bx.get_fn_addr(drop_fn, bx.sess().pointer_authentication_functions()), + bx.get_fn_addr(drop_fn, bx.sess().pointer_authentication_direct_callees()), bx.fn_abi_of_instance(drop_fn, ty::List::empty()), drop_fn, ), @@ -1138,7 +1138,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { None, Some(bx.get_fn_addr( instance, - bx.sess().pointer_authentication_functions(), + bx.sess().pointer_authentication_direct_callees(), )), ) } @@ -1461,7 +1461,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let fn_ptr = match (instance, llfn) { (Some(instance), None) => { - bx.get_fn_addr(instance, bx.sess().pointer_authentication_functions()) + bx.get_fn_addr(instance, bx.sess().pointer_authentication_direct_callees()) } (_, Some(llfn)) => llfn, _ => span_bug!(fn_span, "no instance or llfn for call"), diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 3b33cf5a17602..274692280c315 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -768,7 +768,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { args: ty::GenericArgs::empty(), }; let fn_ptr = - bx.get_fn_addr(instance, bx.sess().pointer_authentication_functions()); + bx.get_fn_addr(instance, bx.sess().pointer_authentication_direct_callees()); let fn_abi = bx.fn_abi_of_instance(instance, ty::List::empty()); let fn_ty = bx.fn_decl_backend_type(fn_abi); let fn_attrs = if bx.tcx().def_kind(instance.def_id()).has_codegen_attrs() { diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index b630155c144ea..5c7f73817aa7e 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -268,6 +268,12 @@ fn parse_rust_feature_list<'a>( } } +/// Whether disabling `base_feature` would turn pointer authentication off on an arm64e Apple +/// target. `pauth` is LLVM's name for the pair that Rust calls `paca` and `pacg`. +fn disables_arm64e_apple_ptrauth(target: &Target, base_feature: &str, enable: bool) -> bool { + !enable && target.is_apple_arm64e() && matches!(base_feature, "paca" | "pacg" | "pauth") +} + /// Utility function for a codegen backend to compute the set of all actually enabled Rust target /// features (which will be stored in `sess.internal_target_features`). /// @@ -321,6 +327,12 @@ pub fn internal_target_features<'a, const N: usize>( sess.dcx().emit_warn(diagnostics::UnknownCTargetFeaturePrefix { feature }); }, |base_feature, new_features, enable| { + if disables_arm64e_apple_ptrauth(&sess.target, base_feature, enable) { + sess.dcx().emit_err(diagnostics::Arm64eApplePtrauthDisableForbidden { + feature: base_feature, + }); + return; + } match features_map.get(base_feature) { None => { // This is definitely not a valid Rust feature name. We do not add it to @@ -487,6 +499,10 @@ pub fn flag_to_backend_features<'a>( // Errors are already emitted in `internal_target_features`; avoid duplicates. }, |base_feature, new_features, enable| { + // Rejected in `internal_target_features`. + if disables_arm64e_apple_ptrauth(&sess.target, base_feature, enable) { + return; + } // Forward unknown features to the backend as that's what we have always done. let new_features = new_features.unwrap_or_else(|| FxHashSet::from_iter(std::iter::once(base_feature))); diff --git a/compiler/rustc_session/src/diagnostics.rs b/compiler/rustc_session/src/diagnostics.rs index 4ec8ca42edbee..71d60c693baef 100644 --- a/compiler/rustc_session/src/diagnostics.rs +++ b/compiler/rustc_session/src/diagnostics.rs @@ -398,6 +398,14 @@ pub(crate) struct PointerAuthenticationNotSupportedForTarget<'a> { pub(crate) target_triple: &'a TargetTuple, } +#[derive(Diagnostic)] +#[diag( + "`-Z pointer-authentication` cannot change pointer authentication for target {$target_triple}, whose ABI fixes it" +)] +pub(crate) struct PointerAuthenticationFixedByTarget<'a> { + pub(crate) target_triple: &'a TargetTuple, +} + #[derive(Diagnostic)] #[diag( "`-Z small-data-threshold` is not supported for target {$target_triple} and will be ignored" @@ -410,6 +418,12 @@ pub(crate) struct SmallDataThresholdNotSupportedForTarget<'a> { #[diag("`-Zbranch-protection` is only supported on aarch64")] pub(crate) struct BranchProtectionRequiresAArch64; +#[derive(Diagnostic)] +#[diag( + "`-Zbranch-protection` cannot use `pac-ret` on arm64e Apple targets, which already sign return addresses" +)] +pub(crate) struct BranchProtectionPacRetOnArm64eApple; + #[derive(Diagnostic)] #[diag("`-Csplit-debuginfo={$debuginfo}` is unstable on this platform")] pub(crate) struct SplitDebugInfoUnstablePlatform { diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 060e614e82180..55edfcf772dfc 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -123,7 +123,7 @@ pub struct PointerAuthSchema { } impl PointerAuthSchema { pub fn function_pointers_default(target: &Target) -> Self { - assert!(target.cfg_abi == CfgAbi::Pauthtest); + assert!(target.cfg_abi == CfgAbi::Pauthtest || target.is_apple_arm64e()); return Self { is_address_discriminated: PointerAuthAddressDiscriminator::HardwareAddress(false), discrimination_kind: PointerAuthDiscrimination::None, @@ -143,7 +143,20 @@ impl PointerAuthSchema { } } +/// Which function pointers are signed, and which calls authenticate their callee. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PointerAuthScope { + /// Functions with the C or system ABI. Their address is signed wherever codegen takes it, so + /// direct calls to them carry a `ptrauth` bundle too, until LLVM folds the pair away. + ExternC, + /// Every function. An address is signed where it becomes a value: a function pointer, a + /// v-table entry, a constant or a static. Every indirect call is authenticated, and direct + /// calls stay direct. + AllFunctions, +} + pub struct PointerAuthConfig { + pub scope: PointerAuthScope, /// Should return addresses be authenticated? pub return_addresses: bool, /// Do authentication failures cause a trap? @@ -170,6 +183,7 @@ impl PointerAuthConfig { fn default(target: &Target) -> Self { assert!(target.cfg_abi == CfgAbi::Pauthtest); return Self { + scope: PointerAuthScope::ExternC, return_addresses: true, auth_traps: true, indirect_gotos: true, @@ -183,6 +197,28 @@ impl PointerAuthConfig { vt_ptr_type_discrimination: true, }; } + /// The arm64e ABI of Apple's platforms, as clang's Darwin driver sets it up. C function + /// pointers use key IA with no diversity. Rust function pointers and v-table entries use the + /// same schema, so that Rust's indirect calls are as protected as those of the C and Swift + /// code in the same process. Initializer and terminator entries are not signed. `intrinsics` + /// and the three v-table fields feed only the ELF PAuth ABI version, so nothing reads them for + /// these Mach-O targets; they record what clang's Darwin driver turns on. + fn arm64e_apple(target: &Target) -> Self { + Self { + scope: PointerAuthScope::AllFunctions, + return_addresses: true, + auth_traps: true, + indirect_gotos: true, + elf_got: false, + aarch64_jump_table_hardening: false, + function_pointers: Some(PointerAuthSchema::function_pointers_default(target)), + init_fini: None, + intrinsics: true, + typeinfo_vt_ptr_discrimination: false, + vt_ptr_addr_discrimination: true, + vt_ptr_type_discrimination: true, + } + } pub fn calculate_pauth_abi_version(&self, target: &Target) -> u32 { assert!(target.cfg_abi == CfgAbi::Pauthtest); // Bit positions of version flags for AARCH64_PAUTH_PLATFORM_LLVM_LINUX. @@ -229,6 +265,10 @@ impl PointerAuthConfig { pauth_abi_version } pub fn from_raw(raw: &[(PointerAuthOption, bool)], target: &Target) -> Option { + // The platform fixes the arm64e ABI, so `-Zpointer-authentication` does not apply. + if target.is_apple_arm64e() { + return Some(Self::arm64e_apple(target)); + } if target.cfg_abi != CfgAbi::Pauthtest { return None; } @@ -1264,6 +1304,18 @@ impl Session { self.pointer_auth_config.as_ref().and_then(|cfg| cfg.function_pointers.as_ref()) } + /// The schema for a function address taken as the callee of a direct call. + pub fn pointer_authentication_direct_callees(&self) -> Option<&PointerAuthSchema> { + match self.pointer_auth_config.as_ref()?.scope { + PointerAuthScope::ExternC => self.pointer_authentication_functions(), + PointerAuthScope::AllFunctions => None, + } + } + + pub fn pointer_authentication_scope(&self) -> Option { + self.pointer_auth_config.as_ref().map(|cfg| cfg.scope) + } + pub fn pointer_authentication_init_fini(&self) -> Option<&PointerAuthSchema> { self.pointer_auth_config.as_ref().and_then(|cfg| cfg.init_fini.as_ref()) } @@ -1562,7 +1614,11 @@ fn validate_commandline_args_with_session_available(sess: &Session) { ); } - if sess.target.cfg_abi != CfgAbi::Pauthtest + if sess.target.is_apple_arm64e() && !sess.opts.unstable_opts.pointer_authentication.is_empty() { + sess.dcx().emit_err(diagnostics::PointerAuthenticationFixedByTarget { + target_triple: &sess.opts.target_triple, + }); + } else if sess.target.cfg_abi != CfgAbi::Pauthtest && !sess.opts.unstable_opts.pointer_authentication.is_empty() { sess.dcx().emit_warn(diagnostics::PointerAuthenticationNotSupportedForTarget { @@ -1730,6 +1786,11 @@ fn validate_commandline_args_with_session_available(sess: &Session) { if sess.opts.unstable_opts.branch_protection.is_some() && sess.target.arch != Arch::AArch64 { sess.dcx().emit_err(diagnostics::BranchProtectionRequiresAArch64); } + if sess.target.is_apple_arm64e() + && sess.opts.unstable_opts.branch_protection.is_some_and(|bp| bp.pac_ret.is_some()) + { + sess.dcx().emit_err(diagnostics::BranchProtectionPacRetOnArm64eApple); + } if let Some(dwarf_version) = sess.opts.cg.dwarf_version.or(sess.opts.unstable_opts.dwarf_version) diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 2d7cd049eb61c..2813650e7c5b4 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -1470,6 +1470,7 @@ supported_targets! { ("aarch64-apple-visionos", aarch64_apple_visionos), ("aarch64-apple-visionos-sim", aarch64_apple_visionos_sim), + ("arm64e-apple-visionos", arm64e_apple_visionos), ("armebv7r-none-eabi", armebv7r_none_eabi), ("armebv7r-none-eabihf", armebv7r_none_eabihf), @@ -2128,6 +2129,11 @@ impl Target { } } + /// Whether this is an arm64e target on an Apple platform, whose ABI signs code pointers. + pub fn is_apple_arm64e(&self) -> bool { + self.is_like_darwin && self.llvm_target.starts_with("arm64e") + } + /// Is this target single-threaded? /// /// This affects both optimizations (e.g., atomics can be lowered to regular operations) and diff --git a/compiler/rustc_target/src/spec/targets/arm64e_apple_darwin.rs b/compiler/rustc_target/src/spec/targets/arm64e_apple_darwin.rs index ab8f369f1bee2..9e14526513af3 100644 --- a/compiler/rustc_target/src/spec/targets/arm64e_apple_darwin.rs +++ b/compiler/rustc_target/src/spec/targets/arm64e_apple_darwin.rs @@ -16,6 +16,7 @@ pub(crate) fn target() -> Target { .into(), arch, options: TargetOptions { + features: "+v8.3a,+paca,+pacg".into(), mcount: "\u{1}mcount".into(), cpu: "apple-m1".into(), max_atomic_width: Some(128), diff --git a/compiler/rustc_target/src/spec/targets/arm64e_apple_visionos.rs b/compiler/rustc_target/src/spec/targets/arm64e_apple_visionos.rs new file mode 100644 index 0000000000000..b1000c2c1c7eb --- /dev/null +++ b/compiler/rustc_target/src/spec/targets/arm64e_apple_visionos.rs @@ -0,0 +1,25 @@ +use crate::spec::base::apple::{Arch, TargetEnv, base}; +use crate::spec::{Os, SanitizerSet, Target, TargetMetadata, TargetOptions}; + +pub(crate) fn target() -> Target { + let (opts, llvm_target, arch) = base(Os::VisionOs, Arch::Arm64e, TargetEnv::Normal); + Target { + llvm_target, + metadata: TargetMetadata { + description: Some("ARM64e Apple visionOS".into()), + tier: Some(3), + host_tools: Some(false), + std: Some(true), + }, + pointer_width: 64, + data_layout: "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32" + .into(), + arch, + options: TargetOptions { + features: "+neon,+apple-a12,+v8.3a,+paca,+pacg".into(), + max_atomic_width: Some(128), + supported_sanitizers: SanitizerSet::ADDRESS | SanitizerSet::THREAD, + ..opts + }, + } +} diff --git a/library/std/build.rs b/library/std/build.rs index 98856b58d890f..248e603151626 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -81,6 +81,14 @@ fn main() { println!("cargo:rustc-env=STD_ENV_ARCH={}", env::var("CARGO_CFG_TARGET_ARCH").unwrap()); + // The arm64e ABI on Apple platforms signs code pointers, and its unwinder expects signed + // landing pads. No `cfg` tells these targets from plain arm64, so recognise them by name. + // A cfg seen only by std keeps this out of `target_abi`, which every build script reads. + println!("cargo:rustc-check-cfg=cfg(arm64e_apple)"); + if target_vendor == "apple" && env::var("TARGET").is_ok_and(|t| t.starts_with("arm64e-")) { + println!("cargo:rustc-cfg=arm64e_apple"); + } + println!("cargo:rustc-check-cfg=cfg(vxworks_lt_25_09)"); if target_os == "vxworks" { diff --git a/library/std/src/sys/personality/gcc.rs b/library/std/src/sys/personality/gcc.rs index b2fa38293f4e5..d231a4276ea07 100644 --- a/library/std/src/sys/personality/gcc.rs +++ b/library/std/src/sys/personality/gcc.rs @@ -211,7 +211,9 @@ cfg_select! { #[rustc_force_inline] unsafe fn sign_lpad(context: *mut uw::_Unwind_Context, lpad: *const u8) -> *const u8 { cfg_select! { - all(target_abi = "pauthtest", target_arch = "aarch64") => { + // An unwinder that authenticates return addresses wants the landing pad signed + // with key IB and the stack pointer, as a return address would be. + any(all(target_abi = "pauthtest", target_arch = "aarch64"), arm64e_apple) => { // DWARF register number for SP on AArch64. const SP_REG: i32 = 31; diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index 6983030fedd0a..775b3c691ec9c 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -36,6 +36,7 @@ pub struct Finder { /// when the newly-bumped stage 0 compiler now knows about the formerly-missing targets. const STAGE0_MISSING_TARGETS: &[&str] = &[ // just a dummy comment so the list doesn't get onelined + "arm64e-apple-visionos", ]; /// Minimum version threshold for libstdc++ required when using prebuilt LLVM diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index f15c712c32b91..e040c00c69cbe 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -46,6 +46,7 @@ - [arm64e-apple-tvos](platform-support/arm64e-apple-tvos.md) - [\*-apple-watchos](platform-support/apple-watchos.md) - [\*-apple-visionos](platform-support/apple-visionos.md) + - [arm64e-apple-visionos](platform-support/arm64e-apple-visionos.md) - [aarch64-nintendo-switch-freestanding](platform-support/aarch64-nintendo-switch-freestanding.md) - [aarch64-unknown-linux-gnu](platform-support/aarch64-unknown-linux-gnu.md) - [aarch64-unknown-linux-musl](platform-support/aarch64-unknown-linux-musl.md) diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index c2a86d85dfcde..18ef3ba5556cc 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -303,6 +303,7 @@ target | std | host | notes [`arm64e-apple-darwin`](platform-support/arm64e-apple-darwin.md) | ✓ | ✓ | ARM64e Apple Darwin [`arm64e-apple-ios`](platform-support/arm64e-apple-ios.md) | ✓ | | ARM64e Apple iOS [`arm64e-apple-tvos`](platform-support/arm64e-apple-tvos.md) | ✓ | | ARM64e Apple tvOS +[`arm64e-apple-visionos`](platform-support/arm64e-apple-visionos.md) | ✓ | | ARM64e Apple visionOS [`armeb-unknown-linux-gnueabi`](platform-support/armeb-unknown-linux-gnueabi.md) | ✓ | ? | Arm BE8 the default Arm big-endian architecture since [Armv6](https://developer.arm.com/documentation/101754/0616/armlink-Reference/armlink-Command-line-Options/--be8?lang=en). [`armebv7r-none-eabi`](platform-support/armebv7r-none-eabi.md) | * | | Bare Armv7-R, Big Endian [`armebv7r-none-eabihf`](platform-support/armebv7r-none-eabi.md) | * | | Bare Armv7-R, Big Endian, hardfloat diff --git a/src/doc/rustc/src/platform-support/arm64e-apple-visionos.md b/src/doc/rustc/src/platform-support/arm64e-apple-visionos.md new file mode 100644 index 0000000000000..fcecca12049f3 --- /dev/null +++ b/src/doc/rustc/src/platform-support/arm64e-apple-visionos.md @@ -0,0 +1,46 @@ +# `arm64e-apple-visionos` + +**Tier: 3** + +Apple arm64e visionOS. + +## Target maintainers + +[cypherair](https://github.com/cypherair) + +## Requirements + +This target is cross-compiled and supports `std`. + +This target requires the visionOS SDK provided by Xcode. + +The deployment target can be configured with `XROS_DEPLOYMENT_TARGET`. + +## Building the target + +You can build Rust with support for the target by adding it to the `target` +list in `bootstrap.toml`: +```toml +[build] +target = ["arm64e-apple-visionos"] +``` + +## Building Rust programs + +Rust does not yet ship pre-compiled artifacts for this target. +To compile for this target, you will need to build Rust with the target enabled. + +## Testing + +This target is not tested by Rust CI. + +Binaries are intended to run on visionOS devices with `arm64e` architecture. + +The visionOS simulator targets use `arm64`, not `arm64e`. + +## Cross-compilation toolchains and C code + +C code should be built with Apple's Clang from Xcode, using the visionOS SDK and +matching deployment target. + +The Clang target is suffixed with `-xros`. diff --git a/src/doc/unstable-book/src/compiler-flags/branch-protection.md b/src/doc/unstable-book/src/compiler-flags/branch-protection.md index c15567dcac2ee..fc03a4b944a04 100644 --- a/src/doc/unstable-book/src/compiler-flags/branch-protection.md +++ b/src/doc/unstable-book/src/compiler-flags/branch-protection.md @@ -19,6 +19,10 @@ It takes some combination of the following values, separated by a `,`. For example, `-Z branch-protection=bti,pac-ret,leaf` is valid, but `-Z branch-protection=bti,leaf,pac-ret` is not. -Rust's standard library does not ship with BTI or pointer authentication enabled by default. +arm64e Apple targets already sign return addresses as part of their ABI, so they reject `pac-ret`. +They accept `bti` and `gcs`. + +Outside arm64e Apple targets, Rust's standard library does not ship with BTI or pointer +authentication enabled by default. In Cargo projects the standard library can be recompiled with pointer authentication using the nightly [build-std](../../cargo/reference/unstable.html#build-std) feature. diff --git a/tests/assembly-llvm/arm64e-apple-ptrauth-calls.rs b/tests/assembly-llvm/arm64e-apple-ptrauth-calls.rs new file mode 100644 index 0000000000000..4d24814e1ea2a --- /dev/null +++ b/tests/assembly-llvm/arm64e-apple-ptrauth-calls.rs @@ -0,0 +1,59 @@ +// Indirect calls authenticate with `blraaz` or `braaz`; direct calls do not. A function that saves +// its return address signs it with `pacibsp` and returns with `retab`. +// Disabling `v8.3a` does not disable `paca` and `pacg`, which it implies: the backend keeps `pauth` +// and still emits these instructions (without `pauth` it prints `pacibsp` as `hint #27`). +//@ add-minicore +//@ assembly-output: emit-asm +//@ revisions: DARWIN NO_V83A IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=2 +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [NO_V83A] compile-flags: --target arm64e-apple-darwin -Copt-level=2 -Ctarget-feature=-v8.3a +//@ [NO_V83A] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=2 +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=2 +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=2 +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +extern crate minicore; + +#[inline(never)] +#[unsafe(no_mangle)] +pub extern "C" fn target(x: i32) -> i32 { + x +} + +#[unsafe(no_mangle)] +pub extern "C" fn direct(x: i32) -> i32 { + target(x) +} + +#[unsafe(no_mangle)] +pub extern "C" fn indirect_tail(f: extern "C" fn(i32) -> i32, x: i32) -> i32 { + f(x) +} + +#[unsafe(no_mangle)] +pub extern "C" fn indirect_non_tail(f: extern "C" fn(i32) -> i32, x: i32) -> i32 { + match f(x) { + 0 => target(x), + y => y, + } +} + +// CHECK-LABEL: _direct: +// CHECK-NOT: braaz +// CHECK-NOT: blraaz + +// CHECK-LABEL: _indirect_tail: +// CHECK: braaz + +// CHECK-LABEL: _indirect_non_tail: +// CHECK: pacibsp +// CHECK: blraaz +// CHECK: retab diff --git a/tests/assembly-llvm/targets/targets-macho.rs b/tests/assembly-llvm/targets/targets-macho.rs index 037f3c0093619..00383a6028d3e 100644 --- a/tests/assembly-llvm/targets/targets-macho.rs +++ b/tests/assembly-llvm/targets/targets-macho.rs @@ -34,6 +34,9 @@ //@ revisions: aarch64_apple_visionos //@ [aarch64_apple_visionos] compile-flags: --target aarch64-apple-visionos //@ [aarch64_apple_visionos] needs-llvm-components: aarch64 +//@ revisions: arm64e_apple_visionos +//@ [arm64e_apple_visionos] compile-flags: --target arm64e-apple-visionos +//@ [arm64e_apple_visionos] needs-llvm-components: aarch64 //@ revisions: aarch64_apple_visionos_sim //@ [aarch64_apple_visionos_sim] compile-flags: --target aarch64-apple-visionos-sim //@ [aarch64_apple_visionos_sim] needs-llvm-components: aarch64 diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-aarch64-control.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-aarch64-control.rs new file mode 100644 index 0000000000000..c43a3a8ffccfc --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-aarch64-control.rs @@ -0,0 +1,44 @@ +// The plain arm64 Apple targets keep upstream's behaviour: no pointer-authentication attributes, +// no signed function pointers and no `ptrauth` bundles. +//@ add-minicore +//@ revisions: AARCH64_DARWIN AARCH64_IOS AARCH64_VISIONOS +//@ [AARCH64_DARWIN] compile-flags: --target aarch64-apple-darwin -Copt-level=0 +//@ [AARCH64_DARWIN] needs-llvm-components: aarch64 +//@ [AARCH64_IOS] compile-flags: --target aarch64-apple-ios -Copt-level=0 +//@ [AARCH64_IOS] needs-llvm-components: aarch64 +//@ [AARCH64_VISIONOS] compile-flags: --target aarch64-apple-visionos -Copt-level=0 +//@ [AARCH64_VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +extern crate minicore; + +// CHECK-NOT: "ptrauth +// CHECK-NOT: ptrauth (ptr +// CHECK-LABEL: @target( +#[unsafe(no_mangle)] +pub fn target() -> u32 { + 7 +} + +// CHECK-NOT: "ptrauth +// CHECK-NOT: ptrauth (ptr +// CHECK-LABEL: @reify( +// CHECK: ret ptr @target +#[unsafe(no_mangle)] +pub fn reify() -> fn() -> u32 { + target +} + +// CHECK-LABEL: @call( +// CHECK-NOT: "ptrauth +// CHECK: ret i32 +#[unsafe(no_mangle)] +pub fn call(f: fn() -> u32) -> u32 { + f() +} + +// CHECK: attributes # +// CHECK-NOT: "ptrauth diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-callbr.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-callbr.rs new file mode 100644 index 0000000000000..ef3db87c7812b --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-callbr.rs @@ -0,0 +1,28 @@ +// `asm goto` lowers to `callbr`, whose callee is inline assembly: it carries no `ptrauth` bundle. +//@ add-minicore +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(asm_goto_with_outputs, no_core, lang_items)] +#![no_core] + +extern crate minicore; +use minicore::*; + +#[unsafe(no_mangle)] +pub unsafe fn inline_asm_goto() { + unsafe { asm!("b {}", label {}) }; +} + +// CHECK-LABEL: define{{.*}} @inline_asm_goto( +// CHECK-NOT: callbr{{.*}}"ptrauth" +// CHECK: callbr void asm sideeffect alignstack "b ${0:l}", "!i{{.*}}"() #{{[0-9]+}}{{$}} +// CHECK-NEXT: to label %{{.*}} [label %{{.*}}] diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs new file mode 100644 index 0000000000000..94707d65e108a --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs @@ -0,0 +1,51 @@ +// Indirect calls carry a `ptrauth` bundle with key IA and discriminator 0; direct calls carry none. +//@ add-minicore +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=2 +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=2 +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=2 +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=2 +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +extern crate minicore; + +#[inline(never)] +#[unsafe(no_mangle)] +pub extern "C" fn target(x: i32) -> i32 { + x +} + +#[unsafe(no_mangle)] +pub extern "C" fn direct(x: i32) -> i32 { + target(x) +} + +#[unsafe(no_mangle)] +pub extern "C" fn indirect_tail(f: extern "C" fn(i32) -> i32, x: i32) -> i32 { + f(x) +} + +#[unsafe(no_mangle)] +pub extern "C" fn indirect_non_tail(f: extern "C" fn(i32) -> i32, x: i32) -> i32 { + match f(x) { + 0 => target(x), + y => y, + } +} + +// CHECK-LABEL: define{{.*}} @direct( +// CHECK-NOT: [ "ptrauth"(i32 0, i64 0) ] +// CHECK: ret i32 + +// CHECK-LABEL: define{{.*}} @indirect_tail( +// CHECK: {{(tail )?}}call{{.*}} {{%.*}}(i32 {{.*}}){{.*}} [ "ptrauth"(i32 0, i64 0) ]{{$}} + +// CHECK-LABEL: define{{.*}} @indirect_non_tail( +// CHECK: {{(tail )?}}call{{.*}} {{%.*}}(i32 {{.*}}){{.*}} [ "ptrauth"(i32 0, i64 0) ]{{$}} diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-direct-callees.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-direct-callees.rs new file mode 100644 index 0000000000000..13baaa1af720e --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-direct-callees.rs @@ -0,0 +1,97 @@ +// ignore-tidy-file-linelength +// Callees that codegen calls directly stay unsigned and carry no `ptrauth` bundle, even +// unoptimised: drop glue, a lang item (the abort when a `nounwind` function would unwind), and +// the personality, which unwind tables name. minicore has no `Drop` impl and no personality, so +// this test declares its own lang items. +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=0 -Cpanic=unwind +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=0 -Cpanic=unwind +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 -Cpanic=unwind +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=0 -Cpanic=unwind +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(auto_traits, no_core, lang_items, rustc_attrs)] +#![no_core] + +#[lang = "pointee_sized"] +pub trait PointeeSized {} + +#[lang = "meta_sized"] +pub trait MetaSized: PointeeSized {} + +#[lang = "sized"] +pub trait Sized: MetaSized {} + +#[lang = "destruct"] +pub trait Destruct: PointeeSized {} + +#[lang = "copy"] +pub trait Copy: Sized {} + +#[lang = "freeze"] +pub unsafe auto trait Freeze {} + +#[lang = "unsafe_unpin"] +pub unsafe auto trait UnsafeUnpin {} + +#[lang = "unpin"] +pub auto trait Unpin {} + +#[lang = "legacy_receiver"] +pub trait LegacyReceiver: PointeeSized {} + +impl LegacyReceiver for &mut T {} + +#[lang = "drop"] +pub trait Drop { + fn drop(&mut self); +} + +#[lang = "drop_glue"] +fn drop_glue(_: &mut T) {} + +#[lang = "panic_cannot_unwind"] +fn panic_cannot_unwind() -> ! { + loop {} +} + +#[lang = "eh_personality"] +extern "C" fn rust_eh_personality() {} + +pub struct Guard; + +impl Drop for Guard { + fn drop(&mut self) {} +} + +unsafe extern "C-unwind" { + safe fn may_unwind(); +} + +// CHECK-LABEL: @drop_guard( +// CHECK-NOT: "ptrauth" +// CHECK-NOT: ptrauth (ptr +// CHECK: call void @{{.*}}drop_glue{{.*}}(ptr +// CHECK-NOT: "ptrauth" +// CHECK-NOT: ptrauth (ptr +// CHECK: ret void +#[unsafe(no_mangle)] +pub fn drop_guard(_guard: Guard) {} + +// CHECK-LABEL: @no_unwind_boundary( +// CHECK-SAME: personality ptr @{{[^ ]*}}rust_eh_personality +// CHECK-NOT: "ptrauth" +// CHECK-NOT: ptrauth (ptr +// CHECK: invoke void @may_unwind() +// CHECK-NOT: "ptrauth" +// CHECK-NOT: ptrauth (ptr +// CHECK: call void @{{.*}}panic_cannot_unwind{{.*}}() +#[unsafe(no_mangle)] +#[rustc_nounwind] +pub fn no_unwind_boundary() { + may_unwind(); +} diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-entry.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-entry.rs new file mode 100644 index 0000000000000..9303e5b6e57a4 --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-entry.rs @@ -0,0 +1,34 @@ +// The C `main` that rustc generates carries the pointer-authentication attributes too, and the +// pointer to the Rust `main` that it hands to `lang_start` is signed. +//@ add-minicore +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=0 +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=0 +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=0 +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![feature(no_core, lang_items)] +#![no_core] + +extern crate minicore; + +#[lang = "start"] +fn lang_start( + _main: fn() -> T, + _argc: isize, + _argv: *const *const u8, + _sigpipe: u8, +) -> isize { + 0 +} + +fn main() {} + +// CHECK: define i32 @main({{.*}}) unnamed_addr [[MAIN_ATTR:#[0-9]+]] +// CHECK: call {{[^(]*}}@{{[^(]*}}lang_start{{[^(]*}}(ptr ptrauth (ptr @{{.*}}4main{{.*}}, i32 0), +// CHECK: attributes [[MAIN_ATTR]] = {{.*}} "ptrauth-auth-traps" "ptrauth-calls" +// CHECK-SAME: "ptrauth-indirect-gotos" "ptrauth-returns" diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs new file mode 100644 index 0000000000000..a8dbf3e03562c --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs @@ -0,0 +1,39 @@ +// A function pointer passed as an argument is signed, and the callee authenticates it. +//@ add-minicore +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=0 -Zverify-llvm-ir +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=0 -Zverify-llvm-ir +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 -Zverify-llvm-ir +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=0 -Zverify-llvm-ir +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +extern crate minicore; + +#[unsafe(no_mangle)] +pub extern "C" fn invoke_main(main_fn: extern "C" fn(*const u8) -> i32, state: *const u8) -> i32 { + main_fn(state) +} + +#[unsafe(no_mangle)] +pub extern "C" fn sample_main(_: *const u8) -> i32 { + 7 +} + +#[unsafe(no_mangle)] +pub extern "C" fn call_sample() -> i32 { + let f = sample_main; + invoke_main(f, 0 as *const u8) +} + +// CHECK-LABEL: define{{.*}} i32 @invoke_main( +// CHECK: call i32 %{{.*}}(ptr %{{.*}}){{.*}} [ "ptrauth"(i32 0, i64 0) ] + +// CHECK-LABEL: define{{.*}} i32 @call_sample( +// CHECK: call i32 @invoke_main(ptr ptrauth (ptr @sample_main, i32 0), ptr null) diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-init-term.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-init-term.rs new file mode 100644 index 0000000000000..beb83a7654f23 --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-init-term.rs @@ -0,0 +1,55 @@ +// ignore-tidy-file-linelength +// On arm64e Apple targets the entries of `__mod_init_func` and `__mod_term_func` stay unsigned, +// whether the section is named or typed, like the constructors clang emits itself. (Clang signs a +// static that C code places in these sections by hand.) Either form works: Apple's linker turns +// initializer entries into offsets and dyld re-signs them, and dyld ignores `__mod_term_func` on +// arm64e. The same function pointer in an ordinary static is signed. +//@ add-minicore +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core)] +#![no_core] + +extern crate minicore; + +// Different signatures, so that LLVM does not merge the two functions. +extern "C" fn init_fn() {} +extern "C" fn term_fn(_: i32) {} + +// CHECK-DAG: @{{.*}}CTOR_CRATE = {{.*}}constant ptr @{{.*}}init_fn{{.*}}, section "__DATA,__mod_init_func,mod_init_funcs" +#[used] +#[unsafe(link_section = "__DATA,__mod_init_func,mod_init_funcs")] +static CTOR_CRATE: extern "C" fn() = init_fn; + +// CHECK-DAG: @{{.*}}BY_NAME = {{.*}}constant ptr @{{.*}}init_fn{{.*}}, section "__DATA,__mod_init_func" +#[used] +#[unsafe(link_section = "__DATA,__mod_init_func")] +static BY_NAME: extern "C" fn() = init_fn; + +// CHECK-DAG: @{{.*}}BY_TYPE = {{.*}}constant ptr @{{.*}}init_fn{{.*}}, section "__DATA, __inits , mod_init_funcs" +#[used] +#[unsafe(link_section = "__DATA, __inits , mod_init_funcs")] +static BY_TYPE: extern "C" fn() = init_fn; + +// CHECK-DAG: @{{.*}}TERM = {{.*}}constant ptr @{{.*}}term_fn{{.*}}, section "__DATA,__mod_term_func,mod_term_funcs" +#[used] +#[unsafe(link_section = "__DATA,__mod_term_func,mod_term_funcs")] +static TERM: extern "C" fn(i32) = term_fn; + +// CHECK-DAG: @{{.*}}TERM_BY_TYPE = {{.*}}constant ptr @{{.*}}term_fn{{.*}}, section "__DATA,__terms,mod_term_funcs" +#[used] +#[unsafe(link_section = "__DATA,__terms,mod_term_funcs")] +static TERM_BY_TYPE: extern "C" fn(i32) = term_fn; + +// CHECK-DAG: @{{.*}}ORDINARY = {{.*}}constant ptr ptrauth (ptr @{{.*}}init_fn{{.*}}, i32 0) +#[used] +static ORDINARY: extern "C" fn() = init_fn; diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-inline-asm.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-inline-asm.rs new file mode 100644 index 0000000000000..2da0081c94b29 --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-inline-asm.rs @@ -0,0 +1,28 @@ +// A call to inline assembly carries no `ptrauth` bundle: its callee is not a pointer. +//@ add-minicore +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +extern crate minicore; +use minicore::*; + +#[unsafe(no_mangle)] +pub unsafe fn inline_asm() { + unsafe { asm!("nop") }; +} + +// CHECK-LABEL: define{{.*}} @inline_asm( +// CHECK: call void asm sideeffect alignstack "nop" +// CHECK-NOT: [ "ptrauth"(i32 0, i64 0) ] +// CHECK: ret void diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs new file mode 100644 index 0000000000000..99d5340beda78 --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs @@ -0,0 +1,39 @@ +// ignore-tidy-file-linelength +// `__rust_try`, which rustc generates for `catch_unwind`, carries the pointer-authentication +// attributes, and its calls through the `try_fn` and `catch_fn` pointers authenticate the callee. +//@ add-minicore +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=0 -Cpanic=unwind -Zverify-llvm-ir +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=0 -Cpanic=unwind -Zverify-llvm-ir +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 -Cpanic=unwind -Zverify-llvm-ir +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=0 -Cpanic=unwind -Zverify-llvm-ir +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(intrinsics, no_core, lang_items)] +#![no_core] + +extern crate minicore; + +#[rustc_intrinsic] +pub unsafe fn catch_unwind( + try_fn: unsafe fn(*mut Data), + data: *mut Data, + catch_fn: unsafe fn(*mut Data, *mut u8), +) -> bool; + +unsafe fn try_fn(_: *mut u8) {} +unsafe fn catch_fn(_: *mut u8, _: *mut u8) {} + +#[unsafe(no_mangle)] +pub unsafe fn invoke_catch() -> bool { + unsafe { catch_unwind(try_fn, 0 as *mut u8, catch_fn) } +} + +// CHECK: define internal {{.*}}@__rust_try({{.*}}) unnamed_addr [[TRY_ATTR:#[0-9]+]] personality +// CHECK: invoke void %{{.*}}(ptr %{{.*}}){{.*}} [ "ptrauth"(i32 0, i64 0) ] +// CHECK: call void %{{.*}}(ptr %{{.*}}, ptr %{{.*}}){{.*}} [ "ptrauth"(i32 0, i64 0) ] +// CHECK: attributes [[TRY_ATTR]] = {{.*}} "ptrauth-auth-traps" "ptrauth-calls" "ptrauth-indirect-gotos" "ptrauth-returns" diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-scope.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-scope.rs new file mode 100644 index 0000000000000..6923de3f58766 --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-scope.rs @@ -0,0 +1,137 @@ +// ignore-tidy-file-linelength +// On arm64e Apple targets every function address that becomes a value is signed with key IA and +// discriminator 0, whatever the function's ABI: function pointers, closures, constants, statics, +// `extern_weak` statics and v-table entries. Every indirect call authenticates its callee, and +// direct calls stay direct, even unoptimised. +//@ add-minicore +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=0 -Zverify-llvm-ir +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=0 -Zverify-llvm-ir +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 -Zverify-llvm-ir +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=0 -Zverify-llvm-ir +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items, linkage)] +#![no_core] + +extern crate minicore; +use minicore::*; + +pub trait Shape { + fn area(&self) -> u32; +} + +pub struct Square(pub u32); + +impl Shape for Square { + fn area(&self) -> u32 { + self.0 + } +} + +#[inline(never)] +fn rust_target() -> u32 { + 7 +} + +#[inline(never)] +#[unsafe(no_mangle)] +pub extern "C" fn c_target(x: u32) -> u32 { + x +} + +const RUST_TARGET: fn() -> u32 = rust_target; + +// rustc recognises an `extern_weak` function by the `Option` lang item, which minicore lacks. +#[lang = "Option"] +pub enum Weak { + Missing, + Present(T), +} +impl Copy for Weak {} + +#[allow(improper_ctypes)] +extern "C" { + #[linkage = "extern_weak"] + static weak_rust_fn: Weak u32>; +} + +// CHECK-DAG: @RUST_TARGET_STATIC = {{.*}}constant ptr ptrauth (ptr @{{.*}}rust_target{{.*}}, i32 0) +// CHECK-DAG: @_rust_extern_with_linkage_{{.*}}weak_rust_fn = internal {{.*}}global ptr ptrauth (ptr @weak_rust_fn, i32 0) +// CHECK-DAG: constant {{.*}}ptrauth (ptr @{{.*}}Square{{.*}}area{{.*}}, i32 0) +#[unsafe(no_mangle)] +pub static RUST_TARGET_STATIC: fn() -> u32 = rust_target; + +// CHECK-LABEL: @rust_fn_ptr( +// CHECK: ret ptr ptrauth (ptr @{{.*}}rust_target{{.*}}, i32 0) +#[unsafe(no_mangle)] +pub fn rust_fn_ptr() -> fn() -> u32 { + rust_target +} + +// CHECK-LABEL: @closure_fn_ptr( +// CHECK: ret ptr ptrauth (ptr @{{.*}}, i32 0) +#[unsafe(no_mangle)] +pub fn closure_fn_ptr() -> fn() -> u32 { + || 7 +} + +// CHECK-LABEL: @const_fn_ptr( +// CHECK: ret ptr ptrauth (ptr @{{.*}}rust_target{{.*}}, i32 0) +#[unsafe(no_mangle)] +pub fn const_fn_ptr() -> fn() -> u32 { + RUST_TARGET +} + +#[unsafe(no_mangle)] +pub fn weak_fn_ptr() -> Weak u32> { + unsafe { weak_rust_fn } +} + +// CHECK-LABEL: @call_rust_fn_ptr( +// CHECK: call {{.*}}i32 %{{.*}}(){{.*}} [ "ptrauth"(i32 0, i64 0) ] +#[unsafe(no_mangle)] +pub fn call_rust_fn_ptr(f: fn() -> u32) -> u32 { + f() +} + +// CHECK-LABEL: @call_reified( +// CHECK: call {{.*}}i32 ptrauth (ptr @{{.*}}rust_target{{.*}}, i32 0)(){{.*}} [ "ptrauth"(i32 0, i64 0) ] +#[unsafe(no_mangle)] +pub fn call_reified() -> u32 { + let f: fn() -> u32 = rust_target; + f() +} + +// CHECK-LABEL: @call_dyn( +// CHECK: call {{.*}}i32 %{{.*}}(ptr {{.*}}%{{.*}}){{.*}} [ "ptrauth"(i32 0, i64 0) ] +#[unsafe(no_mangle)] +pub fn call_dyn(shape: &dyn Shape) -> u32 { + shape.area() +} + +// CHECK-LABEL: @make_dyn( +#[unsafe(no_mangle)] +pub fn make_dyn(square: &Square) -> u32 { + call_dyn(square) +} + +// CHECK-LABEL: @direct_calls( +// CHECK-NOT: "ptrauth" +// CHECK-NOT: ptrauth (ptr +// CHECK: call {{.*}}i32 @{{.*}}rust_target{{.*}}() +// CHECK-NOT: "ptrauth" +// CHECK-NOT: ptrauth (ptr +// CHECK: call {{.*}}i32 @c_target(i32 {{.*}}%{{.*}}) +// CHECK-NOT: "ptrauth" +// CHECK-NOT: ptrauth (ptr +// CHECK: ret i32 +#[unsafe(no_mangle)] +pub fn direct_calls(x: u32) -> u32 { + rust_target(); + c_target(x) +} diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth.rs b/tests/codegen-llvm/arm64e-apple-ptrauth.rs new file mode 100644 index 0000000000000..fee3859adf892 --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth.rs @@ -0,0 +1,36 @@ +// ignore-tidy-file-linelength +// Every function on an arm64e Apple target carries the four attributes that clang's Darwin driver +// turns on, and none of the ELF PAuth ABI's attributes or module flags. +//@ add-minicore +//@ filecheck-flags: --implicit-check-not=aarch64-jump-table-hardening +//@ filecheck-flags: --implicit-check-not=aarch64-elf-pauthabi --implicit-check-not=ptrauth-elf-got +//@ filecheck-flags: --implicit-check-not=ptrauth-sign-personality +//@ filecheck-flags: --implicit-check-not=ptrauth.abi-version +//@ revisions: DARWIN DARWIN_BTI DARWIN_GCS IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [DARWIN_BTI] compile-flags: --target arm64e-apple-darwin -Zbranch-protection=bti +//@ [DARWIN_BTI] needs-llvm-components: aarch64 +//@ [DARWIN_GCS] compile-flags: --target arm64e-apple-darwin -Zbranch-protection=gcs +//@ [DARWIN_GCS] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +extern crate minicore; + +// CHECK: @test(){{.*}} [[ATTR:#[0-9]+]] +#[unsafe(no_mangle)] +pub fn test() {} + +// CHECK: attributes [[ATTR]] = {{.*}} "ptrauth-auth-traps" "ptrauth-calls" "ptrauth-indirect-gotos" "ptrauth-returns" +// CHECK-SAME: "target-features"="{{.*}}+pauth{{.*}}" +// DARWIN_BTI: !{{[0-9]+}} = !{i32 8, !"branch-target-enforcement", i32 1} +// DARWIN_GCS: !{{[0-9]+}} = !{i32 8, !"guarded-control-stack", i32 1} diff --git a/tests/run-make/arm64e-ptrauth-allocator-shim/rmake.rs b/tests/run-make/arm64e-ptrauth-allocator-shim/rmake.rs new file mode 100644 index 0000000000000..b80cc502ab95a --- /dev/null +++ b/tests/run-make/arm64e-ptrauth-allocator-shim/rmake.rs @@ -0,0 +1,59 @@ +//! rustc builds the allocator shim outside the usual function path, as its own LLVM module. On +//! arm64e Apple targets its functions must still carry the pointer-authentication attributes. + +//@ needs-llvm-components: aarch64 +//@ ignore-backends: gcc + +use run_make_support::{llvm_dis, rfs, rustc}; + +const PTRAUTH_ATTRS: [&str; 4] = [ + "\"ptrauth-auth-traps\"", + "\"ptrauth-calls\"", + "\"ptrauth-indirect-gotos\"", + "\"ptrauth-returns\"", +]; + +fn main() { + // Only `-Csave-temps` keeps the shim's module, as bitcode. + rustc() + .input("shim.rs") + .target("arm64e-apple-darwin") + .emit("obj") + .arg("-Csave-temps") + .arg("-Ccodegen-units=1") + .run(); + + let bitcode: Vec<_> = rfs::read_dir(".") + .map(|entry| entry.unwrap().path()) + .filter(|path| path.extension().is_some_and(|ext| ext == "bc")) + .collect(); + let mut shims = 0; + for bc in bitcode { + let ll = bc.with_extension("ll"); + llvm_dis().input(&bc).arg("-o").arg(&ll).run(); + let ir = rfs::read_to_string(&ll); + if ir.lines().any(|line| { + line.starts_with("define ") && line.contains("__rust_no_alloc_shim_is_unstable_v2") + }) { + shims += 1; + check_functions(&ir); + } + } + assert!(shims > 0, "no bitcode file holds the allocator shim"); +} + +fn check_functions(ir: &str) { + for define in ir.lines().filter(|line| line.starts_with("define ")) { + let group = define + .split_whitespace() + .find(|word| word.starts_with('#')) + .unwrap_or_else(|| panic!("no attribute group on `{define}`")); + let attrs = ir + .lines() + .find(|line| line.starts_with(&format!("attributes {group} = "))) + .unwrap_or_else(|| panic!("attribute group {group} is not defined")); + for attr in PTRAUTH_ATTRS { + assert!(attrs.contains(attr), "`{define}` lacks {attr}: `{attrs}`"); + } + } +} diff --git a/tests/run-make/arm64e-ptrauth-allocator-shim/shim.rs b/tests/run-make/arm64e-ptrauth-allocator-shim/shim.rs new file mode 100644 index 0000000000000..76cd0dee1ccdc --- /dev/null +++ b/tests/run-make/arm64e-ptrauth-allocator-shim/shim.rs @@ -0,0 +1,5 @@ +#![feature(allocator_internals, no_core)] +#![crate_type = "cdylib"] +#![default_lib_allocator] +#![needs_allocator] +#![no_core] diff --git a/tests/run-make/arm64e-ptrauth-serialized-output/ptrauth.rs b/tests/run-make/arm64e-ptrauth-serialized-output/ptrauth.rs new file mode 100644 index 0000000000000..3ed331931bd6e --- /dev/null +++ b/tests/run-make/arm64e-ptrauth-serialized-output/ptrauth.rs @@ -0,0 +1,30 @@ +#![crate_type = "lib"] +#![feature(lang_items, no_core)] +#![no_core] + +#[lang = "pointee_sized"] +pub trait PointeeSized {} + +#[lang = "meta_sized"] +pub trait MetaSized: PointeeSized {} + +#[lang = "sized"] +pub trait Sized: MetaSized {} + +#[lang = "copy"] +pub trait Copy: Sized {} + +// External, so that LLVM cannot remove the call once it is direct. +extern "C" { + fn target(x: i32) -> i32; +} + +#[inline(always)] +fn call_indirect(f: unsafe extern "C" fn(i32) -> i32, x: i32) -> i32 { + unsafe { f(x) } +} + +#[no_mangle] +pub extern "C" fn devirtualized(x: i32) -> i32 { + call_indirect(target, x) +} diff --git a/tests/run-make/arm64e-ptrauth-serialized-output/rmake.rs b/tests/run-make/arm64e-ptrauth-serialized-output/rmake.rs new file mode 100644 index 0000000000000..e50b97db14d68 --- /dev/null +++ b/tests/run-make/arm64e-ptrauth-serialized-output/rmake.rs @@ -0,0 +1,65 @@ +//! Inlining turns the call through a signed pointer in `devirtualized` into a direct call. In the +//! IR, the bitcode and the embedded bitcode that rustc writes, that call must carry no `ptrauth` +//! bundle. + +//@ needs-llvm-components: aarch64 + +use std::path::{Path, PathBuf}; + +use run_make_support::{llvm_ar, llvm_dis, llvm_objcopy, rfs, rustc}; + +/// Checks that `devirtualized` still calls `target` directly, without a `ptrauth` bundle. +fn assert_no_illegal_direct_ptrauth(path: &str) { + let ir = rfs::read_to_string(path); + let body: Vec<&str> = ir + .lines() + .skip_while(|line| !(line.starts_with("define ") && line.contains("@devirtualized("))) + .take_while(|line| *line != "}") + .collect(); + assert!(!body.is_empty(), "{path} defines no `devirtualized`"); + let call = body + .iter() + .find(|line| line.contains("@target(")) + .unwrap_or_else(|| panic!("`devirtualized` in {path} no longer calls `target` directly")); + assert!( + !call.contains("\"ptrauth\""), + "{path}: the direct call carries a ptrauth operand bundle:\n{call}" + ); +} + +fn main() { + rustc() + .input("ptrauth.rs") + .target("arm64e-apple-darwin") + .opt_level("2") + .arg("--emit=llvm-ir=module.ll,llvm-bc=module.bc") + .run(); + assert_no_illegal_direct_ptrauth("module.ll"); + + llvm_dis().input("module.bc").arg("-o").arg("module-bc.ll").run(); + assert_no_illegal_direct_ptrauth("module-bc.ll"); + + rustc() + .input("ptrauth.rs") + .target("arm64e-apple-darwin") + .opt_level("2") + .arg("-Cembed-bitcode=yes") + .crate_type("rlib") + .arg("-o") + .arg("libptrauth.rlib") + .run(); + + if Path::new("extract").exists() { + rfs::recursive_remove("extract"); + } + rfs::create_dir("extract"); + llvm_ar().extract().arg("../libptrauth.rlib").current_dir("extract").run(); + let object = rfs::read_dir("extract") + .map(|entry| entry.unwrap().path()) + .find(|path| path.extension().is_some_and(|ext| ext == "o")) + .expect("embedded-bitcode rlib should contain an object file"); + let embedded_bc = PathBuf::from("extract").join("embedded.bc"); + llvm_objcopy().dump_section("__LLVM,__bitcode", &embedded_bc).arg(&object).run(); + llvm_dis().input(&embedded_bc).arg("-o").arg("embedded.ll").run(); + assert_no_illegal_direct_ptrauth("embedded.ll"); +} diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PACRET.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PACRET.stderr new file mode 100644 index 0000000000000..f1edbbf2be921 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PACRET.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` cannot use `pac-ret` on arm64e Apple targets, which already sign return addresses + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PAUTHLR_LEAF.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PAUTHLR_LEAF.stderr new file mode 100644 index 0000000000000..f1edbbf2be921 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PAUTHLR_LEAF.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` cannot use `pac-ret` on arm64e Apple targets, which already sign return addresses + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PACRET.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PACRET.stderr new file mode 100644 index 0000000000000..f1edbbf2be921 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PACRET.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` cannot use `pac-ret` on arm64e Apple targets, which already sign return addresses + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PAUTHLR_LEAF.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PAUTHLR_LEAF.stderr new file mode 100644 index 0000000000000..f1edbbf2be921 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PAUTHLR_LEAF.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` cannot use `pac-ret` on arm64e Apple targets, which already sign return addresses + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PACRET.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PACRET.stderr new file mode 100644 index 0000000000000..f1edbbf2be921 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PACRET.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` cannot use `pac-ret` on arm64e Apple targets, which already sign return addresses + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PAUTHLR_LEAF.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PAUTHLR_LEAF.stderr new file mode 100644 index 0000000000000..f1edbbf2be921 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PAUTHLR_LEAF.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` cannot use `pac-ret` on arm64e Apple targets, which already sign return addresses + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PACRET.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PACRET.stderr new file mode 100644 index 0000000000000..f1edbbf2be921 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PACRET.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` cannot use `pac-ret` on arm64e Apple targets, which already sign return addresses + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PAUTHLR_LEAF.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PAUTHLR_LEAF.stderr new file mode 100644 index 0000000000000..f1edbbf2be921 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PAUTHLR_LEAF.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` cannot use `pac-ret` on arm64e Apple targets, which already sign return addresses + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs new file mode 100644 index 0000000000000..b02edc5976790 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs @@ -0,0 +1,43 @@ +// ignore-tidy-file-linelength +// arm64e Apple targets sign return addresses through their ABI, so they reject `pac-ret`. +//@ ignore-backends: gcc +//@ revisions: DARWIN_PACRET DARWIN_PAUTHLR_LEAF IOS_PACRET IOS_PAUTHLR_LEAF TVOS_PACRET TVOS_PAUTHLR_LEAF VISIONOS_PACRET VISIONOS_PAUTHLR_LEAF +//@ [DARWIN_PACRET] compile-flags: --target=arm64e-apple-darwin -Zbranch-protection=pac-ret +//@ [DARWIN_PACRET] check-fail +//@ [DARWIN_PACRET] needs-llvm-components: aarch64 +//@ [DARWIN_PAUTHLR_LEAF] compile-flags: --target=arm64e-apple-darwin -Zbranch-protection=pac-ret,pc,leaf +//@ [DARWIN_PAUTHLR_LEAF] check-fail +//@ [DARWIN_PAUTHLR_LEAF] needs-llvm-components: aarch64 +//@ [IOS_PACRET] compile-flags: --target=arm64e-apple-ios -Zbranch-protection=pac-ret +//@ [IOS_PACRET] check-fail +//@ [IOS_PACRET] needs-llvm-components: aarch64 +//@ [IOS_PAUTHLR_LEAF] compile-flags: --target=arm64e-apple-ios -Zbranch-protection=pac-ret,pc,leaf +//@ [IOS_PAUTHLR_LEAF] check-fail +//@ [IOS_PAUTHLR_LEAF] needs-llvm-components: aarch64 +//@ [TVOS_PACRET] compile-flags: --target=arm64e-apple-tvos -Zbranch-protection=pac-ret +//@ [TVOS_PACRET] check-fail +//@ [TVOS_PACRET] needs-llvm-components: aarch64 +//@ [TVOS_PAUTHLR_LEAF] compile-flags: --target=arm64e-apple-tvos -Zbranch-protection=pac-ret,pc,leaf +//@ [TVOS_PAUTHLR_LEAF] check-fail +//@ [TVOS_PAUTHLR_LEAF] needs-llvm-components: aarch64 +//@ [VISIONOS_PACRET] compile-flags: --target=arm64e-apple-visionos -Zbranch-protection=pac-ret +//@ [VISIONOS_PACRET] check-fail +//@ [VISIONOS_PACRET] needs-llvm-components: aarch64 +//@ [VISIONOS_PAUTHLR_LEAF] compile-flags: --target=arm64e-apple-visionos -Zbranch-protection=pac-ret,pc,leaf +//@ [VISIONOS_PAUTHLR_LEAF] check-fail +//@ [VISIONOS_PAUTHLR_LEAF] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +#[lang = "pointee_sized"] +pub trait PointeeSized {} + +#[lang = "meta_sized"] +pub trait MetaSized: PointeeSized {} + +#[lang = "sized"] +pub trait Sized: MetaSized {} + +//~? ERROR `-Zbranch-protection` cannot use `pac-ret` on arm64e Apple targets, which already sign return addresses diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_BOTH.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_BOTH.stderr new file mode 100644 index 0000000000000..8a622a357df98 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_BOTH.stderr @@ -0,0 +1,6 @@ +error: `-Ctarget-feature` cannot disable `paca` on arm64e Apple targets, whose ABI requires pointer authentication + +error: `-Ctarget-feature` cannot disable `pacg` on arm64e Apple targets, whose ABI requires pointer authentication + +error: aborting due to 2 previous errors + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACA.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACA.stderr new file mode 100644 index 0000000000000..569d2f28027b5 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACA.stderr @@ -0,0 +1,4 @@ +error: `-Ctarget-feature` cannot disable `paca` on arm64e Apple targets, whose ABI requires pointer authentication + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACG.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACG.stderr new file mode 100644 index 0000000000000..c32981e61baca --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACG.stderr @@ -0,0 +1,4 @@ +error: `-Ctarget-feature` cannot disable `pacg` on arm64e Apple targets, whose ABI requires pointer authentication + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PAUTH.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PAUTH.stderr new file mode 100644 index 0000000000000..fccd848ab06d4 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PAUTH.stderr @@ -0,0 +1,4 @@ +error: `-Ctarget-feature` cannot disable `pauth` on arm64e Apple targets, whose ABI requires pointer authentication + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.IOS_BOTH.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.IOS_BOTH.stderr new file mode 100644 index 0000000000000..8a622a357df98 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.IOS_BOTH.stderr @@ -0,0 +1,6 @@ +error: `-Ctarget-feature` cannot disable `paca` on arm64e Apple targets, whose ABI requires pointer authentication + +error: `-Ctarget-feature` cannot disable `pacg` on arm64e Apple targets, whose ABI requires pointer authentication + +error: aborting due to 2 previous errors + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.TVOS_BOTH.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.TVOS_BOTH.stderr new file mode 100644 index 0000000000000..8a622a357df98 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.TVOS_BOTH.stderr @@ -0,0 +1,6 @@ +error: `-Ctarget-feature` cannot disable `paca` on arm64e Apple targets, whose ABI requires pointer authentication + +error: `-Ctarget-feature` cannot disable `pacg` on arm64e Apple targets, whose ABI requires pointer authentication + +error: aborting due to 2 previous errors + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.VISIONOS_BOTH.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.VISIONOS_BOTH.stderr new file mode 100644 index 0000000000000..8a622a357df98 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.VISIONOS_BOTH.stderr @@ -0,0 +1,6 @@ +error: `-Ctarget-feature` cannot disable `paca` on arm64e Apple targets, whose ABI requires pointer authentication + +error: `-Ctarget-feature` cannot disable `pacg` on arm64e Apple targets, whose ABI requires pointer authentication + +error: aborting due to 2 previous errors + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs new file mode 100644 index 0000000000000..035145284fc6e --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs @@ -0,0 +1,43 @@ +// ignore-tidy-file-linelength +// `-Ctarget-feature` cannot turn pointer authentication off on arm64e Apple targets, under +// Rust's feature names or LLVM's. +//@ ignore-backends: gcc +//@ revisions: DARWIN_PACA DARWIN_PACG DARWIN_PAUTH DARWIN_BOTH IOS_BOTH TVOS_BOTH VISIONOS_BOTH +//@ [DARWIN_PACA] compile-flags: --target=arm64e-apple-darwin -Ctarget-feature=-paca +//@ [DARWIN_PACA] check-fail +//@ [DARWIN_PACA] needs-llvm-components: aarch64 +//@ [DARWIN_PACG] compile-flags: --target=arm64e-apple-darwin -Ctarget-feature=-pacg +//@ [DARWIN_PACG] check-fail +//@ [DARWIN_PACG] needs-llvm-components: aarch64 +//@ [DARWIN_PAUTH] compile-flags: --target=arm64e-apple-darwin -Ctarget-feature=-pauth +//@ [DARWIN_PAUTH] check-fail +//@ [DARWIN_PAUTH] needs-llvm-components: aarch64 +//@ [DARWIN_BOTH] compile-flags: --target=arm64e-apple-darwin -Ctarget-feature=-paca,-pacg +//@ [DARWIN_BOTH] check-fail +//@ [DARWIN_BOTH] needs-llvm-components: aarch64 +//@ [IOS_BOTH] compile-flags: --target=arm64e-apple-ios -Ctarget-feature=-paca,-pacg +//@ [IOS_BOTH] check-fail +//@ [IOS_BOTH] needs-llvm-components: aarch64 +//@ [TVOS_BOTH] compile-flags: --target=arm64e-apple-tvos -Ctarget-feature=-paca,-pacg +//@ [TVOS_BOTH] check-fail +//@ [TVOS_BOTH] needs-llvm-components: aarch64 +//@ [VISIONOS_BOTH] compile-flags: --target=arm64e-apple-visionos -Ctarget-feature=-paca,-pacg +//@ [VISIONOS_BOTH] check-fail +//@ [VISIONOS_BOTH] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +#[lang = "pointee_sized"] +pub trait PointeeSized {} + +#[lang = "meta_sized"] +pub trait MetaSized: PointeeSized {} + +#[lang = "sized"] +pub trait Sized: MetaSized {} + +//[DARWIN_PACA,DARWIN_BOTH,IOS_BOTH,TVOS_BOTH,VISIONOS_BOTH]~? ERROR `-Ctarget-feature` cannot disable `paca` on arm64e Apple targets, whose ABI requires pointer authentication +//[DARWIN_PACG,DARWIN_BOTH,IOS_BOTH,TVOS_BOTH,VISIONOS_BOTH]~? ERROR `-Ctarget-feature` cannot disable `pacg` on arm64e Apple targets, whose ABI requires pointer authentication +//[DARWIN_PAUTH]~? ERROR `-Ctarget-feature` cannot disable `pauth` on arm64e Apple targets, whose ABI requires pointer authentication diff --git a/tests/ui/pointer_authentication/arm64e_apple_fixed_pointer_authentication.rs b/tests/ui/pointer_authentication/arm64e_apple_fixed_pointer_authentication.rs new file mode 100644 index 0000000000000..ab273adb367a3 --- /dev/null +++ b/tests/ui/pointer_authentication/arm64e_apple_fixed_pointer_authentication.rs @@ -0,0 +1,14 @@ +// ignore-tidy-file-linelength +// The arm64e ABI fixes pointer authentication on Apple targets, so `-Z pointer-authentication` is +// an error there rather than an option that is quietly ignored. +//@ ignore-backends: gcc +//@ check-fail +//@ needs-llvm-components: aarch64 +//@ compile-flags: -Zpointer-authentication=-calls --crate-type=lib --target arm64e-apple-darwin + +#![feature(no_core)] +#![no_std] +#![no_main] +#![no_core] + +//~? ERROR `-Z pointer-authentication` cannot change pointer authentication for target arm64e-apple-darwin, whose ABI fixes it diff --git a/tests/ui/pointer_authentication/arm64e_apple_fixed_pointer_authentication.stderr b/tests/ui/pointer_authentication/arm64e_apple_fixed_pointer_authentication.stderr new file mode 100644 index 0000000000000..4635fd039cced --- /dev/null +++ b/tests/ui/pointer_authentication/arm64e_apple_fixed_pointer_authentication.stderr @@ -0,0 +1,4 @@ +error: `-Z pointer-authentication` cannot change pointer authentication for target arm64e-apple-darwin, whose ABI fixes it + +error: aborting due to 1 previous error +