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.
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