securityhaskell

why ghc does not support PIE and Full RelRO in linux?


I am Haskell beginner. I wrote a simple code and compiled it with ghc. As a result of checking the compiled binary with the command checksec, PIE was not applied and RelRO was set to partial.

λ vm-ubuntu22 projects → checksec --file haskell_code
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   haskell_code

I want to compiled binary to have PIE and Full ReloRO. I've also tried -fPIE option, but it hasn't changed. How can I apply it?

FYI I'm using version ghc 9.6.6 and the operating system is ubuntu 22.04.


Solution

  • Short answer: Try:

    ghc -fforce-recomp -O2 -fPIE -pie -dynamic -dynload deploy -optl=-znow -optl=-s Hello.hs
    

    to generate a PIE-enabled, full RELO, dynamically linked executable with stripped symbols. IMPORTANT: The resulting binary will require you to ensure the all needed Haskell libraries are installed in the appropriate system-wide library directories. See the longer answer below for details.

    The -fforce-recomp isn't strictly needed, but if you're doing some experimentation, some flag changes won't automatically trigger a recompilation, so you can get misleading results.

    Longer answer: You should be able to generate a PIE-enable executable with -fPIE -pie -dynamic:

    $ ghc -O2 -fPIE -pie -dynamic Hello.hs
    Loaded package environment from /u/buhr/.ghc/x86_64-linux-9.6.4/environments/default
    [1 of 2] Compiling Main             ( Hello.hs, Hello.o )
    [2 of 2] Linking Hello
    

    Here, -pie is necessary to link the executable in a position-independent manner, and -dynamic is necessary to force GHC to use the shared library versions of compiled packages and the runtime, which have been compiled with -fPIC.

    On my machine, this gives a binary that's PIE enabled but with a new RW-RUNPATH warning:

    $ checksec --file=Hello
    RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols     FORTIFY Fortified   Fortifiable FILE
    Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   RW-RUNPATH   56 Symbols    No    0       0       Hello
    

    This is because the -dynamic flag has the effect of dynamically linking to all the Haskell libraries. This can actually be a big plus, since it results in a much smaller binary than the usual static linking:

    $ ls -l Hello
    -rwxr-xr-x 1 buhr buhr 128104 Oct 23 11:49 Hello
    

    But, the default GHC library search strategy is -dynload sysdep which adds a RUNPATH. (In this example, my RUNPATH has writable directories, since my Haskell libraries are all installed in .ghcup and .cabal under my homedir.) You can suppress the addition of the RUNPATH by adding an extra -dynload deploy flag to the GHC command:

    $ ghc -fforce-recomp -O2 -fPIE -pie -dynamic -dynload deploy Hello.hs
    

    (This is an example where -fforce-recomp is needed, if you already compiled without the -dynload setting, since GHC won't relink in this case.) Now, this executable will pass the RUNPATH test, but you'll need to make sure that all the libraries have been installed in "/lib" or "/usr/lib" or some other system-wide location.

    If you instead want a statically linked executable that's still position independent, you can try dropping the -dynamic flag:

    $ ghc -O2 -fPIE -pie Hello.hs
    

    If you're lucky, your distribution might have all the static Haskell runtime and built-in packages already built with -fPIC, and it'll just work. On my Debian machine, it generates a whole bunch of link errors about needing to recompile libraries with -fPIC. If you run into this problem, I think your only option is to recompile GHC from scratch with some patches to build the static versions of the runtime and built-in packages with -fPIC. See this blog post for a walkthrough of the process.

    To get a full RELRO executable, you need to pass the -znow option to the linker. The following seems to work:

    $ ghc -O2 -optl=-znow Hello.hs
    

    You could also strip the executable to get rid of the symbols, either using a separate strip command or just by passing -s as a linker option.

    So, the following GHC command line:

    ghc -fforce-recomp -O2 -fPIE -pie -dynamic -dynload deploy -optl=-znow -optl=-s Hello.hs
    

    ought to give you an executable that passes everything except the canary and fortify tests:

    $ ghc -fforce-recomp -O2 -fPIE -pie -dynamic -dynload deploy -optl=-znow -optl=-s Hello.hs
    Loaded package environment from /u/buhr/.ghc/x86_64-linux-9.6.4/environments/default
    [1 of 2] Compiling Main             ( Hello.hs, Hello.o )
    [2 of 2] Linking Hello [Objects changed]
    $ checksec --file=Hello
    RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH  Symbols     FORTIFY Fortified   Fortifiable FILE
    Full RELRO      No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols    No    0       0       Hello