Re: [Haskell-cafe] GHC 6.6.1 and SELinux issues

On Tue, Aug 28, 2007 at 08:53:05PM -0600, Stuart Jansen wrote:
On Wed, 2007-08-29 at 00:59 +0400, Alexander Vodomerov wrote:
In what domain do you run GHC?
Sorry about that, should've dug deeper. And here we have the difference:
$ ls -Z /usr/lib/ghc-6.6.1/ghc-6.6.1 -rwxr-xr-x root root system_u:object_r:unconfined_execmem_exec_t /usr/lib/ghc-6.6.1/ghc-6.6.1
This explains that GHC works fine. unconfined_execmem_exec_t gives permission to map memory with PROT_EXEC and PROT_WRITE simultaneously. I've put GHC in unconfined_execmem_t and it started to work fine. But the problem is not in GHC -- it is in programs compiled by GHC. They also require exec/write memory. Only root can grant unconfined_execmem privileges, so simple user can not run binaries compiled by GHC. How do you solve this problem? Does Fedora GHC package has any additional patches? With best regards, Alexander.

Alexander Vodomerov wrote:
I've put GHC in unconfined_execmem_t and it started to work fine. But the problem is not in GHC -- it is in programs compiled by GHC. They also require exec/write memory. Only root can grant unconfined_execmem privileges, so simple user can not run binaries compiled by GHC. How do you solve this problem?
Running "chcon -t unconfined_execmem_exec_t" as root will let you run the binaries, which you probably already knew. The underlying problem is harder to fix: the default SELinux policy doesn't allow PROT_EXEC pages to be mapped with PROT_WRITE, for obvious reasons. The solution is expensive in terms of address space and TLB entries: map the same pages twice, once only with PROT_EXEC, and once only with PROT_WRITE. There's already a Trac ticket filed against this problem, but Simon Marlow marked it as closed because he couldn't test the code he wrote to try to fix it, and nobody stepped in to help out at the time: http://hackage.haskell.org/trac/ghc/ticket/738

On Wed, Aug 29, 2007 at 08:41:12AM -0700, Bryan O'Sullivan wrote:
The underlying problem is harder to fix: the default SELinux policy doesn't allow PROT_EXEC pages to be mapped with PROT_WRITE, for obvious reasons. The solution is expensive in terms of address space and TLB entries: map the same pages twice, once only with PROT_EXEC, and once only with PROT_WRITE. Just for experiment I've removed PROT_EXEC from my_mmap function in rts/MBlock.c and recompiled GHC. The resulting GHC was able to compile itself and my code. Binaries, produced by it worked fine with SELinux. However, another problem related to GHCi ocurred. More details are available at the 738 ticket you mentioned.
So it is not clear if GHC does really need this PROT_EXEC. Can someone familiar with GHC internals answer why PROT_EXEC is used in getMBlocks?
There's already a Trac ticket filed against this problem, but Simon Marlow marked it as closed because he couldn't test the code he wrote to try to fix it, and nobody stepped in to help out at the time: http://hackage.haskell.org/trac/ghc/ticket/738 Yes, I reopened the bug some days ago. I can also provide a shell access to Simon Marlow (or someone else willing to help) on a machine to experiment with. Both x86 and x86_64 boxes are available.
With best regards, Alexander.

On Wed, Aug 29, 2007 at 10:40:41PM +0400, Alexander Vodomerov wrote:
On Wed, Aug 29, 2007 at 08:41:12AM -0700, Bryan O'Sullivan wrote:
The underlying problem is harder to fix: the default SELinux policy doesn't allow PROT_EXEC pages to be mapped with PROT_WRITE, for obvious reasons. The solution is expensive in terms of address space and TLB entries: map the same pages twice, once only with PROT_EXEC, and once only with PROT_WRITE. Just for experiment I've removed PROT_EXEC from my_mmap function in rts/MBlock.c and recompiled GHC. The resulting GHC was able to compile itself and my code. Binaries, produced by it worked fine with SELinux. However, another problem related to GHCi ocurred. More details are available at the 738 ticket you mentioned.
So it is not clear if GHC does really need this PROT_EXEC. Can someone familiar with GHC internals answer why PROT_EXEC is used in getMBlocks?
It's not possible to correctly implement 'foreign import ccall "wrapper"' without self-modifying code on any mainstream computer architecture. Does this program work on your no-PROT_EXEC ghc? : {-# OPTIONS_GHC -ffi #-} import Foreign foreign import ccall "wrapper" wrap :: IO () -> IO (FunPtr (IO ())) foreign import ccall "dynamic" call :: FunPtr (IO ()) -> IO () main = call =<< wrap (print "hi!") Stefan

On Wed, Aug 29, 2007 at 01:03:56PM -0700, Stefan O'Rear wrote:
So it is not clear if GHC does really need this PROT_EXEC. Can someone familiar with GHC internals answer why PROT_EXEC is used in getMBlocks?
It's not possible to correctly implement 'foreign import ccall "wrapper"' without self-modifying code on any mainstream computer architecture. Does this program work on your no-PROT_EXEC ghc? :
{-# OPTIONS_GHC -ffi #-} import Foreign
foreign import ccall "wrapper" wrap :: IO () -> IO (FunPtr (IO ())) foreign import ccall "dynamic" call :: FunPtr (IO ()) -> IO ()
main = call =<< wrap (print "hi!")
Thanks, that is exactly what I asked for. This program compiles with my no-PROT_EXEC ghc, but doesn't work: $ ghc -o 1 1.hs $ ./1 1: internal error: makeExecutable: failed to protect 0x0x2b115597e000 (GHC version 6.6.1 for x86_64_unknown_linux) Please report this as a GHC bug: http://www.haskell.org/ghc/reportabug Aborted $ The errors comes from rts/posix/OSMem.c, function setExecutable which tries to mprotect block of memory with PROT_EXEC and PROT_WRITE. What is so special about wrapper and dynamic functions? I've never used Haskell FFI, but I've heavily used Python-C interface and Ocaml-C interface, calling both C from Ocaml and Ocaml from C and even Ocaml from Python via C and etc. It all worked fine without any runtime code generation or self-modifying code. Can you please give some ideas how self-modifying code can be used in FFI implementation? With best regards, Alexander.

Alexander Vodomerov wrote:
On Wed, Aug 29, 2007 at 01:03:56PM -0700, Stefan O'Rear wrote: [snip] What is so special about wrapper and dynamic functions?
Can you please give some ideas how self-modifying code can be used in FFI implementation?
It's not self-modifying code really, it's dynamically generated code. Here is a small complete example for illustration:
V.hs >>> import Foreign import Foreign.C
foreign import ccall "wrapper" mkCallback :: (CInt -> CInt) -> IO (FunPtr (CInt -> CInt)) foreign import ccall "v.h foo" c_foo :: FunPtr (CInt -> CInt) -> IO CInt main = do add_21 <- mkCallback (+21) c_foo add_21 >>= print <<<
v.c >>> #include "v.h" int foo(callback_t fun) { return fun(fun(0)); } <<<
v.h >>> typedef int (* callback_t)(int); int foo(callback_t fun); <<<
(compile with ghc -ffi V.hs v.c) This program takes a function that adds 21 to a CInt, wraps it into a C function pointer of type callback_t, and then calls this function from the C side two times; it prints 42. In particular, the wrapper mkCallback takes any *Haskell* function of type CInt -> CInt and returns a *C* function pointer that represents the same function. Note that this function receives no additional context in its arguments. This is convenient but it means that each call to mkCallback has to return a different function pointer, so it is necessary to generate a small piece of code dynamically to implement it. HTH, Bertram

On Mon, Sep 03, 2007 at 02:56:52AM +0200, Bertram Felgenhauer wrote:
Here is a small complete example for illustration: Thank you for detailed explanation! It is very helpful!
Note that this function receives no additional context in its arguments. This is convenient but it means that each call to mkCallback has to return a different function pointer, so it is necessary to generate a small piece of code dynamically to implement it. This seems to be very different from usual FFI.
In Ocaml and Python a special function is used that takes a function and an arguments. For example: value caml_callback(value closure, value arg); http://caml.inria.fr/pub/docs/manual-ocaml/manual032.html#htoc233 PyObject* PyObject_CallObject(PyObject *func, PyObject *args) http://docs.python.org/api/object.html Why not use the same convention in Haskell? Anyway, have you any ideas about making this work under SELinux? It seems that dynamic code generation is used in many programs and there are some tricks to make it work. See for example http://people.redhat.com/drepper/selinux-mem.html section "Example code to avoid execmem violations" Is this possible to use this technique in GHC? With best regards, Alexander.

Hello Alexander, Monday, September 3, 2007, 11:46:56 AM, you wrote:
In Ocaml and Python a special function is used that takes a function and an arguments. For example:
value caml_callback(value closure, value arg); PyObject* PyObject_CallObject(PyObject *func, PyObject *args)
both uses dynamic typing while Haskell wrappers are statically typed (and Haskell just doesn't supported any analog of "value"/PyObject at time of Haskell98) -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

Bryan O'Sullivan wrote:
Alexander Vodomerov wrote:
I've put GHC in unconfined_execmem_t and it started to work fine. But the problem is not in GHC -- it is in programs compiled by GHC. They also require exec/write memory. Only root can grant unconfined_execmem privileges, so simple user can not run binaries compiled by GHC. How do you solve this problem?
Running "chcon -t unconfined_execmem_exec_t" as root will let you run the binaries, which you probably already knew.
The underlying problem is harder to fix: the default SELinux policy doesn't allow PROT_EXEC pages to be mapped with PROT_WRITE, for obvious reasons. The solution is expensive in terms of address space and TLB entries: map the same pages twice, once only with PROT_EXEC, and once only with PROT_WRITE.
There's already a Trac ticket filed against this problem, but Simon Marlow marked it as closed because he couldn't test the code he wrote to try to fix it, and nobody stepped in to help out at the time: http://hackage.haskell.org/trac/ghc/ticket/738
IIRC, what I did was work around execheap, not execmem (and similar problems with "Data Execution Prevention" on Windows). There aren't any uncommitted patches. Does anyone know how the dynamic linker works? Does it map the pages writable first, then mprotect them executable/read-only after relocation? I guess we should do this in the RTS linker, and use the double-mapping trick for foreign-import-wrapper stuff. Cheers, Simon
participants (6)
-
Alexander Vodomerov
-
Bertram Felgenhauer
-
Bryan O'Sullivan
-
Bulat Ziganshin
-
Simon Marlow
-
Stefan O'Rear