I was reading https://zapps.app/technology/ yesterday and it ocurred to me that the shim process seemed extraneous.
ld.so is a ELF dynamic object, so is a dynamically linked executable:
$ readelf -h /lib64/ld-linux-x86-64.so.2 | grep Type
Type: DYN (Shared object file)
$ readelf -h /bin/bash | grep Type
Type: DYN (Position-Independent Executable file)
Both are able to run, because there's an entry point:
$ readelf -h /lib64/ld-linux-x86-64.so.2 | grep Entry
Entry point address: 0x1b190
$ readelf -h /bin/bash | grep Entry
Entry point address: 0x67020
But ld.so does not have an INTERP ELF segment:
$ readelf -lW /lib64/ld-linux-x86-64.so.2 | grep INTERP -A 1
$ readelf -lW /bin/bash | grep INTERP -A 1
INTERP 0x000318 0x0000000000000318 0x0000000000000318 0x00001c 0x00001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
This means, to start the execution of a dynamically linked object, the INTERP segment is optional as far as the kernel is concerned. If the object has no INTERP, the kernel will load it at a randomized address and jump to the entry point.
... which got me thinking, if the entry point is reachable, we can do in userspace the what used to be the kernel's job of loading ld.so into the address space, right? All we need to do is fix up the auxiliary vector to make ld.so believe nothing is out of the ordinary.
So here is the POC:
I chose to write it in assembly because I was too lazy to mess with compiler options. I just want something that will work regardless of compiler. Rewriting most of it in C is on my TODO. Only the initial stage of the entry point and then the jump to ld.so have to be assembly, but the rest should be convertible to C.
It also turned out that libc will require an INTERP segment when it's called (otherwise this assertion will occur: https://elixir.bootlin.com/glibc/glibc-2.36.9000/source/elf/rtld.c#L1291) so I patched it in at runtime. This unforunately meant that I have that page as RWX. I can re-mprotect it with the initial permissions but it needs a bit more code to find the right segment for the right permission bits.
Why do we care? I think one of the potential use cases where a jumploader could fail is in the case of binfmt-misc, or even setuids (though I'm not sure about the security of my approach either yet). In the case of binfmt-misc, the kernel would pass information about what's being executed to the binary via auxiliary vector in O mode. This would be lost upon a re-exec. And for setuids, invoking ld.so directly breaks setuid executable (though I'm not sure if this use case is something to support). And besides, saving an exec sounds cool since exec is very expensive process.
Anyways, here's a demo of the POC:
zhuyifei1999@zhuyifei1999-ThinkPad-P14s-Gen-2a ~/zapps-poc $ make
gcc -shared -o absolute/lib.so lib.c
gcc -fPIC -o absolute/exe -L absolute -l:lib.so -Wl,-rpath=absolute exe.c
gcc -o tmp/strip_interp strip_interp.c
gcc -shared -o relative/lib.so lib.c
cp $(gcc --print-file-name=ld-linux-x86-64.so.2) relative/ld-linux-x86-64.so.2
cp $(gcc --print-file-name=libc.so.6) relative/libc.so.6
# gcc -o relative/exe -L relative -l:lib.so -Wl,-rpath=XORIGIN -Wl,-e_zapps_start -Wl,-Ild-linux-x86-64.so.2 tmp/zapps-crt0.o exe.c
gcc -o relative/exe -L relative -l:lib.so -Wl,-rpath=XORIGIN -Wl,-e_zapps_start tmp/zapps-crt0.o exe.c
# gcc -o relative/exe -L relative -l:lib.so -Wl,-rpath=XORIGIN -Wl,-e_zapps_start -Wl,--no-dynamic-linker tmp/zapps-crt0.o exe.c
# gcc -o relative/exe -L relative -l:lib.so -Wl,-rpath=XORIGIN exe.c
sed -i '0,/XORIGIN/{s/XORIGIN/$ORIGIN/}' relative/exe
tmp/strip_interp relative/exe
The absolute only have minimal rpath just to show what the output looks like for a normal executable:
zhuyifei1999@zhuyifei1999-ThinkPad-P14s-Gen-2a ~/zapps-poc $ absolute/exe
static_constructor in lib invoked
static_constructor in exe invoked
main invoked with arguments:
argv[0] = absolute/exe
foo invoked
contents of /proc/self/maps:
55eb90655000-55eb90656000 r--p 00000000 00:2b 55391978 /home/zhuyifei1999/zapps-poc/absolute/exe
55eb90656000-55eb90657000 r-xp 00001000 00:2b 55391978 /home/zhuyifei1999/zapps-poc/absolute/exe
55eb90657000-55eb90658000 r--p 00002000 00:2b 55391978 /home/zhuyifei1999/zapps-poc/absolute/exe
55eb90658000-55eb90659000 r--p 00002000 00:2b 55391978 /home/zhuyifei1999/zapps-poc/absolute/exe
55eb90659000-55eb9065a000 rw-p 00003000 00:2b 55391978 /home/zhuyifei1999/zapps-poc/absolute/exe
55eb91c90000-55eb91cb1000 rw-p 00000000 00:00 0 [heap]
7f8b8765f000-7f8b87662000 rw-p 00000000 00:00 0
7f8b87662000-7f8b87684000 r--p 00000000 00:20 23718226 /lib64/libc.so.6
7f8b87684000-7f8b877d9000 r-xp 00022000 00:20 23718226 /lib64/libc.so.6
7f8b877d9000-7f8b8782b000 r--p 00177000 00:20 23718226 /lib64/libc.so.6
7f8b8782b000-7f8b8782f000 r--p 001c9000 00:20 23718226 /lib64/libc.so.6
7f8b8782f000-7f8b87831000 rw-p 001cd000 00:20 23718226 /lib64/libc.so.6
7f8b87831000-7f8b87839000 rw-p 00000000 00:00 0
7f8b8785d000-7f8b8785e000 r--p 00000000 00:2b 55391977 /home/zhuyifei1999/zapps-poc/absolute/lib.so
7f8b8785e000-7f8b8785f000 r-xp 00001000 00:2b 55391977 /home/zhuyifei1999/zapps-poc/absolute/lib.so
7f8b8785f000-7f8b87860000 r--p 00002000 00:2b 55391977 /home/zhuyifei1999/zapps-poc/absolute/lib.so
7f8b87860000-7f8b87861000 r--p 00002000 00:2b 55391977 /home/zhuyifei1999/zapps-poc/absolute/lib.so
7f8b87861000-7f8b87862000 rw-p 00003000 00:2b 55391977 /home/zhuyifei1999/zapps-poc/absolute/lib.so
7f8b87862000-7f8b87864000 rw-p 00000000 00:00 0
7f8b87864000-7f8b87865000 r--p 00000000 00:20 23718239 /lib64/ld-linux-x86-64.so.2
7f8b87865000-7f8b8788b000 r-xp 00001000 00:20 23718239 /lib64/ld-linux-x86-64.so.2
7f8b8788b000-7f8b87895000 r--p 00027000 00:20 23718239 /lib64/ld-linux-x86-64.so.2
7f8b87895000-7f8b87897000 r--p 00031000 00:20 23718239 /lib64/ld-linux-x86-64.so.2
7f8b87897000-7f8b87899000 rw-p 00033000 00:20 23718239 /lib64/ld-linux-x86-64.so.2
7fffacba6000-7fffacbc8000 rw-p 00000000 00:00 0 [stack]
7fffacbf6000-7fffacbfa000 r--p 00000000 00:00 0 [vvar]
7fffacbfa000-7fffacbfc000 r-xp 00000000 00:00 0 [vdso]
It cannot be relocated:
zhuyifei1999@zhuyifei1999-ThinkPad-P14s-Gen-2a ~/zapps-poc $ mv absolute foo
zhuyifei1999@zhuyifei1999-ThinkPad-P14s-Gen-2a ~/zapps-poc $ foo/exe
foo/exe: error while loading shared libraries: lib.so: cannot open shared object file: No such file or directory
And this is the relocatable:
zhuyifei1999@zhuyifei1999-ThinkPad-P14s-Gen-2a ~/zapps-poc $ relative/exe
static_constructor in lib invoked
static_constructor in exe invoked
main invoked with arguments:
argv[0] = relative/exe
foo invoked
contents of /proc/self/maps:
555555c0e000-555555c2f000 rw-p 00000000 00:00 0 [heap]
7f4453e1b000-7f4453e1e000 rw-p 00000000 00:00 0
7f4453e1e000-7f4453e40000 r--p 00000000 00:2b 55356014 /home/zhuyifei1999/zapps-poc/relative/libc.so.6
7f4453e40000-7f4453f95000 r-xp 00022000 00:2b 55356014 /home/zhuyifei1999/zapps-poc/relative/libc.so.6
7f4453f95000-7f4453fe7000 r--p 00177000 00:2b 55356014 /home/zhuyifei1999/zapps-poc/relative/libc.so.6
7f4453fe7000-7f4453feb000 r--p 001c9000 00:2b 55356014 /home/zhuyifei1999/zapps-poc/relative/libc.so.6
7f4453feb000-7f4453fed000 rw-p 001cd000 00:2b 55356014 /home/zhuyifei1999/zapps-poc/relative/libc.so.6
7f4453fed000-7f4453ff5000 rw-p 00000000 00:00 0
7f4453ff5000-7f4453ff6000 r--p 00000000 00:2b 55391980 /home/zhuyifei1999/zapps-poc/relative/lib.so
7f4453ff6000-7f4453ff7000 r-xp 00001000 00:2b 55391980 /home/zhuyifei1999/zapps-poc/relative/lib.so
7f4453ff7000-7f4453ff8000 r--p 00002000 00:2b 55391980 /home/zhuyifei1999/zapps-poc/relative/lib.so
7f4453ff8000-7f4453ff9000 r--p 00002000 00:2b 55391980 /home/zhuyifei1999/zapps-poc/relative/lib.so
7f4453ff9000-7f4453ffa000 rw-p 00003000 00:2b 55391980 /home/zhuyifei1999/zapps-poc/relative/lib.so
7f4453ffa000-7f4453ffc000 rw-p 00000000 00:00 0
7f4453ffc000-7f4453ffd000 r--p 00000000 00:2b 55356013 /home/zhuyifei1999/zapps-poc/relative/ld-linux-x86-64.so.2
7f4453ffd000-7f4454023000 r-xp 00001000 00:2b 55356013 /home/zhuyifei1999/zapps-poc/relative/ld-linux-x86-64.so.2
7f4454023000-7f445402d000 r--p 00027000 00:2b 55356013 /home/zhuyifei1999/zapps-poc/relative/ld-linux-x86-64.so.2
7f445402d000-7f445402f000 r--p 00031000 00:2b 55356013 /home/zhuyifei1999/zapps-poc/relative/ld-linux-x86-64.so.2
7f445402f000-7f4454031000 rw-p 00033000 00:2b 55356013 /home/zhuyifei1999/zapps-poc/relative/ld-linux-x86-64.so.2
7f4454031000-7f4454032000 rwxp 00000000 00:2b 55391982 /home/zhuyifei1999/zapps-poc/relative/exe
7f4454032000-7f4454033000 r-xp 00001000 00:2b 55391982 /home/zhuyifei1999/zapps-poc/relative/exe
7f4454033000-7f4454034000 r--p 00002000 00:2b 55391982 /home/zhuyifei1999/zapps-poc/relative/exe
7f4454034000-7f4454035000 r--p 00002000 00:2b 55391982 /home/zhuyifei1999/zapps-poc/relative/exe
7f4454035000-7f4454036000 rw-p 00003000 00:2b 55391982 /home/zhuyifei1999/zapps-poc/relative/exe
7fff889ac000-7fff889ce000 rw-p 00000000 00:00 0 [stack]
7fff889e7000-7fff889eb000 r--p 00000000 00:00 0 [vvar]
7fff889eb000-7fff889ed000 r-xp 00000000 00:00 0 [vdso]
... which is relocatable like other zapps:
zhuyifei1999@zhuyifei1999-ThinkPad-P14s-Gen-2a ~/zapps-poc $ mv relative bar
zhuyifei1999@zhuyifei1999-ThinkPad-P14s-Gen-2a ~/zapps-poc $ bar/exe
static_constructor in lib invoked
static_constructor in exe invoked
main invoked with arguments:
argv[0] = bar/exe
foo invoked
contents of /proc/self/maps:
5555565b1000-5555565d2000 rw-p 00000000 00:00 0 [heap]
7fe1993ee000-7fe1993f1000 rw-p 00000000 00:00 0
7fe1993f1000-7fe199413000 r--p 00000000 00:2b 55356014 /home/zhuyifei1999/zapps-poc/bar/libc.so.6
7fe199413000-7fe199568000 r-xp 00022000 00:2b 55356014 /home/zhuyifei1999/zapps-poc/bar/libc.so.6
7fe199568000-7fe1995ba000 r--p 00177000 00:2b 55356014 /home/zhuyifei1999/zapps-poc/bar/libc.so.6
7fe1995ba000-7fe1995be000 r--p 001c9000 00:2b 55356014 /home/zhuyifei1999/zapps-poc/bar/libc.so.6
7fe1995be000-7fe1995c0000 rw-p 001cd000 00:2b 55356014 /home/zhuyifei1999/zapps-poc/bar/libc.so.6
7fe1995c0000-7fe1995c8000 rw-p 00000000 00:00 0
7fe1995c8000-7fe1995c9000 r--p 00000000 00:2b 55391980 /home/zhuyifei1999/zapps-poc/bar/lib.so
7fe1995c9000-7fe1995ca000 r-xp 00001000 00:2b 55391980 /home/zhuyifei1999/zapps-poc/bar/lib.so
7fe1995ca000-7fe1995cb000 r--p 00002000 00:2b 55391980 /home/zhuyifei1999/zapps-poc/bar/lib.so
7fe1995cb000-7fe1995cc000 r--p 00002000 00:2b 55391980 /home/zhuyifei1999/zapps-poc/bar/lib.so
7fe1995cc000-7fe1995cd000 rw-p 00003000 00:2b 55391980 /home/zhuyifei1999/zapps-poc/bar/lib.so
7fe1995cd000-7fe1995cf000 rw-p 00000000 00:00 0
7fe1995cf000-7fe1995d0000 r--p 00000000 00:2b 55356013 /home/zhuyifei1999/zapps-poc/bar/ld-linux-x86-64.so.2
7fe1995d0000-7fe1995f6000 r-xp 00001000 00:2b 55356013 /home/zhuyifei1999/zapps-poc/bar/ld-linux-x86-64.so.2
7fe1995f6000-7fe199600000 r--p 00027000 00:2b 55356013 /home/zhuyifei1999/zapps-poc/bar/ld-linux-x86-64.so.2
7fe199600000-7fe199602000 r--p 00031000 00:2b 55356013 /home/zhuyifei1999/zapps-poc/bar/ld-linux-x86-64.so.2
7fe199602000-7fe199604000 rw-p 00033000 00:2b 55356013 /home/zhuyifei1999/zapps-poc/bar/ld-linux-x86-64.so.2
7fe199604000-7fe199605000 rwxp 00000000 00:2b 55391982 /home/zhuyifei1999/zapps-poc/bar/exe
7fe199605000-7fe199606000 r-xp 00001000 00:2b 55391982 /home/zhuyifei1999/zapps-poc/bar/exe
7fe199606000-7fe199607000 r--p 00002000 00:2b 55391982 /home/zhuyifei1999/zapps-poc/bar/exe
7fe199607000-7fe199608000 r--p 00002000 00:2b 55391982 /home/zhuyifei1999/zapps-poc/bar/exe
7fe199608000-7fe199609000 rw-p 00003000 00:2b 55391982 /home/zhuyifei1999/zapps-poc/bar/exe
7ffd3ea59000-7ffd3ea7b000 rw-p 00000000 00:00 0 [stack]
7ffd3eab4000-7ffd3eab8000 r--p 00000000 00:00 0 [vvar]
7ffd3eab8000-7ffd3eaba000 r-xp 00000000 00:00 0 [vdso]
Wdyt?
I was reading https://zapps.app/technology/ yesterday and it ocurred to me that the shim process seemed extraneous.
ld.sois a ELF dynamic object, so is a dynamically linked executable:Both are able to run, because there's an entry point:
But
ld.sodoes not have an INTERP ELF segment:This means, to start the execution of a dynamically linked object, the INTERP segment is optional as far as the kernel is concerned. If the object has no INTERP, the kernel will load it at a randomized address and jump to the entry point.
... which got me thinking, if the entry point is reachable, we can do in userspace the what used to be the kernel's job of loading
ld.sointo the address space, right? All we need to do is fix up the auxiliary vector to makeld.sobelieve nothing is out of the ordinary.So here is the POC:
I chose to write it in assembly because I was too lazy to mess with compiler options. I just want something that will work regardless of compiler. Rewriting most of it in C is on my TODO. Only the initial stage of the entry point and then the jump to
ld.sohave to be assembly, but the rest should be convertible to C.It also turned out that libc will require an INTERP segment when it's called (otherwise this assertion will occur: https://elixir.bootlin.com/glibc/glibc-2.36.9000/source/elf/rtld.c#L1291) so I patched it in at runtime. This unforunately meant that I have that page as RWX. I can re-mprotect it with the initial permissions but it needs a bit more code to find the right segment for the right permission bits.
Why do we care? I think one of the potential use cases where a jumploader could fail is in the case of binfmt-misc, or even setuids (though I'm not sure about the security of my approach either yet). In the case of binfmt-misc, the kernel would pass information about what's being executed to the binary via auxiliary vector in
Omode. This would be lost upon a re-exec. And for setuids, invokingld.sodirectly breaks setuid executable (though I'm not sure if this use case is something to support). And besides, saving an exec sounds cool since exec is very expensive process.Anyways, here's a demo of the POC:
The
absoluteonly have minimal rpath just to show what the output looks like for a normal executable:It cannot be relocated:
And this is the relocatable:
... which is relocatable like other zapps:
Wdyt?