{-# LANGUAGE CPP, GeneralizedNewtypeDeriving, DeriveDataTypeable #-} module System.IO.Jail ( -- * The IO monad IO, -- instance MonadFix run, JailIO (..), -- * Files and handles FilePath, -- :: String Handle, -- abstract, instance of: Eq, Show. -- | Three handles are allocated during program initialisation, -- and are initially open. stdin, stdout, stderr, -- :: Handle -- * Opening and closing files -- ** Opening files withFile, openFile, -- :: FilePath -> IOMode -> IO Handle IOMode(ReadMode,WriteMode,AppendMode,ReadWriteMode), -- ** Closing files hClose, -- :: Handle -> IO () -- ** Special cases readFile, -- :: FilePath -> IO String writeFile, -- :: FilePath -> String -> IO () appendFile, -- :: FilePath -> String -> IO () -- ** File locking -- $locking -- * Operations on handles -- ** Determining and changing the size of a file hFileSize, -- :: Handle -> IO Integer #ifdef __GLASGOW_HASKELL__ hSetFileSize, -- :: Handle -> Integer -> IO () #endif -- ** Detecting the end of input hIsEOF, -- :: Handle -> IO Bool isEOF, -- :: IO Bool -- ** Buffering operations BufferMode(NoBuffering,LineBuffering,BlockBuffering), hSetBuffering, -- :: Handle -> BufferMode -> IO () hGetBuffering, -- :: Handle -> IO BufferMode hFlush, -- :: Handle -> IO () -- ** Repositioning handles hGetPosn, -- :: Handle -> IO HandlePosn hSetPosn, -- :: HandlePosn -> IO () HandlePosn, -- abstract, instance of: Eq, Show. hSeek, -- :: Handle -> SeekMode -> Integer -> IO () SeekMode(AbsoluteSeek,RelativeSeek,SeekFromEnd), #if !defined(__NHC__) hTell, -- :: Handle -> IO Integer #endif -- ** Handle properties hIsOpen, hIsClosed, -- :: Handle -> IO Bool hIsReadable, hIsWritable, -- :: Handle -> IO Bool hIsSeekable, -- :: Handle -> IO Bool -- ** Terminal operations (not portable: GHC\/Hugs only) #if !defined(__NHC__) hIsTerminalDevice, -- :: Handle -> IO Bool hSetEcho, -- :: Handle -> Bool -> IO () hGetEcho, -- :: Handle -> IO Bool #endif -- ** Showing handle state (not portable: GHC only) #ifdef __GLASGOW_HASKELL__ hShow, -- :: Handle -> IO String #endif -- * Text input and output -- ** Text input hWaitForInput, -- :: Handle -> Int -> IO Bool hReady, -- :: Handle -> IO Bool hGetChar, -- :: Handle -> IO Char hGetLine, -- :: Handle -> IO [Char] hLookAhead, -- :: Handle -> IO Char hGetContents, -- :: Handle -> IO [Char] -- ** Text output hPutChar, -- :: Handle -> Char -> IO () hPutStr, -- :: Handle -> [Char] -> IO () hPutStrLn, -- :: Handle -> [Char] -> IO () hPrint, -- :: Show a => Handle -> a -> IO () -- ** Special cases for standard input and output interact, -- :: (String -> String) -> IO () putChar, -- :: Char -> IO () putStr, -- :: String -> IO () putStrLn, -- :: String -> IO () print, -- :: Show a => a -> IO () getChar, -- :: IO Char getLine, -- :: IO String getContents, -- :: IO String readIO, -- :: Read a => String -> IO a readLn, -- :: Read a => IO a -- * Binary input and output withBinaryFile, openBinaryFile, -- :: FilePath -> IOMode -> IO Handle hSetBinaryMode, -- :: Handle -> Bool -> IO () hPutBuf, -- :: Handle -> Ptr a -> Int -> IO () hGetBuf, -- :: Handle -> Ptr a -> Int -> IO Int #if !defined(__NHC__) && !defined(__HUGS__) hPutBufNonBlocking, -- :: Handle -> Ptr a -> Int -> IO Int hGetBufNonBlocking, -- :: Handle -> Ptr a -> Int -> IO Int #endif -- * Temporary files openTempFile, openBinaryTempFile, ) where import Control.Applicative import Control.Monad.Cont import Control.Monad.Error import Control.Monad.Trans.Identity import Control.Monad.List import Control.Monad.RWS import Control.Monad.Reader import Control.Monad.State import Control.Monad.Writer import Data.List import Data.Set (Set) import Data.Typeable import Foreign.Ptr import Prelude hiding (readFile, writeFile, print, appendFile, IO, getChar, getLine, getContents, readIO, readLn, interact, putChar, putStr, putStrLn) import System.Directory import System.IO (IOMode, Handle, BufferMode, HandlePosn, SeekMode, stdin, stdout, stderr) import qualified Data.Set as Set import qualified System.IO as U -- Make `Handle's orderable. data HandleS = HandleS String Handle mkHWrap :: Handle -> HandleS mkHWrap h = HandleS (show h) h instance Eq HandleS where (HandleS _ s) == (HandleS _ t) = s == t instance Ord HandleS where (HandleS s _) `compare` (HandleS t _) = s `compare` t -- | The jailed IO monad. newtype IO a = IO { unJail :: ReaderT (Maybe FilePath) (StateT (Set HandleS) U.IO) a} deriving (Functor, Applicative, Monad, Typeable, MonadFix) -- | Like `MonadIO`, but for jailed computations. class Monad m => JailIO m where jailIO :: IO a -> m a instance JailIO IO where jailIO = id instance JailIO m => JailIO (ContT r m) where jailIO = lift . jailIO instance (Error e, JailIO m) => JailIO (ErrorT e m) where jailIO = lift . jailIO instance JailIO m => JailIO (IdentityT m) where jailIO = lift . jailIO instance JailIO m => JailIO (ListT m) where jailIO = lift . jailIO instance (Monoid w, JailIO m) => JailIO (RWST r w s m) where jailIO = lift . jailIO instance JailIO m => JailIO (ReaderT r m) where jailIO = lift . jailIO instance JailIO m => JailIO (StateT r m) where jailIO = lift . jailIO instance (Monoid r, JailIO m) => JailIO (WriterT r m) where jailIO = lift . jailIO {- | Run a jailed IO computation. The IO computation will be able to access all files that are within the specified jail directory. All file accesses outside the jail directory will be refused. Only file handles opened from within the jailed computation and the handles from the white list will be accessible to the operations requiring a file handle. No smuggling in of foreign handles, border patrol is very strict. When the jail path is specified as `Nothing' no file access will be possible at all, this means the computation can only rely on the white listed handles. -} run :: Maybe FilePath -- ^ The jail directory or `Nothing' for not allowing file access. -> [Handle] -- ^ A white list of handles that are always accessible. -> IO a -- ^ The jailed IO computation to run. -> U.IO a -- ^ Run the computation from within the insecure real world. run jail = runRaw jail . Set.fromList . map mkHWrap runRaw :: Maybe FilePath -> Set HandleS -> IO a -> U.IO a runRaw p h = flip evalStateT h . flip runReaderT p . unJail isSubPathOf :: FilePath -> FilePath -> U.IO Bool isSubPathOf path jail = isPrefixOf <$> canonicalizePath jail <*> canonicalizePath path -- Unconditionally, embed an IO action into the Jail monad. Not to be exported! io :: U.IO a -> IO a io = IO . liftIO -- Allow a new Handle. allow :: Handle -> IO () allow h = (IO . lift) (modify (Set.insert (mkHWrap h))) {- Embed an IO action that takes a FilePath as input. The IO action will only executed when the path is within the jail directory. Not to be exported! -} embedPath :: String -> (FilePath -> U.IO a) -> FilePath -> IO a embedPath name action path = do jail <- IO ask safe <- io (maybe (return False) (path `isSubPathOf`) jail) if safe then io (action path) else error (name ++ ": Permission denied, filepath outside jailed environment. " ++ show path) {- Embed an IO action that takes a `Handle' as input. The IO action will only executed when the handle is opened from within the jailed IO monad. Not to be exported! -} embedHandles :: String -> ([Handle] -> U.IO a) -> [Handle] -> IO a embedHandles name action handles = do set <- IO (lift get) if all (\h -> mkHWrap h `Set.member` set) handles then io (action handles) else error (name ++ ": Permission denied, handle from outside jailed environment. " ++ show handles) embedHandle :: String -> (Handle -> U.IO a) -> Handle -> IO a embedHandle name action handle = embedHandles name (action . head) [handle] -- Embedded IO actions. withFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a withFile f m w = do r <- runRaw <$> IO ask <*> IO (lift get) embedPath "withFile" (\f' -> U.withFile f' m (\h -> r (allow h >> w h))) f openFile :: FilePath -> IOMode -> IO Handle openFile f m = do h <- embedPath "openFile" (flip U.openFile m) f allow h return h hClose :: Handle -> IO () hClose = io . U.hClose -- readFile :: FilePath -> IO String readFile = embedPath "readFile" U.readFile writeFile :: FilePath -> String -> IO () writeFile f s = embedPath "writeFile" (flip U.writeFile s) f appendFile :: FilePath -> String -> IO () appendFile f s = embedPath "appendFile" (flip U.appendFile s) f -- hFileSize :: Handle -> IO Integer hFileSize = embedHandle "hFileSize" U.hFileSize #ifdef __GLASGOW_HASKELL__ hSetFileSize :: Handle -> Integer -> IO () hSetFileSize h i = embedHandle "setFileSize" (flip U.hSetFileSize i) h #endif -- hIsEOF :: Handle -> IO Bool hIsEOF = io . U.hIsEOF isEOF :: IO Bool isEOF = io U.isEOF -- hSetBuffering :: Handle -> BufferMode -> IO () hSetBuffering h m = embedHandle "hSetBuffering" (flip U.hSetBuffering m) h hGetBuffering :: Handle -> IO BufferMode hGetBuffering = embedHandle "hGetBuffering" U.hGetBuffering hFlush :: Handle -> IO () hFlush = embedHandle "hFlush" U.hFlush -- hGetPosn :: Handle -> IO HandlePosn hGetPosn = embedHandle "hGetPosn" U.hGetPosn hSetPosn :: HandlePosn -> IO () hSetPosn = io . U.hSetPosn hSeek :: Handle -> SeekMode -> Integer -> IO () hSeek h m i = embedHandle "hSeek" (\h' -> U.hSeek h' m i) h #if !defined(__NHC__) hTell :: Handle -> IO Integer hTell = embedHandle "hTell" U.hTell #endif -- hIsOpen :: Handle -> IO Bool hIsOpen = embedHandle "hIsOpen" U.hIsOpen hIsClosed :: Handle -> IO Bool hIsClosed = embedHandle "hIsClosed" U.hIsClosed hIsReadable :: Handle -> IO Bool hIsReadable = embedHandle "hIsReadable" U.hIsReadable hIsWritable :: Handle -> IO Bool hIsWritable = embedHandle "hIsWritable" U.hIsWritable hIsSeekable :: Handle -> IO Bool hIsSeekable = embedHandle "hIsSeekable" U.hIsSeekable -- #if !defined(__NHC__) hIsTerminalDevice :: Handle -> IO Bool hIsTerminalDevice = embedHandle "hIsTerminalDevice" U.hIsTerminalDevice hSetEcho :: Handle -> Bool -> IO () hSetEcho h e = embedHandle "hSetEcho" (flip U.hSetEcho e) h hGetEcho :: Handle -> IO Bool hGetEcho = embedHandle "hGetEcho" U.hGetEcho #endif -- #ifdef __GLASGOW_HASKELL__ hShow :: Handle -> IO String hShow = embedHandle "hShow" U.hShow #endif -- hWaitForInput :: Handle -> Int -> IO Bool hWaitForInput h i = embedHandle "hWaitForInput" (flip U.hWaitForInput i) h hReady :: Handle -> IO Bool hReady = embedHandle "hReady" U.hReady hGetChar :: Handle -> IO Char hGetChar = embedHandle "hGetChar" U.hGetChar hGetLine :: Handle -> IO String hGetLine = embedHandle "hGetLine" U.hGetLine hLookAhead :: Handle -> IO Char hLookAhead = embedHandle "hLookAhead" U.hLookAhead hGetContents :: Handle -> IO String hGetContents = embedHandle "hGetContents" U.hGetContents -- hPutChar :: Handle -> Char -> IO () hPutChar h c = embedHandle "hPutChar" (flip U.hPutChar c) h hPutStr :: Handle -> String -> IO () hPutStr h s = embedHandle "hPutStr" (flip U.hPutStr s) h hPutStrLn :: Handle -> String -> IO () hPutStrLn h s = embedHandle "hPutStrLn" (flip U.hPutStrLn s) h hPrint :: Show a => Handle -> a -> IO () hPrint h s = embedHandle "hPrint" (flip U.hPrint s) h -- interact :: (String -> String) -> IO () interact a = embedHandles "interact" (const (U.interact a)) [stdout, stdin] putChar :: Char -> IO () putChar a = embedHandle "putChar" (const (U.putChar a)) stdout putStr :: String -> IO () putStr a = embedHandle "putStr" (const (U.putStr a)) stdout putStrLn :: String -> IO () putStrLn a = embedHandle "putStrLn" (const (U.putStrLn a)) stdout print :: Show a => a -> IO () print a = embedHandle "print" (const (U.print a)) stdout -- getChar :: IO Char getChar = embedHandle "getChar" (const U.getChar) stdin getLine :: IO String getLine = embedHandle "getLine" (const U.getLine) stdin getContents :: IO String getContents = embedHandle "getContents" (const U.getContents) stdin readIO :: Read a => String -> IO a readIO s = embedHandle "readIO" (const (U.readIO s)) stdin readLn :: Read a => IO a readLn = embedHandle "readLn" (const U.readLn) stdin -- withBinaryFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a withBinaryFile f m w = do r <- runRaw <$> IO ask <*> IO (lift get) embedPath "withBinaryFile" (\f' -> U.withBinaryFile f' m (\h -> r (allow h >> w h))) f openBinaryFile :: FilePath -> IOMode -> IO Handle openBinaryFile f m = do h <- embedPath "openBinaryFile" (flip U.openBinaryFile m) f allow h return h hSetBinaryMode :: Handle -> Bool -> IO () hSetBinaryMode h b = embedHandle "hSetBinaryMode" (flip U.hSetBinaryMode b) h hPutBuf :: Handle -> Ptr a -> Int -> IO () hPutBuf h p i = embedHandle "hPutBuf" (\h' -> U.hPutBuf h' p i) h hGetBuf :: Handle -> Ptr a -> Int -> IO Int hGetBuf h p i = embedHandle "hGetBuf" (\h' -> U.hGetBuf h' p i) h #if !defined(__NHC__) && !defined(__HUGS__) hPutBufNonBlocking :: Handle -> Ptr a -> Int -> IO Int hPutBufNonBlocking h p i = embedHandle "hPutBufNonBlocking" (\h' -> U.hPutBufNonBlocking h' p i) h hGetBufNonBlocking :: Handle -> Ptr a -> Int -> IO Int hGetBufNonBlocking h p i = embedHandle "hGetBufNonBlocking" (\h' -> U.hGetBufNonBlocking h' p i) h #endif -- openTempFile :: FilePath -> String -> IO (FilePath, Handle) openTempFile f s = embedPath "openTempFile" (flip U.openTempFile s) f openBinaryTempFile :: FilePath -> String -> IO (FilePath, Handle) openBinaryTempFile f s = embedPath "openBinaryTempFile" (flip U.openBinaryTempFile s) f