I am a bit shocked about this discover. If I don't have execution permission on a file, say, foo... I can still execute it with:
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 ./foo
(ld-linux-x86-64.so.2
is a symlink to ld-2.31.so
).
Since I don't think I know what's really happening in that command, since I don't really know what ld-2.31.so
is, can you explain why can that execute files? Is this what happens in background that we don't see, when we just do ./foo
? What is ld-2.31.so ? What's its role? Are there any other "tricks" and libraries(I don't know their purpose, I am a newbie actually) that do similar jobs?
Is this what happens in background that we don't see, when we just do ./foo ?
Kind of. What happens is that the kernel
./foo
has execute permissionsmmap
s the file according to the program headers (readelf -Wl foo
) in itPT_INTERP
segment, reads its contents (usually /lib64/ld-linux-x86-64.so.2
on Linux x86_64
systems)mmap
s the interpreter according to its own program headersfoo
s _start
.Because of this startup sequence, for a dynamically linked ./foo
, 10s of thousands of instructions execute before _start
.
This is also how it's possible to have ./foo
executable, but for attempts to run it produce "No such file or directory". You can try this for yourself:
gcc main.c -Wl,--dynamic-linker=/foo/bar/baz
ls -l a.out
./a.out
I can still execute it with:
/path/to/ld.so ./foo
In this case, you are skipping a few steps from the "normal" execution path.
From the kernel perspective, ld.so
is a statically linked binary, and it is executable, so the kernel is happy to mmap
its segments and transfer control to it.
And ld.so
itself recognizes that it's been invoked in "unusual" way with command line arguments, so it proceeds to perform additional mappings of ./foo
s segments (which would have already been performed by the kernel otherwise). In order to perform the mappings, ld.so
just needs a file; it doesn't care about permissions on that file.
This mode of operation proved very useful for e.g. testing new versions of ld.so
itself.
It also allows you to run a binary with missing interpreter (the a.out
above): /lib64/ld-linux-x86-64.so.2 ./a.out
would work when ./a.out
doesn't.