PIC kernel still contains absolute addresses in GOT and vtables

Hello I am working on an OS for fun and have ran into some issues.

My kernel first gets mapped at the address the ELF header wants (0x200000 I think) and then gets remapped to a high address. This works except for when a GOT function or vtable function is called. Both of these contain absolute addresses. Since my final executable is statically linked I don't see any reason why both of these couldn't be offsets. Seems weird that "Position Independent" still contains absolute addresses, but maybe my understanding is lacking"

in my .cargo/config.toml:

rustflags = ["-Crelocation-model=pic"]

My target looks like:

{
    "llvm-target": "x86_64-unknown-none",
    "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
    "arch": "x86_64",
    "target-endian": "little",
    "target-pointer-width": "64",
    "target-c-int-width": "32",
    "os": "none",
    "executables": true,
    "linker-flavor": "ld.lld",
    "linker": "rust-lld",
    "panic-strategy": "abort",
    "disable-redzone": true,
    "features": "-mmx,-sse,+soft-float",
    "static_position_independent_executables": true
}

I have seen that in the rust codegen code it will promote PIC to PIE if all crates are executables, which since I am using other crates (spin, core, alloc, etc) this is not the case. I don't see why this should stop something from being PIE. But also I am not even sure that promoting to PIE is the solution.

Any ideas or suggestions?

Thanks!

You won't be able to avoid absolute adresses within data. That includes vtables. If you want to relocate your kernel and support vtables you will have to write code that performs the necessary relocations at runtime.

How is ASLR done then? Having any absolute address seems to defeat the purpose of position independence, as if I relocate just my code the relative addresses to data wont be valid, but if I relocate both data and code then the absolute addresses become invalid. Am I understanding this wrong?

I also don't know that theres a good way of handling vtable relocations at runtime, they appear to be scattered around the data section.

I could search all data memory for 8byte values that contain an absolute address, this would have the possibility of data that looks like a pointer but isn't.

I could also check the address of a pagefault, but that also seems like a bad idea.

I don't see why its not possible to just make the GOT and vtables relative. it could simply be storing offset from start of .text then add the two together and call. Wouldn't add any overhead really.

Inside data sections all addresses are absolute. Inside code either a relative address to a function or global variable is stored or if it the symbol may come from another dynamic shared object (a single executable or shared library as produced by the linker) a relative address to the GOT that will then be loaded to get the absolute address. (For functions there is also the PLT which contains stubs that either call into the dynamic linker for lazy symbol binding or load from the GOT, but I don't think it is used in your case, so you don't have to worry about it.)

Relative addresses are only used directly inside code to directly reference something. As soon as you get a pointer it is always absolute.

You first need to find the elf header. The bootloader may tell you where to find it. Then you need to lookup the dynamic table using the elf header. The dynamic table contains a list of entries. The DT_JMPREL, DT_REL and DT_RELA entries (some may be missing) point to relocation tables describing exactly where and how relocations need to be performed. The object or crate may be useful for parsing.

See for example how musl libc's ld.so does this:

https://git.musl-libc.org/cgit/musl/tree/ldso/dynlink.c#n1346

https://git.musl-libc.org/cgit/musl/tree/ldso/dynlink.c#n337

fasterthanli.me has a good series describing how elf executables can be loaded: Making our own executable packer - fasterthanli.me Be warned that each individual article is very long with estimated read times of over 40min and sometimes even longer.

Relative to what? Say you have a vtable pointer in one dynamic shared object and pass it to another, how would you get the offset to convert it back to the absolute address? You can't take the offset of your own dynamic shared object as that would result in a different absolute address, but you don't know which dynamic shared object it originated from to get the right offset. It may work when you only got a single dynamic shared object, but that is not a very common, so neither gcc nor llvm support it afaik.

Thanks this pointed me in the right direction, I was already using the header to find the GOT and translate it, when looking at vtables I just assumed they were scattered somewhere but now I see they are in .data.rel.ro which also contains pointers to strings, things labeled DAT_xxxxxx and vtables. It looks to not be just pointers.

DAT_xxxxx

So I will have to figure out what the extra data is, and then once I can tell the difference between pointers and the extra stuff I should just be able to translate. I will read those documents you linked to hopefully figure out what the structure of these are.

Maybe Im thinking too much like C, but if each crate is compiled to some object (I'm thinking similar to .o) and then are linked into one static executable why couldn't you have the addresses be relative to the start of some section. For example looking in ghidra all the .text does get put together, so why couldnt vtable or GOT addrs be relative to start of .text, then strings be relative to .data

Thanks :slight_smile:

Vtables are instinguishable from heap data which has to contain absolute addresses. The GOT is only used when the compiler thought that the referenced symbol may be coming from a different dynamic shared object in which case the addresses have to be absolute.

Because the compiler doesn't know you don't dynamically link. In the past ASLR and other position independent code was only supported for dynamically linked programs and shared objects in which case it simply can't be relative. Somewhat recently support for aslr in statically linked programs has been added to various libc implementations using a crt.o that performs the necessary relocations. The compilers haven't been changed for this. Only a new option to the compiler drivers has been added to link in the right way. Codegen hasn't changed.

I don't think so, vtables are known at compile time, the heap is not introduced until runtime and the data doesn't live within the elf.

Is there any way to disable this in Rust? Can I compile other crates (spin, core, alloc, etc) as static object? Judging by your last paragraph the answer is no.

If the LLVM ir contains a load of a random pointer, it is impossible for LLVM to determine if it is for a stack address, heap address, global variable or vtable and then turn relative into absolute pointers if necessary. In addition LLVM doesn't know if the pointer will ever be casted to a byte array at one point, several bytes loaded at another point and then reassembled back into a pointer at a completely different point, potentially across LTO boundaries in which case there is no location where relative->absolute pointer cast can happen.

AFAIK that is not possible.

So in conclusion there is no way to do a KASLR kernel in rust without translating during runtime?

I disagree with this. Heap addresses will never be in the binary, they are runtime only. So when compiling the final binary it knows all the addresses of vtable funcs, strings, GOT funcs, etc. And could just replace those with relative addresses.

In C you can make kernels that are relocatable, so it is not something the compiler cant do.

also stack addresses are always relative, and so is global variables

I added -Clink-arg=-pie and now I believe it works
I have to change my code up a little bit to test but it looks like its good.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.