Skip to content

Consider applying -Wl,-z,relro or -Wl,-z,relro,-z,now by default #29877

Closed

Description

@brson

These are some options that Debian applies, and that @gus wants to apply to all Rust code. Generally, Rust likes to harden things by default when there are no obvious disadvantages. Is it feasible that we might just use these by default?

Activity

  1. added
    A-linkageArea: linking into static, shared libraries and binaries
    on Nov 16, 2015
  2. gus commented on Nov 16, 2015

    @gus

    Sounds cool, but I didn't ask for that, @brson :)

    Cheers,
    Gus

  3. brson commented on Nov 17, 2015

    @brson
    ContributorAuthor

    Oops, wrong @gus. Sorry! Strong beard though.

  4. gus commented on Nov 17, 2015

    @gus

    ;)

    On Mon, Nov 16, 2015, 20:55 Brian Anderson notifications@github.com wrote:

    Oops, wrong @gus https://github.com/gus. Sorry! Strong beard though.

    —
    Reply to this email directly or view it on GitHub
    #29877 (comment).

  5. genodeftest commented on Dec 8, 2015

    @genodeftest
    Contributor

    There is something similiar on Fedora: Change: Harden all packages

  6. nagisa commented on May 4, 2017

    @nagisa
    Member

    Downside is that not every alternative linker out there might support the flags.

  7. added
    C-enhancementCategory: An issue proposing an enhancement or a PR with one.
    on May 4, 2017
  8. brson commented on May 4, 2017

    @brson
    ContributorAuthor

    @alexcrichton do recall if we've done anything here? I don't see 'relro' in the source.

  9. alexcrichton commented on May 4, 2017

    @alexcrichton
    Member

    No I don't believe we ever implemented this, but would be good to do so!

  10. alexcrichton commented on May 4, 2017

    @alexcrichton
    Member

    er didn't mean to close

  11. cuviper commented on May 4, 2017

    @cuviper
    Member

    FWIW, I also apply -Clink-arg=-Wl,-z,relro,-z,now in Fedora.

  12. 4 remaining items

  13. OfekShilon commented on Sep 28, 2023

    @OfekShilon

    Why not build with -no-plt instead? Seems a bit funny to erect the entire PLT apparatus to support lazy loading and then populate it at program startup.

  14. bjorn3 commented on Sep 28, 2023

    @bjorn3
    Member

    Using a GOT instead will cause every function call to require a load of the address, even if the target is guaranteed to resolve in the same DSO. When emitting relocations for a PLT, the linker can replace PLT calls with direct relative calls in case of non-exported symbols AFAIK which is almost all calls in an average Rust program.

  15. OfekShilon commented on Sep 29, 2023

    @OfekShilon

    @bjorn3 I think PLT relocations must resolve to GOT slots anyway.

  16. bjorn3 commented on Sep 29, 2023

    @bjorn3
    Member

    A PLT relocation for an external symbol is implemented as pc-relative call to a stub function which loads from the GOT. For an internal symbol this can trivially be relaxed to a pc-relative call to the actual function. The stub function is generated by the linker, not the compiler, so the linker has full control over it. For a GOT relocation however the load is part of the code emitted by the compiler and can't be replaced.

  17. OfekShilon commented on Sep 29, 2023

    @OfekShilon

    @bjorn3 internal symbols with "default" visibility are still interposable, and the compiler/linker are forced* to place a GOT entry for them - to account for the case that another binary preempts them.

    *clang sometimes optimizes these calls anyway, but in doing so it knowingly breaks the ABI.

  18. bjorn3 commented on Sep 29, 2023

    @bjorn3
    Member

    Rust uses -fno-semantic-interposition just like clang defaults to. Even if we didn't there wouldn't be any way to interpose mangled rust symbols without depending on implementation details. Symbol names are not stable and depend on the exact rustc version as well as whatever cargo passes as -Cmetadata argument. Only #[no_mangle] symbols can safely be interposed, and those are never internal anyway. Also how can internal symbols be interposed in the first place? They don't end up in .dynsym and as such the dynamic linker should know nothing about them. Rustc is very careful to avoid leaking any rust symbol from cdylibs and bins.

  19. OfekShilon commented on Sep 29, 2023

    @OfekShilon

    @bjorn3 This is an empirical matter. This toy test:

    $ cat main.cpp
    void exefunc() {}
    
    int main() {
        exefunc();
        return 0;
    }
    $
    $ g++ -o exe_gcc main.cpp
    $ readelf --sections exe_clang | grep got$ readelf --sections exe_gcc | grep got
      [13] .plt.got          PROGBITS         0000000000001030  00001030
      [22] .got              PROGBITS         0000000000003fc0  00002fc0
    $ 
    $ g++ -o exe_gcc main.cpp -fno-plt
    $ readelf --sections exe_gcc | grep got
      [13] .plt.got          PROGBITS         0000000000001030  00001030
      [22] .got              PROGBITS         0000000000003fc0  00002fc0
    $ 
    $ clang++ -o exe_clang main.cpp
    $ readelf --sections exe_clang | grep got
      [19] .got              PROGBITS         0000000000403ff0  00002ff0
      [20] .got.plt          PROGBITS         0000000000404000  00003000
    $ 
    $ clang++ -o exe_clang main.cpp -fno-plt
    $ readelf --sections exe_clang | grep got
      [19] .got              PROGBITS         0000000000403ff0  00002ff0
      [20] .got.plt          PROGBITS         0000000000404000  00003000
    

    Seems to show that with or without PLT, in both gcc and clang, the GOT is used. Am I missing something?

  20. OfekShilon commented on Sep 29, 2023

    @OfekShilon

    Rust uses -fno-semantic-interposition just like clang defaults to.

    Actually this switch just avoids the interposition infrastructure for calls into the same translation unit.

    Even if we didn't there wouldn't be any way to interpose mangled rust symbols without depending on implementation details. Symbol names are not stable and depend on the exact rustc version as well as whatever cargo passes as -Cmetadata argument. Only #[no_mangle] symbols can safely be interposed, and those are never internal anyway.

    Indeed, the GOT is redundant for rust - and largely for C++ too. Yet this is not the point in question: The point is that PLT is redundant. The PLT isn't there to facilitate interposition, it is there to facilitate lazy binding. And this merge shows that rust (rightfully) avoids lazy binding anyway, so why pay the extra overhead?

    Also how can internal symbols be interposed in the first place? They don't end up in .dynsym and as such the dynamic linker should know nothing about them.

    You're right, internal symbols have hidden visibility and cannot be interposed. Again, this is a side discussion and not the point of my comment.

  21. bjorn3 commented on Sep 30, 2023

    @bjorn3
    Member

    We don't yet know when compiling object files if non-local rust symbols will be looked up inside the same DSO or outside. Only once we start linking do we know this. This rules out using non-PLT PC-relative relocations for position independent executables. So PLT or GOT relocations are the only option. GOT relocations can't be relaxed to regular PC-relative calls, so PLT relocations it is.

  22. yugr commented on Jul 18, 2025

    @yugr

    Using a GOT instead will cause every function call to require a load of the address, even if the target is guaranteed to resolve in the same DSO. When emitting relocations for a PLT, the linker can replace PLT calls with direct relative calls in case of non-exported symbols AFAIK which is almost all calls in an average Rust program.

    Are you sure? In this example

    $ cat file1.c
    void foo() {}
    $ cat file2.c
    extern void foo();
    
    __attribute__((visibility("default")))
    void bar() { foo(); }
    

    toolchain is able to generate direct call for both cases:

    $ gcc -O2 -fPIC -shared -fvisibility=hidden file1.c file2.c
    $ objdump -d a.out
    ...
    0000000000001110 <bar>:
        1110:       31 c0                   xor    %eax,%eax
        1112:       e9 e9 ff ff ff          jmpq   1100 <foo>
    
    
    $ gcc -O2 -fPIC -shared -fvisibility=hidden -fno-plt file1.c file2.c
    $ objdump -d a.out
    ...
    0000000000001110 <bar>:
        1110:       31 c0                   xor    %eax,%eax
        1112:       e9 e9 ff ff ff          jmpq   1100 <foo>
        1117:       90                      nop
    

    The -fno-plt case is handled through linker relaxation (works only for X86, with ld, gold and lld). More details are available in MaskRay's blog.

    Relaxed case has small size overhead (1 byte for each call) but given that -fno-plt can result in significant savings (I got 10% for Clang with -DBUILD_SHARED_LIBS=ON few years ago, YMMV) this may be acceptable.

  23. yugr commented on Jul 19, 2025

    @yugr

    Why not build with -no-plt instead? Seems a bit funny to erect the entire PLT apparatus to support lazy loading and then populate it at program startup.

    Turns out -fno-plt is enabled by default in Rust on Linux and Android now.

  24. bjorn3 commented on Jul 25, 2025

    @bjorn3
    Member

    The -fno-plt case is handled through linker relaxation (works only for X86, with ld, gold and lld). More details are available in MaskRay's blog.

    I can't find any details about the linker relaxation on that page.

  25. yugr commented on Jul 25, 2025

    @yugr

    The -fno-plt case is handled through linker relaxation (works only for X86, with ld, gold and lld). More details are available in MaskRay's blog.

    I can't find any details about the linker relaxation on that page.

    However, if the target is non-preemptible, i.e. the target is resolved to the same component, this is actually a pessimization.
    The linker (GNU ld, gold, and ld.lld) can mitigate the loss by rewriting the 6 bytes to addr32 call foo.
    The downside is the one-byte-longer code sequence.
    
  26. bjorn3 commented on Jul 25, 2025

    @bjorn3
    Member

    Just disabling the PLT wouldn't enable that relaxation, right? There would need to be some relocation applied to tell thet linker that the relaxation is allowed (the linker isn't allowed to do arbitrary rewrites. that can easily break code). I can't find any details about which relocation or under what exact conditions LLVM will emit them.

  27. yugr commented on Jul 25, 2025

    @yugr

    Yes, relaxations are done by linker based on relocations provided by compiler.

    In this particular case under -fno-plt compiler will emit a R_X86_64_GOTPCRELX relocation which linker will then use to relax

    call *foo@GOTPCREL(%rip)
    

    to

    nop
    call foo
    

    I think this is the relevant code in GNU ld.

    Please note that our discussion is probly irrelevant by now because plt_by_default=false for most amd64 linux platforms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-linkageArea: linking into static, shared libraries and binariesC-enhancementCategory: An issue proposing an enhancement or a PR with one.P-lowLow priority

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions