Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reserve-x18 target feature for aarch64 #124323

Closed

Commits on Apr 24, 2024

  1. Add reserve-x18 target feature for aarch64

    This PR resolves issue 121970 [1] by adding `reserve-x18` as a target
    feature for the aarch64 platform. Enabling the target feature marks the
    x18 register as reserved so that Rust doesn't use it as a temporary
    register when generating machine code. This means that passing the
    `-Ctarget-feature=+reserve-x18` flag will no longer result in the
    following warning:
    
    warning: unknown feature specified for `-Ctarget-feature`: `reserve-x18`
      |
      = note: it is still passed through to the codegen backend
      = help: consider filing a feature request
    
    Typically you will reserve the x18 register when you want to enable SCS
    (the shadow call stack sanitizer [9]), because it uses x18 to store a
    pointer to the shadow stack. However, it is important to not conflate
    `reserve-x18` with `-Zsanitizer=shadow-call-stack` — the latter depends
    on the former, but you can enable `reserve-x18` without enabling SCS.
    
    # ABI compatibility
    
    One concern that was brought up on issue 121970 [1] is that this flag
    affects the ABI. However, it does not affect the ABI in a way where it
    is a problem to mix code with and without the feature. From the ABI
    spec [2]:
    
    > X18 is the platform register and is reserved for the use of platform
    > ABIs. This is an additional temporary register on platforms that don't
    > assign a special meaning to it.
    
    That is to say, the register is either already reserved (this is the
    case on Android targets), or it is a caller-saved temporary register
    (this is the case on `aarch64-unknown-none`). Changing a register from
    caller-saved temporary register to reserved is not breaking, so
    selectively enabling `reserve-x18` on some compilation targets (or even
    on specific functions) cannot result in UB.
    
    That said, *removing* the `reserve-x18` target feature from a function
    can potentially trigger UB under some circumstances. This is because it
    is UB to link together `-Zsanitizer=shadow-call-stack` code with code
    where x18 is a temporary register. So enabling SCS in a binary requires
    that x18 is reserved globally. However, right now
    `-Zsanitizer=shadow-call-stack` can only be used on targets such as
    Android where x18 is never a temporary register, so this shouldn't be an
    issue for this PR.
    
    # Use in the Linux Kernel
    
    This motivation for this change is use in the Linux Kernel. When
    compiling Rust code for the kernel, the `aarch64-unknown-none` target is
    used, and this is a platform where x18 is a temporary caller-saved
    register by default. I am proposing to add this target feature so that
    the Linux Kernel can make x18 into a reserved register when necessary.
    
    The Linux Kernel has some cases where it needs to reserve x18, but does
    not pass the `-Zsanitizer=shadow-call-stack` flag. This is due to the
    dynamic shadow call stack feature [3], where the Linux Kernel is
    able to choose whether SCS should be enabled at boot. This works by
    having the compiler emit PACIASP/AUTIASP instructions instead of
    SCS_PUSH/SCS_POP. If Linux decides to enable SCS at boot, then it will
    use the unwind tables to find the PACIASP/AUTIASP instructions, and
    modify the machine code at runtime by replacing PACIASP/AUTIASP with
    SCS_PUSH/SCS_POP instructions in all functions.
    
    The transformation from PACIASP/AUTIASP to SCS_PUSH/SCS_POP is only
    valid if the x18 register is reserved globally.
    
    It is also possible to configure Linux to always use SCS. In this case,
    it does so using the `-fsanitize=shadow-call-stack` flag instead.
    
    The Linux Kernel configuration used by Android uses the dynamic shadow
    call stack feature in production, so `reserve-x18` is a prerequisite for
    using Rust in the Linux Kernel on Android.
    
    # Alternatives
    
    I have considered a few different alternatives.
    
    ## Add a `-Cfixed-x18` flag
    
    When compiling C code with clang or gcc, this is configured by passing
    the `-ffixed-x18` flag instead of using the target feature
    functionality. We could mirror that and add our own `-Cfixed-x18` flag
    to rustc. It would have the same effect as passing
    `-Ctarget-feature=+reserve-x18`.
    
    ## Use a different target
    
    The Rust compiler could provide a version of `aarch64-unknown-none`
    where x18 is reserved, and the Linux Kernel build system could switch to
    that target whenever `CONFIG_SHADOW_CALL_STACK` is enabled in the Linux
    build system. However, there are a few disadvantages with using that
    strategy for this kind of flag:
    
    * As the number of flags that are configured in this way increases, the
      number of targets increases exponentially.
    * It complicates the Kernel build system by significantly deviating from
      both clang and gcc on how this can be configured.
    
    My understanding is that the primary reason in favor of using a
    different target is that compiling the standard library yourself is
    unstable, so even if this target feature is added, there is no stable
    way to get a standard library compiled with
    `-Ctarget-feature=+reserve-x18`.
    
    However, as outlined in the abi stability section, there is no issue
    with enabling `reserve-x18` in some crates, but not in the standard
    library.
    
    The Linux Kernel already compiles the standard library manually. Using a
    prebuilt standard library is pretty unlikely to be the way forward for
    many other reasons unrelated to this flag.
    
    ## Use a `target.json` in the kernel
    
    The Linux Kernel is already using a `target.json` file for x86 targets
    due to issue 116852 [4], which is a similar issue with a different
    target feature.
    
        if cfg.has("ARM64") {
            panic!("arm64 uses the builtin rustc aarch64-unknown-none target");
        } else if cfg.has("X86_64") {
            ts.push("arch", "x86_64");
            ts.push(
                "data-layout",
                "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128",
            );
            let mut features = "-3dnow,-3dnowa,-mmx,+soft-float".to_string();
            if cfg.has("MITIGATION_RETPOLINE") {
                features += ",+retpoline-external-thunk";
            }
            ts.push("features", features);
            ts.push("llvm-target", "x86_64-linux-gnu");
            ts.push("target-pointer-width", "64");
    
    However, Linux is trying to move away from `target.json` targets because
    Rust considers `target.json` to be permanently unstable.
    
    # Future possibilities
    
    We could make it possible to use `-Zsanitizer=shadow-call-stack`
    together with `-Ctarget-feature=+reserve-x18` to enable SCS on targets
    where x18 is normally a temporary caller-saved register. This could be
    done similarly to `required_panic_strategy`, which enforces that all
    compilation units have a shared understanding of the panic strategy.
    That is, if `-Zsanitizer=shadow-call-stack` is passed, then fail
    compilation unless
    
    1. the target is one where x18 is always reserved, or
    2. `-Ctarget-feature=+reserve-x18` is passed as an argument to all crates
       in the crate graph.
    
    This lets us avoid adding any compiler flags combinations that trigger
    UB.
    
    # References
    
    1. Discussion in the t-compiler stream on zulip. [5]
    2. Discussion on the Linux Kernel mailing list. [6]
    3. General issue on unrecognized target features. [7]
    4. List of wanted Rust for Linux features. [8]
    
    Link: https://www.github.com/rust-lang/rust/issues/121970 [1]
    Link: https://developer.arm.com/documentation/den0024/a/The-ABI-for-ARM-64-bit-Architecture/Register-use-in-the-AArch64-Procedure-Call-Standard/Parameters-in-general-purpose-registers [2]
    Link: https://lore.kernel.org/all/[email protected]/ [3]
    Link: https://www.github.com/rust-lang/rust/issues/116852 [4]
    Link: https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/-ffixed-x18/near/430864291 [5]
    Link: https://lore.kernel.org/rust-for-linux/[email protected]/ [6]
    Link: https://www.github.com/rust-lang/rust/issues/96472 [7]
    Link: https://www.github.com/Rust-for-Linux/linux/issues/355 [8]
    Link: https://www.github.com/rust-lang/rust/pull/98208 [9]
    Signed-off-by: Alice Ryhl <[email protected]>
    Darksonn committed Apr 24, 2024
    Configuration menu
    Copy the full SHA
    2b15cf6 View commit details
    Browse the repository at this point in the history