Is there an option to GNU ld to omit -dynamic-linker (PT_INTERP) completely?
Asked Answered
M

5

20

I'm experimenting with the concept of pure-static-linked PIE executables on Linux, but running into the problem that the GNU binutils linker insists on adding a PT_INTERP header to the output binary when -pie is used, even when also given -static. Is there any way to inhibit this behavior? That is, is there a way to tell GNU ld specifically not to write certain headers to the output file? Perhaps with a linker script?

(Please don't answer with claims that it won't work; I'm well aware that the program still needs relocation processing - load-address-relative relocations only due to my use of -Bsymbolic - and I have special startup code in place of the standard Scrt1.o to handle this. But I can't get it to be invoked without the dynamic linker already kicking in and doing the work unless hexedit the PT_INTERP header out of the binary.)

Margaritamargarite answered 5/5, 2012 at 20:48 Comment(6)
let me see if I have this straight. you are specifying your own entry point, which is in turn handling some custom relocation, and you don't want the kernel to load in the standard interpreter? what if you're linking against libraries which need an initialization run via .init? in my experience if you want to do something with your executables but there's no way to generate it with some permutation of LDFLAGS then it's not a good idea.Gareri
If I were trying to put this in an application's build system, I would agree with you 100%. For an application to fool around with linker options like this is a horrible hack, and it wouldn't work anyway because it requires all .a libraries to be built as PIC. However, what I'm working on is a new toolchain option intended for use in security-oriented distributions where having the dynamic linker run for setuid binaries is an unacceptable risk. It's a lot easier to deploy if no changes are needed at the ld level, only at the gcc specfile and crt level.Margaritamargarite
it look's like you'll have to write a patch for ld and then argue with them over why it should be applied to trunk. also, that sounds like very interesting work.Gareri
If it's possible with a linker script, that would be less ideal than just a command line option, but much better than internal patching. An answer from somebody well-versed in linker scripts would be much appreciated.Margaritamargarite
Do you need to omit it in the first place? i.e. won't a post-build Make (or whatever you prefer) step to strip the PT_INTERP header suffice?Convivial
Again that would be perfectly acceptable if I were trying to build an application. It's not acceptable since what I'm building is a way to build existing applications. Adding -staticpie or -pie -static or whatnot to LDFLAGS is trivial to use with nearly any build system. Running extra commands on each generated binary is absolutely not possible in a general way.Margaritamargarite
M
12

I think I might have found a solution: simply using -shared instead of -pie to make pie binaries. You need a few extra linker options to patch up the behavior, but it seems to avoid the need for a custom linker script. Or in other words, the -shared linker script is already essentially correct for linking static pie binaries.

If I get it working with this, I'll update the answer with the exact command line I'm using.

Update: It works! Here's the command line:

gcc -shared -static-libgcc -Wl,-static -Wl,-Bsymbolic \
    -nostartfiles -fPIE Zcrt1.s Zcrt2.c /usr/lib/crti.o hello.c /usr/lib/crtn.o

where Zcrt1.s is a modified version of Scrt1.s that calls a function in Zcrt2.c before doing its normal work, and the code in Zcrt2.c processes the aux vector just past the argv and environment arrays to find the DYNAMIC section, then loops over the relocation tables and applies all the relative-type relocations (the only ones that should exist).

Now all of this can (with a little work) be wrapped up into a script or gcc specfile...

Margaritamargarite answered 27/5, 2012 at 23:25 Comment(1)
Neat trick. You're essentially building up to what you need instead of subtracting away. +1 and best of luck.Convivial
P
13

Maybe I'm being naïve, but... woudn't suffice to search for the default linker script, edit it, and remove the line that links in the .interp section?

For example, in my machine the scripts are in /usr/lib/ldscripts and the line in question is interp : { *(.interp) } in the SECTIONS section.

You can dumpp the default script used running the following command:

$ ld --verbose ${YOUR_LD_FLAGS} | \
    gawk 'BEGIN { s = 0 } { if ($0 ~ /^=/) s = !s; else if (s == 1) print; }'

