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

[pauthabielf64] Allow relocatable objects to request PLT GOT signing #294

Open
kovdan01 opened this issue Oct 28, 2024 · 7 comments
Open

Comments

@kovdan01
Copy link

A pauth-enabled environment comes with a set of pauth features enabled by default. One of the features such environment might include is PLT GOT signing. Currently, it requires a special option being passed to a linker (-z pac-plt in case of lld). If the linker is invoked from the compiler driver, this option can be automatically applied to linker's command line arguments when a pauth-enabled environment including PLT GOT signing is requested - say, when compiling with -target aarch64-unknown-linux-mypauthenv. However, if we first compile to object files, and then invoke the linker separately (which is a very common approach), the user has to pass -z pac-plt to the linker manually, which makes usage of pauth-enabled environment not transparent.

To keep transparency in this case, we need to somehow request PLT GOT signing without passing -z pac-plt manually. So, we need to somehow encode this request into relocatable objects files passed to the linker as an input. Essentially, it would be ideal to use smth like dynamic array tags but for static linking - unfortunately, it looks like that no direct alternative is available. Possible implementations include the following.

  1. Defining a new GNU property like GNU_PROPERTY_AARCH64_FORCE_PAC_PLT. This looks as the most applicable way to me. The new property might hold a single integer value with non-zero meaning signed PLT GOT requested and zero
    meaning usual unsigned PLT GOT should be used.

    Pros: The GNU property section is already pretty widely used for various AArch64 info (see GNU_PROPERTY_AARCH64_FEATURE_1_AND and GNU_PROPERTY_AARCH64_FEATURE_PAUTH properties). Using a separate property
    for this particular purpose makes it easy for every pauth-enabled environment to adopt this way of requesting PLT GOT signing.

    Cons: need to decide how to handle files w/o the property, for example, objects compiled from raw assembly files which were not modified to include this property. I think the same way as for missing PAuth core info might be
    used - see -z pauth-report=none|warning|error: [lld][AArch64][ELF][PAC] Support AUTH relocations and AUTH ELF marking llvm/llvm-project#72714

  2. Using build attributes AArch64 build attributes specification #230. Once merged, this looks like the most suitable option for encoding PLT GOT signing request.

  3. Using PAuth core info and encoding the PLT GOT signing request in (platform,version) tuple.

    Pros: no need for new entities, PAuth core info is already defined and its initial support is already present in llvm.

    Cons: meaning of the version value is defined by platform, and for each new platform, if it wants to support PLT GOT signing, we'll need to teach linker about meaning of particular (platform,version) value. I think that
    a linker should treat (platform,version) tuple as an opaque value with no meaning other than a) (0,0) - explicitly pauth-disabled; b) different tuples in input files - link error. IMHO, this makes using PAuth core info discouraged for the desired purpose.

  4. Using new AUTH relocation types like R_AARCH64_CALL26 -> R_AARCH64_AUTH_CALL26, R_AARCH64_CONDBR19 -> R_AARCH64_AUTH_CONDBR19, R_AARCH64_JUMP26 -> R_AARCH64_AUTH_JUMP26, R_AARCH64_TSTBR14 -> R_AARCH64_AUTH_TSTBR14.

    Pros: ?

    Cons: requires many new AUTH relocation types (at least 4, as far as I see from lld's sources). This will also cause some pain with handling mix of signed/unsigned PLT-generating relocations in one object file. Since PLT GOT signing is a per-link-job decision, putting the signing request into individual relocations looks as a bad decision - having one place encoding the request in each input object file looks better.

@kovdan01
Copy link
Author

Tagging @smithp35 @asl

@kovdan01
Copy link
Author

kovdan01 commented Nov 7, 2024

@smithp35 Would be glad to see your opinion on the subject

@smithp35
Copy link
Contributor

smithp35 commented Nov 7, 2024

Apologies for the delay in responding. I missed this one in my mailbox.

I would expect -z pac-plt to be passed by the compiler driver to the linker given the signing schema in the triple. For example the aarch64-linux-android triple at some point passes -z max-page-size=0x1000 to LLD. Unless the user is calling lld directly, which I wouldn't expect to be common, I'd expect them not to notice the option.

If it is necessary then I think that inferring it from the existing signing schema is my preferred option. My expectation is that .got and .got.plt signing are going to be rare because of RELRO. I don't think that this likely to need factoring out for other signing schemas. It also helps that the signing schema will be consistent across all relocatable object files.

I'm hesitant to add a new GNU property for this. The existing properties propagate into executable/shared-object and while I don't think it would do any harm, I think the choice of signed-got wouldn't as we've already got a dynamic tag DT_AARCH64_PAC_PLT for that. I also think we'd need a new property with a different combination rule. For example:
GNU_PROPERTY_AARCH64_FEATURE_1_OR with a feature bit for GNU_PROPERTY_AARCH64_FEATURE_1_PACPLT . That would enable -z pac-plt if at least one object has the bit set. Or as you suggest something like GNU_PROPERTY_AARCH64_PAC_PLT that takes an integer which could scale to multiple signing schemas.

BuildAttributes (which are being implemented for GCS in GNU and LLD, I think for LLVM 20, but if not LLVM 21) would be a better match as BuildAttributes don't propagate to ELF Executables and Shared Libraries. I'm still not convinced that it is necessary to factor this part out, but if we did, then build attributes would be the way to go.

From discussions on Discord, I think there was some call for an alternative signing schema than what is currently implemented, which would mean a change to the interface to tell the static linker (and dynamic linker) what the signing schema is. I've put some thoughts on that below.

[1] Thoughts on specifying an alternative .got.plt signing schema.

Signing of the .got.plt is a contract between the static linker and the dynamic linker, a decision which can be made independently of the signing schema of the relocatable object. It is also possible for a dynamic linker to support a choice of signing schemas on a per executable/shared-object basis. I expect that we would need to use dynamic tags to do that. We've already got DT_AARCH64_PAC_PLT which is currently set to 0 by lld and GNU ld if the PLT is signed using the glibc signing schema that didn't get merged in glibc. I think that could be extended to use non zero values to communicate alternative signing schema used to the dynamic linker rather than adding an additional dynamic tag.

If a flexible choice is needed I personally favour a linker command-line option, perhaps altering -z pac-plt to accept an optional argument. For example -z pac-plt=<option> with = none, entries,entry-resolveror something similar, with-z pac-pltwithout an option mapping toentries`.

@asl
Copy link

asl commented Nov 7, 2024

I would expect -z pac-plt to be passed by the compiler driver to the linker given the signing schema in the triple. For example the aarch64-linux-android triple at some point passes -z max-page-size=0x1000 to LLD. Unless the user is calling lld directly, which I wouldn't expect to be common, I'd expect them not to notice the option.

Well, the main motivation for this is to ensure that the option is not forgotten even if linker is invoked directly. There are some cases when manual linking is the default way of doing things :) It is very easy to forget the option and in this case it could went unnoticed.

If it is necessary then I think that inferring it from the existing signing schema is my preferred option

Well, I'd rather not. We do not have generic way to encode a signing scheme. The pauthtest is just an example and different platforms might use different signing schemes (and way to encode them). So it would be great to have a common solution that is not tied to a particular encoding, etc.

@smithp35
Copy link
Contributor

smithp35 commented Nov 7, 2024

Are you able to elaborate on the use cases? Outside of assembly language only where I don't want the language runtime being included I'd almost always use clang as the linker driver. There's also the possibility of upstream having the same reaction as me and asking why not just use the compiler driver?

I can understand the desire to avoid a downstream patch for the proprietary platform, as that would be needed if we were inferring the signed .got.plt from a specific (signing-schema, version).

Main reason I'm trying to avoid adding a linker flag to attributes/properties that could be passed by the driver is to avoid setting a precedent for properties/build-attributes for any arbitrary linker option that is passed by the compiler driver but someone might forget [1].

If its' not urgent this may be better talked over in the next PAuthABI sync-up. To summarise I think we need:

  • Is there going to be widespread use of lld without a linker driver by users, or just a handful of experts?
  • How many signing schema's for the .got.plt are needed? If there's more than one then that will affect the design of the property, it may also mean changes to DT_AARCH64_PAC_PLT and -z pac-plt command line option.
  • How urgent is this? If we do need an additional way to give the signed .got.plt then I'd prefer to just have build attributes. There are people starting on the implementation now targeting LLVM 20.

Not strictly this issue but related:

  • The signed GOT relocation codes are in the "private space" reserved for experiments. If these are going to be stable then I should move the signed GOT out of the appendix and use stable codes in the main ABI.

[1] For example -z now and -z max-page-size on Android are always passed by the compiler driver.

@asl
Copy link

asl commented Nov 18, 2024

Are you able to elaborate on the use cases? Outside of assembly language only where I don't want the language runtime being included I'd almost always use clang as the linker driver. There's also the possibility of upstream having the same reaction as me and asking why not just use the compiler driver?

I guess my reasoning is: there are multiple parts of PAuth ABI. Some of those are explicitly controlled by a frontend (e.g. all signing / auths emitted by frontend), some are essentially a backend-only option (PAC-RET) but could be even used outside PAuth ABI. Here we are having another part, but it is implemented entirely in the linker. It is enabled by a linker option, yes. However, if the linker option is missed by any reason (e.g. linking is done explicitly due to some distributed build system, etc., I definitely saw lots of cases in such scenarios), then noone would even notice until suddenly it will be found that no PLT GOT signing was produced. And as it happens, it will be found too late :)

So, ideally I though of having a single switch, that would enforce that all different components are in order. This certainly would require a propagation of some kind of metadata down the compilation pipeline. Another issue is that the spec allows lots of customization, so this ought to be customizable as well. Just passing linker option in the compiler driver looks fragile enough for the reasons I outlined above.

In this sense, the build attribute looks like a proper solution to me, we can ensure the required metadata flow from the frontend down to the linker. I cannot think of why we'd need some other signing schemes for PLT GOT.

@smithp35
Copy link
Contributor

Based on what I've heard so far. I think the build-attribute would look something like:

Tag_PAuth_PACPLT (=3)
     0 The user does not permit the static Iinker to sign the .got.plt, or no information available.
     1 The user requires the .got.plt entries to be signed except the first entry used by the lazy resolver.
     2 The user requires all .got.plt to be signed.

0 matches the default linker behaviour.
1 matches the current -z pacplt (with lazy resolver slots assumed read-only)
2 matches an alternative pacplt that also signs the lazy resolver slots.

I've not gone into the specific signing schema. I think we could accomodate new ones by adding additional values.

The recommended combination rules for the attribute are somewhat complex as they will depend on additional linker command-line options.

For example: in the presence of -znow then 1 and 2 are the same so it would be a bit harsh to error if there were a clash. Without -znow then 1 and 2 are mutually exclusive. Otherwise the way it is written then 0, 1 and 2 are mutually exclusive.

An alternative:

Tag_PAuth_PACPLT (=3)
     0 The user does not use the .got.plt, or no information available.
     1 The user requires the .got.plt to not be signed.
     2 The user requires the .got.plt entries to be signed except the first entry used by the lazy resolver.
     3 The user requires all .got.plt to be signed.

This would permit mixing of 0 and (1, 2, 3) however if the signing schema attributes are also present then no information available shouldn't be possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants