riscv-collab / riscv-gnu-toolchain

GNU toolchain for RISC-V, including GCC
Other
3.58k stars 1.17k forks source link

Execute-only .text section #668

Closed fwsGonzo closed 4 years ago

fwsGonzo commented 4 years ago

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

fwsGonzo commented 4 years ago

Are linker scripts too inflexible for this?

jim-wilson commented 4 years ago

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.

jim-wilson commented 4 years ago

you should be able to do this by modifying the default linker script

fwsGonzo commented 4 years ago

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.

fwsGonzo commented 4 years ago

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
}
fwsGonzo commented 4 years ago

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.

fwsGonzo commented 4 years ago

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.

jim-wilson commented 4 years ago

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.