Closed fwsGonzo closed 4 years ago
Are linker scripts too inflexible for this?
readelf -l will show you program headers, and there are flag bits RWE for read write and execute. Normally read-only data is put in the text segment, that would have to change. A debugger needs read access to code if you want to show disassembly of the executing code. libunwind doesn't read code, it only uses the unwind info. Otherwise, you would have to try it and see what breaks.
you should be able to do this by modifying the default linker script
Thanks, I am in the process of doing that now. I am having problems isolating text from rodata though, and while the linker accepts my NOREAD attribute on .text, it doesn't seem to honor it:
.text NOREAD :
{
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(SORT(.text.sorted.*))
*(.text .stub .text.* .gnu.linkonce.t.*)
}
. = ALIGN(0x1000);
/* Read-only sections, *not* merged into text segment: */
.init :
{
KEEP (*(SORT_NONE(.init)))
}
--->
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x000000000011f000 0x000000000011f000
0x0000000000030158 0x0000000000030158 R E 0x1000
LOAD 0x0000000000030158 0x0000000000150158 0x0000000000150158
0x0000000000008992 0x0000000000009290 RW 0x1000
Meanwhile the sections seems to be aligned, at least:
[ 1] .text PROGBITS 0000000000120000 00001000
0000000000028c78 0000000000000000 AX 0 0 4
[ 2] .rodata PROGBITS 0000000000149000 0002a000
0000000000006158 0000000000000000 A 0 0 16
[ 3] .eh_frame PROGBITS 0000000000150158 00030158
0000000000006c64 0000000000000000 WA 0 0 8
I can remove the read permission for execute when loading the binary, but it would be nice if the binary itself decided A third option would be to simply get the linker script to output 3 sections and then post-process the ELF to remove the read-bit on the executable code.
Having investigated this some more, here are my conclusions: linker scripts don't support directly setting segment permissions, but you can get pretty close, and you can separate the text and rodata sections. ld does not support execute-only segments, nor does it support separating .text and .rodata as a program argument.
However, gold supports it. man gold
shows:
--rosegment
Put read-only non-executable sections in their own segment
I'm guessing that gold does not support elf64lriscv, though. So I am out of luck.
I did notice that ld
does support custom options for specific emulations, and so it should be possible to add a -z option for RISC-V that does separate the two segments, even if it does not change segment permissions. Not necessary for me though.
I did some experiments with MEMORY. but I have not been able to ditch the read bit on execute, however I do have 3 segments now, so I am technically able to continue my work provided I build a program to remove the read-bit on text:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x000000000011f000 0x000000000011f000
0x0000000000029c78 0x0000000000029c78 R E 0x1000
LOAD 0x000000000002a000 0x0000000010000000 0x0000000010000000
0x0000000000006158 0x0000000000006158 R 0x1000
LOAD 0x0000000000031000 0x0000000020000000 0x0000000020000000
0x0000000000008992 0x0000000000009290 RW 0x1000
from adding these regions:
MEMORY
{
progmem (x) : ORIGIN = 0x120000, LENGTH = 500M
rom (r) : ORIGIN = 0x10000000, LENGTH = 1000M
ram (rw) : ORIGIN = 0x20000000, LENGTH = 2000M
}
I tested this method in my emulator. I just ditched the execute pages completely (and ran using instruction translation cache). The programs are running as expected. Execute-only works on RISC-V!
There are other challenges, like I am still not able to remove the read-bit from execute segment. I had to modify the linker script a bit as it's placing lots of data around .text, such as .init, which is just an array of function pointers, and is usually well inside .rodata.
I'll keep this open until I can figure out a way to get rid of the read bit on my .text section. The reason why is because I can't differentiate between the segments. I have code that depends on rodata being linear. If rodata and text is merged, that's fine. But if they aren't how can I detect it when text is RX? It just looks like there are 2 rodata segments, which is fine. I can't, unless I guess that RX really means X because there are 3 segments. I need a way to detect if the text segment is really execute-only. Otherwise I am writing broken code.
The linker script I am using now. It's not much, but it works: https://gist.github.com/fwsGonzo/9184d9352ef37bce0ad03e4cef0c0f13
To remove the read-bit from the execute segment I used this chperm code I found. It had some bugs and annoyances, but they are largely fixed now. It really only needs to support 32-bit ELFs as well: https://gist.github.com/fwsGonzo/e3dbcd9097953e7e113fd3f3ada04316
This should help any future people who are looking into how to make unreadable execute segments. Should be useful for most architectures. Cheers.
You might try discussing this on the FSF Binutils mailing list. The fundamental issues are not RISC-V specific, and the vast majority of binutils developers won't see anything posted here.
Would it be possible to have execute-only code as a feature... somewhere in the toolchain? How would that work and which program would be responsible for supporting such a feature? Looking at readelf the tool doesn't really tell you if a section is readable or not - even though the ELF format does have a bit for that permission.
What do you think are the pros and cons of this feature? Afaik RISC-V doesn't really need to read the .text segment outside of executing the machine code. Maybe libunwind needs to read it, or is the .eh_frame information rich enough?
You can find some light information here: https://linuxplumbersconf.org/event/4/contributions/283/attachments/357/588/Touch_but_dont_look__Running_the_kernel_in_execute_only_memory-presented.pdf
-gonzo