aboutsummaryrefslogtreecommitdiff
path: root/elfstartup.md
diff options
context:
space:
mode:
Diffstat (limited to 'elfstartup.md')
-rw-r--r--elfstartup.md84
1 files changed, 84 insertions, 0 deletions
diff --git a/elfstartup.md b/elfstartup.md
new file mode 100644
index 0000000..37ee0db
--- /dev/null
+++ b/elfstartup.md
@@ -0,0 +1,84 @@
+# Running dynamically linked programs on Linux
+
+This section provides a high level overview of the startup process of a
+dynamically linked program on Linux.
+
+When using the `exec` system call to run a program, the kernel maps it into
+memory and tries to determine what kind of executable it is by looking at
+the magic number. Based on the type of executable, some data structures are
+parsed and the program is run. For a statically linked ELF program, this means
+fiddling the entry point address out of the header and jumping to it (with
+a kernel to user space transition of course).
+
+The kernel also supports exec-ing programs that require an interpreter to be
+run. This mechanism is also used for implementing dynamically linked programs.
+
+Similar to how scripts have an interpreter field (`#!/bin/sh`
+or `#!/usr/bin/perl`), ELF files can also have an interpreter section. For
+dynamically linked ELF executables, the compiler sets the interpreter field
+to the loader (`ld-linux.so` or similar).
+
+The `ld-linux.so` loader is typically provided by the `libc` implementation
+(i.e. Musl, glibc, ...) then maps the actual executable into memory
+with `mmap(2)`, parses the dynamic section and mmaps the used libraries
+(possibly recursively since libraries may need other libraries), does
+some relocations if applicable and then jumps to the entry point address.
+
+The kernel itself actually has no concept of libraries. Thanks to this
+mechanism, it doesn't have to.
+
+The whole process of using an interpreter is actually done recursively. An
+interpreter can in-turn also have an interpreter. For instance if you exec
+a shell script that starts with `#!/bin/sh`, the kernel detects it to be a
+script (because it starts with `#!`), extracts the interpreter and then
+runs `/bin/sh <script-path>` instead. The kernel then detects that `/bin/sh`
+is an ELF binary (because it starts with `\x7fELF`) and extracts the
+interpreter field, which is set to `/lib/ld-linux.so`. So now the kernel
+tries to run `/lib/ld-linux.so /bin/sh <script-path>`. The `ld-linux.so` has
+no interpreter field set, so the kernel maps it into memory, extracts the
+entry point address and runs it.
+
+If `/bin/sh` were statically linked, the last step would be missing and the
+kernel would start executing right there. It should also be noted that Linux
+has a hard limit for interpreter recursion depth, typically set to 3 to
+support this exact standard case (script, interpreter, loader).
+
+The entry point of the ELF file that the loader jumps to is of course NOT
+the `main` function of the C program. It points to setup code provided by
+the libc implementation that does some initialization first, such as stack
+setup, getting the argument vector, initializing malloc or whatever other
+internals and then calls the `main` function. When `main` returns, the
+startup code calls the `exit` system call with the return value from `main`.
+
+The startup code is provided by the libc, typically in the form of an object
+file in `/lib`, e.g. `/lib/crt0.o`. The C compiler links executable programs
+against this object file and expects it to have a symbol called `_start`. The
+entry point address of the ELF file is set to the location of `_start` and the
+interpreter is set to the path of the loader.
+
+Finally, somewhere inside the `main` function of `/bin/sh` is run, it opens
+the file it has been provided on the command line and starts interpreting your
+shell script.
+
+## Take Away Message
+
+In summary, the compiler needs to know the following things about the libc:
+ - The path to the loader for dynamically linked programs.
+ - The path to the startup object code it needs to link against.
+ - The path of the libc itself to link against.
+
+If you try to run a program and you get the possibly most useless error
+message `no such file or directory`, it could have the following reasons:
+ - The kernel couldn't find the program you are trying to run.
+ - The kernel couldn't find the interpreter set by the program.
+ - The kernel couldn't find the interpreter of the interpreter.
+ - The loader couldn't find a library used by either your program, the
+ interpreter of your program, or another library that it loaded.
+
+So if you see that error message, don't panic, try to figure out the root
+cause by walking through this checklist. You can use the `ldd` program (that
+is provided by the libc) to display libraries that the loader would try to
+load. But **NEVER** use `ldd` on untrusted programs. Typical implementations
+of ldd try to execute the interpreter with special options to collect
+dependencies. An attacker could set this to something other than `ld-linux.so`
+and gain code execution.