mueval 0.3 → 0.3.1
raw patch · 6 files changed
+60/−38 lines, 6 filesdep ~hint
Dependency ranges changed: hint
Files
- Mueval/Context.hs +14/−2
- Mueval/Interpreter.hs +5/−0
- Mueval/Resources.hs +13/−22
- mueval.cabal +17/−6
- mueval.hs +8/−8
- tests.sh +3/−0
Mueval/Context.hs view
@@ -1,6 +1,18 @@-module Mueval.Context (cleanModules, defaultModules) where+module Mueval.Context (cleanModules, defaultModules, unsafed) where -import Data.List (elem)+import Data.List (elem, isInfixOf)++{- | Return true if the String contains anywhere in it any keywords associated+ with dangerous functions. Unfortunately, this blacklist leaks like a sieve+ and will return many false positives (eg. unsafed "id \"unsafed\"" -> True). But it+ will at least catch naive and simplistic invocations of "unsafePerformIO",+ "inlinePerformIO", and "unsafeCoerce". -}+unsafed :: String -> Bool+unsafed = \z -> any (`isInfixOf` z) ["unsafe", "inlinePerform", "liftIO", "Coerce", "Foreign",+ "Typeable", "Array", "IOBase", "Handle", "ByteString",+ "Editline", "GLUT", "lock", "ObjectIO", "System.Time",+ "OpenGL", "Control.Concurrent", "System.Posix",+ "throw", "Dyn", "cache", "stdin", "stdout", "stderr"] -- | Return false if any of the listed modules cannot be found in the whitelist. cleanModules :: [String] -> Bool
Mueval/Interpreter.hs view
@@ -9,6 +9,8 @@ import Control.Monad.Trans (liftIO) import System.Exit (exitWith, ExitCode(ExitFailure)) +import qualified Mueval.Resources (limitResources)+ say :: String -> Interpreter () say = liftIO . putStr . take 1024 @@ -23,6 +25,9 @@ -- more programs terminate. reset -- Make sure nothing is available setImports modules++ liftIO Mueval.Resources.limitResources+ checks <- typeChecks expr if checks then do if prt then do say =<< typeOf expr
Mueval/Resources.hs view
@@ -1,6 +1,5 @@ module Mueval.Resources (limitResources) where -import Control.Monad (zipWithM_) import System.Posix.Process (nice) import System.Posix.Resource -- (Resource(..), ResourceLimits, setResourceLimit) import System.Directory (setCurrentDirectory)@@ -10,7 +9,7 @@ limitResources :: IO () limitResources = do setCurrentDirectory "/tmp" -- will at least mess up relative links nice 19 -- Set our process priority way down- zipWithM_ (setResourceLimit) resources limits+ mapM_ (uncurry setResourceLimit) limits -- | Set all the available rlimits. -- These values have been determined through trial-and-error@@ -26,31 +25,23 @@ -- doesn't seem to be security problem because it'll be opened at the module -- stage, before code ever evaluates. openFilesLimitSoft = openFilesLimitHard-openFilesLimitHard = ResourceLimit 7+openFilesLimitHard = ResourceLimit 8 fileSizeLimitSoft = fileSizeLimitHard fileSizeLimitHard = zero dataSizeLimitSoft = dataSizeLimitHard-dataSizeLimitHard = ResourceLimit $ 5^(12::Int)+dataSizeLimitHard = ResourceLimit $ 6^(12::Int) -- These should not be identical, to give the XCPU handler time to trigger-cpuTimeLimitSoft = ResourceLimit 3-cpuTimeLimitHard = ResourceLimit 4+cpuTimeLimitSoft = ResourceLimit 4+cpuTimeLimitHard = ResourceLimit 5 coreSizeLimitSoft = coreSizeLimitHard coreSizeLimitHard = zero zero = ResourceLimit 0 -resources :: [Resource]-resources = [ResourceStackSize,- ResourceTotalMemory,- ResourceOpenFiles,- ResourceFileSize,- ResourceDataSize,- ResourceCoreFileSize,- ResourceCPUTime]-limits :: [ResourceLimits]-limits = [ (ResourceLimits stackSizeLimitSoft stackSizeLimitHard)- , (ResourceLimits totalMemoryLimitSoft totalMemoryLimitHard)- , (ResourceLimits openFilesLimitSoft openFilesLimitHard)- , (ResourceLimits fileSizeLimitSoft fileSizeLimitHard)- , (ResourceLimits dataSizeLimitSoft dataSizeLimitHard)- , (ResourceLimits coreSizeLimitSoft coreSizeLimitHard)- , (ResourceLimits cpuTimeLimitSoft cpuTimeLimitHard)]+limits :: [(Resource, ResourceLimits)]+limits = [ (ResourceStackSize, ResourceLimits stackSizeLimitSoft stackSizeLimitHard)+ , (ResourceTotalMemory, ResourceLimits totalMemoryLimitSoft totalMemoryLimitHard)+ , (ResourceOpenFiles, ResourceLimits openFilesLimitSoft openFilesLimitHard)+ , (ResourceFileSize, ResourceLimits fileSizeLimitSoft fileSizeLimitHard)+ , (ResourceDataSize, ResourceLimits dataSizeLimitSoft dataSizeLimitHard)+ , (ResourceCoreFileSize, ResourceLimits coreSizeLimitSoft coreSizeLimitHard)+ , (ResourceCPUTime, ResourceLimits cpuTimeLimitSoft cpuTimeLimitHard)]
mueval.cabal view
@@ -1,5 +1,5 @@ name: mueval-version: 0.3+version: 0.3.1 license: BSD3 license-file: LICENSE@@ -9,14 +9,25 @@ category: Development, Language synopsis: Safely evaluate Haskell expressions description: Mueval is a Haskell interpreter. It- uses the GHC API to evaluate arbitrary Haskell- expressions. Importantly, mueval takes many precautions- to avoid 'evil' code. It uses resource limits, whitelisted modules,+ uses the GHC API to evaluate arbitrary Haskell expressions.+ Importantly, mueval takes many precautions to defang and avoid "evil"+ code. It uses resource limits, whitelisted modules, special Show instances for IO, threads, changes of directory, and so on to sandbox the Haskell code. (It is much like Lambdabot's famous evaluation functionality.) .- Mueval is POSIX-only.+ Currently there is a major hole in Mueval: it is possible use a function+ without importing it, which allows the module whitelisting to be bypassed,+ and hence, unsafePerformIO and its various type-breaking friends can be used+ to do arbitrary things. Mueval uses a blacklist to avoid the most naive and obvious+ dangerous function imports, but this is a very weak mechanism and cannot be relied+ upon.+ .+ Until this hole is fixed, Mueval should *not* be used on potentially hostile input.+ .+ Mueval is currently POSIX-only.+homepage: http://code.haskell.org/mubot/+ build-type: Simple Cabal-Version: >= 1.2 Tested-with: GHC==6.8.2@@ -26,7 +37,7 @@ Library exposed-modules: Mueval.Context, Mueval.Interpreter, Mueval.ParseArgs, Mueval.Resources- build-Depends: base, directory, mtl, unix, hint>=0.2.1, show+ build-Depends: base, directory, mtl, unix, hint>=0.2.2, show ghc-options: -Wall -static Executable mueval
mueval.hs view
@@ -12,24 +12,24 @@ import System.IO (hSetBuffering, stdout, BufferMode(NoBuffering)) import System.Posix.Signals (sigXCPU, installHandler, Handler(CatchOnce)) -import qualified Mueval.Context (cleanModules)+import qualified Mueval.Context (cleanModules, unsafed) import Mueval.Interpreter import Mueval.ParseArgs-import qualified Mueval.Resources (limitResources) main :: IO () main = do input <- getArgs (opts,_) <- interpreterOpts input if (Mueval.Context.cleanModules $ modules opts) then do- mvar <- newEmptyMVar+ if (not $ Mueval.Context.unsafed $ expression opts) then do+ mvar <- newEmptyMVar - Mueval.Resources.limitResources- myThreadId >>= watchDog (timeLimit opts)+ myThreadId >>= watchDog (timeLimit opts) - forkIO $ forkedMain (mvar) opts- takeMVar mvar -- block until a ErrorCall or the forkedMain succeeds+ forkIO $ forkedMain (mvar) opts+ takeMVar mvar -- block until a ErrorCall or the forkedMain succeeds - return ()+ return ()+ else error "Unsafe functions to use mentioned." else error "Unknown or untrusted module supplied! Aborting." -- Set a watchdog, and then evaluate.
tests.sh view
@@ -39,6 +39,9 @@ mu --module Data.List --module System.IO.Unsafe --module Control.Monad --expression 1+1 mu --module System.IO.Unsafe --expression "let foo = unsafePerformIO readFile \"/etc/passwd\" in foo" mu --module Data.List --module Text.HTML.Download --expression "head [1..]"+### Can we bypass the whitelisting by fully qualified module names?+mu --expression "Foreign.unsafePerformIO $ readFile \"/etc/passwd\""+mu --expression "Data.ByteString.Internal.inlinePerformIO $ readFile \"/etc/passwd\"" ## We need a bunch of IO tests, but I guess this will do for now. mu --expression "let foo = readFile \"/etc/passwd\" >>= print in foo" mu --expression "writeFile \"tmp.txt\" \"foo bar\""