(Semi-) Abitrary Code Execution using `-main-is` flag
I doubt this is a serious security concern, as anyone who is able to pass flags to ghc(i) or run(ghc|haskell) already has enough access to execute abitrary code, but this is a security flaw nonetheless.
Upon investigating #23996 (closed) I saw that ghci merely uses the value passed to main-is
, wraps it in Control.Monad.void
and executes the code.
ghci -main-is "putStrLn \"this could be malicious code\""
GHCi, version 9.4.8: https://www.haskell.org/ghc/ :? for help
ghci> main = putStrLn "this is normal code"
ghci> :main
this could be malicious code
This works the same with runhaskell, as that simply calls ghci under the hood:
λx. echo "main = putStrLn \":)\"" | runhaskell -main-is "print (-1)"
-1
I was even more surprised that this (kind of) works with ghc itself. While ghc does not allow abitrary code (it tries to resolve the entire string as an identifier), you can use -main-is "undefined"
which ... yeah that shouldn't be possible.
Additionally you get "weird" errors (that make sense if you know what's happening)
λx. touch Foo.hs
λx. ghc -main-is "undefined" Foo.hs
[1 of 2] Compiling Main ( Foo.hs, Foo.o )
[2 of 2] Linking Foo
λx. ./Foo
Foo: Prelude.undefined
CallStack (from HasCallStack):
undefined, called at Foo.hs:1:1 in main:Main
λx. ghc -main-is "putStrLn" Foo.hs
[1 of 2] Compiling Main ( Foo.hs, Foo.o ) [Flags changed]
Foo.hs:1:1: error:
• Couldn't match expected type: IO t0
with actual type: String -> IO ()
• Probable cause: ‘putStrLn’ is applied to too few arguments
In the expression: putStrLn
When checking the type of the main IO action ‘putStrLn’
λx.
At first I thought the patch I had envisioned for #23996 (closed) would fix this, but that this is happening to ghc itself surprised me and should probably be fixed.
This may be more difficult than it might first seem, as it is possible to specify imported identifiers as being the main method. Maybe it would be sufficient to check if the string is a valid identifier and is not from prelude.