packages feed

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 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\""