You can modify the gawk script slightly to remove the interp line (or just use grep -v and use that script to link your program.

Packsaddle answered 11/5, 2012 at 3:46 Comment(3)
So far this is probably the best approach. Unfortunately it requires making new versions of the linker scripts for each system, and can't just piggyback onto an existing working linker script. But if I can get the proof of concept working well, perhaps I can get it in upstream binutils.Margaritamargarite
@R.. - Well, my trick of using strace does not work because the script is actually compiled into ld and not read from disk. But you can get it using ld --verbose and a bit of magic in the output (see the updated answer.Packsaddle
From this code, you can make the ld wrapper, just as some do for gcc.Peripeteia
M
12

I think I might have found a solution: simply using -shared instead of -pie to make pie binaries. You need a few extra linker options to patch up the behavior, but it seems to avoid the need for a custom linker script. Or in other words, the -shared linker script is already essentially correct for linking static pie binaries.

If I get it working with this, I'll update the answer with the exact command line I'm using.

Update: It works! Here's the command line:

gcc -shared -static-libgcc -Wl,-static -Wl,-Bsymbolic \
    -nostartfiles -fPIE Zcrt1.s Zcrt2.c /usr/lib/crti.o hello.c /usr/lib/crtn.o

where Zcrt1.s is a modified version of Scrt1.s that calls a function in Zcrt2.c before doing its normal work, and the code in Zcrt2.c processes the aux vector just past the argv and environment arrays to find the DYNAMIC section, then loops over the relocation tables and applies all the relative-type relocations (the only ones that should exist).

Now all of this can (with a little work) be wrapped up into a script or gcc specfile...

Margaritamargarite answered 27/5, 2012 at 23:25 Comment(1)
Neat trick. You're essentially building up to what you need instead of subtracting away. +1 and best of luck.Convivial
C
4

Expanding on my earlier note as this doesn't fit in that puny box (and this is just as an idea or discussion, please do not feel obligated to accept or reward bounty), perhaps the easiest and cleanest way of doing this is to juts add a post-build step to strip the PT_INTERP header from the resulting binary?

Even easier than manually editing the headers and potentially having to shift everything around is to just replace PT_INTERP with PT_NULL. I don't know whether you can find a way of simply patching the file via existing tools (some sort of scriptable hex find and replace) or if you'll have to write a small program to do that. I do know that libbfd (the GNU Binary File Descriptor library) might be your friend in the latter case, as it'll make that entire business a lot easier.

I guess I just don't understand why it's important to have this performed via an ld option. If available, I can see why it would be preferable; but as some (admittedly light) Googling indicates there isn't such a feature, it might be less of a hassle to just do it separately and after-the-fact. (Perhaps adding the flag to ld is easier than scripting the replacement of PT_INTERP with PT_NULL, but convincing the devs to pull it upstream is a different matter.)


Apparently (and please correct me if this is something you've already seen) you can override the behavior of ld with regards to any of the ELF headers in your linker script with the PHDRS command, and using :none to specify that a particular header type should not be included in any segment. I'm not certain of the syntax, but I presume it would look something like this:

PHDRS
{
  headers PT_PHDR PHDRS ;
  interp PT_INTERP ;
  text PT_LOAD FILEHDR PHDRS ;
  data PT_LOAD ;
  dynamic PT_DYNAMIC ;
}

SECTIONS
{
  . = SIZEOF_HEADERS;
  .interp : { } :none
  ...
}

From the ld docs you can override the linker script with --library-path:

--library-path=searchdir

Add path searchdir to the list of paths that ld will search for archive libraries and ld control scripts. You may use this option any number of times. The directories are searched in the order in which they are specified on the command line. Directories specified on the command line are searched before the default directories. All -L options apply to all -l options, regardless of the order in which the options appear. The default set of paths searched (without being specified with `-L') depends on which emulation mode ld is using, and in some cases also on how it was configured. See section Environment Variables. The paths can also be specified in a link script with the SEARCH_DIR command. Directories specified this way are searched at the point in which the linker script appears in the command line.

Also, from the section on Implicit Linker Scripts:

If you specify a linker input file which the linker can not recognize as an object file or an archive file, it will try to read the file as a linker script. If the file can not be parsed as a linker script, the linker will report an error.

Which would seem to imply values in user-defined linker scripts, in contrast with implicitly defined linker scripts, will replace values in the default scripts.

Convivial answered 11/5, 2012 at 3:31 Comment(7)
This is easy to do for experimenting, and in fact it's what I did for experimenting. But it doesn't help at all when the goal is to build a toolchain whereby you can drop some extra options in CFLAGS and LDFLAGS and get any program to build as static pie.Margaritamargarite
Barring an upstream patch of ld to provide support for omitting PT_INTERP (assuming one doesn't already exist, that is), I don't think there's any alternative that can create a generic toolchain..Convivial
I've found some info on overriding the default behavior for any ELF header and updated my answer - is this applicable to you?Convivial
This looks promising. Is there a way to chain this onto existing linker scripts ld is already using without copying/duplicating all the stuff already in them?Margaritamargarite
There's both user-defined and implicitly-defined linker scripts. I think one or the other will do what you need, because I am unsure of what the meaning of "override" in the ld docs actually is. See updated post.Convivial
@R.. - When you add a script in the linker command, it will be appended to the default script. But AFAIK you cannot delete or modify lines from the default script without replacing it altogether (with the -T option).Packsaddle
Thanks for all the help and ideas. I eventually found a solution.Margaritamargarite
P
3

I'am not an expert in GNU ld, but I have found the following information in the documentation:

The special secname `/DISCARD/' may be used to discard input sections. Any sections which are assigned to an output section named `/DISCARD/' are not included in the final link output.

I hope this will help you.

UPDATE:

(This is the first version of the solution, which don't work because INTERP section is dropped along with the header PT_INTERP.)

main.c:

int main(int argc, char **argv)                                                                                                                               
{                                                                                                                                                             
    return 0;                                                                                                                                                 
}

main.x:

SECTIONS {                                                                                                                                                    
    /DISCARD/ : { *(.interp) }                                                                                                                                
}

build command:

$ gcc -nostdlib -pie -static -Wl,-T,main.x main.c
$ readelf -S a.out | grep .interp

build command without option -Wl,-T,main.x:

$ gcc -nostdlib -pie -static main.c 
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000000218
$ readelf -S a.out | grep .interp
  [ 1] .interp           PROGBITS        00000134 000134 000013 00   A  0   0  1

UPDATE 2:

The idea of this solution is that the original section 'INTERP' (. interp in the linker script file) is renamed to .interp1. In other words, the entire contents of the section is placed to the .interp1 section. Therefore, we can safe remove INTERP section (now empty) without fear of losing default linker script settings and hence the header INTERP_PT will be removed too.

SECTIONS {
    .interp1 : { *(.interp); } : NONE
    /DISCARD/ : { *(.interp) }
}

In order to show that the contents of the section INTERP present in the file (as .interp1), but INTERP_PT header removed, I use a combination of readelf + grep.

$ gcc -nostdlib -pie -Wl,-T,main.x main.c
$ readelf -l a.out | grep interp
   00     .note.gnu.build-id .text .interp1 .dynstr .hash .gnu.hash .dynamic .got.plt 
$ readelf -S a.out | grep interp
  [ 3] .interp1          PROGBITS        0000002e 00102e 000013 00   A  0   0  1
Peripeteia answered 17/5, 2012 at 21:31 Comment(6)
I tried using this with .interp, but couldn't get it to work, perhaps since it's created by the default linker script and not the source files. Do you have an idea how it should be written?Margaritamargarite
Well it's -l not -S you need to look at (program headers). You're right that this inhibits the INTERP header, but it also seems to cause a single bogus LOAD header to get generated instead of the correct ones (whole program is loaded read-write) so it seems to be just skipping the underlying system ld script...Margaritamargarite
Now the problem became clearer for me. Could you please try another solution. Also I found a link where it is said that -static and -pie options are incompatible.Peripeteia
Anything with -T is going to throw away the entire default linker script and thus generate broken (or at least highly suboptimal) binaries. And it's INTERP (as in PT_INTERP, the program header), not interp that you want to grep for to check the output. Sections in an executable file are purely annotation/debug information; they are not used whatsoever by the program loader.Margaritamargarite
Have you tried the second solution, or not tried because you do not like the first? It doesn't throw away the entire default linker script. Or if it does, could you, please, describe how? Naturally, you need to build with other options instead of the '-nostdlib.' Options in the example are chosen so that the file was being built and not run. And, how can it be that the sections are not used by program loader? I mean, the section contains code and data. Very strange...Peripeteia
The -T option is documented to cause ld to skip/replace the default linker scripts.Margaritamargarite
D
3

The option -Wl,--no-dynamic-linker solves the issues with binutils 2.26 or later.

Duenas answered 7/3, 2019 at 18:17 Comment(2)
Yes, I wrote the patch that added it. :-) But +1 anyway and welcome to the site.Margaritamargarite
Doesn't work in binutils 2.25, unrecognized option '--no-dynamic-linker'. This option is available only in version 2.26 or later.Anaanabaena

© 2022 - 2024 — McMap. All rights reserved.