{-# LANGUAGE CPP #-} #if __GLASGOW_HASKELL__ >= 702 {-# LANGUAGE Trustworthy #-} #endif {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FunctionalDependencies #-} -- | This module abstracts the basic 'FileHandle' methods provided by -- the system library, and provides an 'LHandle' ('Labeled' 'Handle') -- type that can be manipulated from within the 'LIO' Monad. -- (There is no notion of changeable current working directory in -- the 'LIO' Monad, nor symbolic links.) -- -- The actual storage of labeled files is handled by the "LIO.FS" -- module. -- -- /IMPORTANT:/ To use a labeled filesystem you must use 'evalWithRoot', -- otherwise any actions built using the combinators of this module will -- crash. -- -- An example use is shown below: -- -- > -- > main = dcEvalWithRoot "/tmp/lioFS" $ do -- > createDirectoryP p lsecrets "secrets" -- > writeFileP p ("secrets" "alice" ) "I like Bob!" -- > where p = ... -- > lsecrets = .... -- > -- -- The file store for the labeled filesystem (see "LIO.FS") will -- be created in @\/tmp\/lioFS@, but this is transparent and the user -- can think of the filesystem as having root @/@. module LIO.Handle ( -- * LIO with filesystem support evalWithRoot -- * Generic Handle operations , DirectoryOps(..) , CloseOps(..) , HandleOps(..) , readFile, writeFile, writeFileL , IOMode(..) -- * LIO Handle , LHandle, labelOfHandle -- ** Privileged combinators , getDirectoryContentsP , createDirectoryP , openFileP , hCloseP , hFlushP , hGetP , hGetNonBlockingP , hGetContentsP , hPutP , hPutStrP , hPutStrLnP , readFileP, writeFileP, writeFileLP ) where #if __GLASGOW_HASKELL__ >= 702 import safe Prelude hiding (catch, readFile, writeFile) import safe System.IO (IOMode(..)) import safe qualified System.IO as IO #else import Prelude hiding (catch, readFile, writeFile) import System.IO (IOMode(..)) import qualified System.IO as IO #endif import LIO.TCB import LIO.FS import Data.Serialize import qualified System.Directory as IO import System.FilePath import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy.Char8 as LC -- | Class used to abstract reading and creating directories, and -- opening (possibly creating) files. class (Monad m) => DirectoryOps h m | m -> h where -- | Get the contents of a directory. getDirectoryContents :: FilePath -> m [FilePath] -- | Create a directory at the supplied path. -- The LIO instance labels the directory with the current label. createDirectory :: FilePath -> m () -- | Open handle to manage the file at the supplied path. openFile :: FilePath -> IOMode -> m h -- | Class used to abstract close and flush operations on handles. class (Monad m) => CloseOps h m where hClose :: h -> m () hFlush :: h -> m () -- | Class used to abstract reading and writing from and to handles, -- respectively. class (CloseOps h m) => HandleOps h b m where hGet :: h -> Int -> m b hGetNonBlocking :: h -> Int -> m b hGetContents :: h -> m b hPut :: h -> b -> m () hPutStr :: h -> b -> m () hPutStr = hPut hPutStrLn :: h -> b -> m () -- -- Standard IO Handle Operations -- instance DirectoryOps IO.Handle IO where getDirectoryContents = IO.getDirectoryContents createDirectory = IO.createDirectory openFile = IO.openBinaryFile instance CloseOps IO.Handle IO where hClose = IO.hClose hFlush = IO.hFlush instance HandleOps IO.Handle L.ByteString IO where hGet = L.hGet hGetNonBlocking = L.hGetNonBlocking hGetContents = L.hGetContents hPut = L.hPut hPutStrLn = LC.hPutStrLn -- -- LIO Handle Operations -- -- | A labeled handle. data LHandle l = LHandleTCB l IO.Handle -- | Get the label of a labeled handle. labelOfHandle :: Label l => LHandle l -> l labelOfHandle (LHandleTCB l _) = l instance (Serialize l, LabelState l p s) => DirectoryOps (LHandle l) (LIO l p s) where getDirectoryContents = getDirectoryContentsP noPrivs createDirectory f = do l <- getLabel createDirectoryP noPrivs l f openFile f m = do l <- getLabel openFileP noPrivs (Just l) f m -- | Get the contents of a directory. The current label is raised to -- the join of the current label and that of all the directories -- traversed to the leaf directory (of course, using privileges to -- keep the current label unchanged when possible). Note that, unlike -- the standard Haskell 'getDirectoryContents', we first normalise the -- path by collapsing all the @..@'s. (The LIO filesystem does not -- support links.) getDirectoryContentsP :: (LabelState l p s, Serialize l) => p -- ^ Privilege -> FilePath -- ^ Directory -> LIO l p s [FilePath] getDirectoryContentsP p' dir = withCombinedPrivs p' $ \p -> do path <- lookupObjPathP p dir >>= unlabelFilePathP p rtioTCB $ IO.getDirectoryContents path -- | Create a directory at the supplied path with the given label. -- The current label (after traversing the filesystem to the -- directory path) must flow to the supplied label which in turn must -- flow to the current label (of course, using privileges to bypass -- certain restrictions). If this information flow restriction is -- satisfied, the directory is created. createDirectoryP :: (LabelState l p s, Serialize l) => p -- ^ Privilege -> l -- ^ Label of new directory -> FilePath -- ^ Path of directory -> LIO l p s () createDirectoryP p ldir path' = withCombinedPrivs p $ \priv -> do path <- cleanUpPath path' aguardP priv ldir lcDir <- lookupObjPathP priv (containingDir path) wguardP priv $ labelOfFilePath lcDir rtioTCB $ createDirectoryTCB ldir path where stripLastSlash = (reverse . stripSlash . reverse) containingDir = takeDirectory . ([pathSeparator] ) . stripLastSlash -- | Given a set of privileges, a new (maybe) label of a file, a filepath -- and the handle mode, open (and possibly create) the file. If the file -- exists the supplied label is not necessary; otherwise it must be supplied. -- The current label is raised to reflect all the traversed directories -- (of course, using privileges to minimize the taint). Additionally the -- label of the file (new or existing) must be between the current label -- and clearance. If the file is created, it is further required that the -- current process be able to write to the containing directory. openFileP :: (LabelState l p s, Serialize l) => p -- ^ Privileges -> Maybe l -- ^ Label of file if created -> FilePath -- ^ File to open -> IOMode -- ^ Mode of handle -> LIO l p s (LHandle l) openFileP p mlfile path' mode = withCombinedPrivs p $ \priv -> do path <- cleanUpPath path' let containingDir = takeDirectory path fileName = takeFileName path -- check that the supplied label is bounded by current label and clearance: maybe (return ()) (aguardP priv) mlfile -- lookup object corresponding to containing dir: lcDir <- lookupObjPathP priv containingDir -- unlabel the containing dir object: actualCDir <- unlabelFilePathP priv lcDir let objPath = actualCDir fileName -- actual object path exists <- rtioTCB $ IO.doesFileExist objPath if exists then do l <- getObjLabelTCB objPath -- label of object aguardP priv l -- make sure we can actually read the file -- NOTE: if mode == ReadMode, we might want to instead do -- aguardP priv (l `lub` currentLabel) to allow opening -- a handle for an object whose label is below the current -- label. Some Unix systems still update a file's atime -- when performing a read and so, for now, a read always -- implies a write. h <- rtioTCB $ IO.openFile objPath mode return $ LHandleTCB l h else case mlfile of Nothing -> throwIO $ userError "openFileP: File label missing." Just l -> do wguardP priv (labelOfFilePath lcDir) -- can write to containing dir aguardP priv l -- make sure we can actually read the file -- NOTE: the latter is necessary as looking up the containing -- directory object might have raised the current label. h <- ioTCB $ createFileTCB l objPath mode return $ LHandleTCB l h instance (LabelState l p s) => CloseOps (LHandle l) (LIO l p s) where hClose = hCloseP noPrivs hFlush = hFlushP noPrivs instance (LabelState l p s, CloseOps (LHandle l) (LIO l p s) , HandleOps IO.Handle b IO) => HandleOps (LHandle l) b (LIO l p s) where hGet = hGetP noPrivs hGetNonBlocking = hGetNonBlockingP noPrivs hGetContents = hGetContentsP noPrivs hPut = hPutP noPrivs hPutStrLn = hPutStrLnP noPrivs -- | Close a labeled file handle. hCloseP :: (LabelState l p s) => p -> LHandle l -> LIO l p s () hCloseP p' (LHandleTCB l h) = withCombinedPrivs p' $ \p -> wguardP p l >> rtioTCB (hClose h) -- | Flush a labeled file handle. hFlushP :: (LabelState l p s) => p -> LHandle l -> LIO l p s () hFlushP p' (LHandleTCB l h) = withCombinedPrivs p' $ \p -> wguardP p l >> rtioTCB (hFlush h) -- | Read @n@ bytes from the labeled handle, using privileges when -- performing label comparisons and tainting. hGetP :: (LabelState l p s, HandleOps IO.Handle b IO) => p -- ^ Privileges -> LHandle l -- ^ Labeled handle -> Int -- ^ Number of bytes to read -> LIO l p s b hGetP p' (LHandleTCB l h) n = withCombinedPrivs p' $ \p -> wguardP p l >> rtioTCB (hGet h n) -- | Same as 'hGetP', but will not block waiting for data to become -- available. Instead, it returns whatever data is available. -- Privileges are used in the label comparisons and when raising -- the current label. hGetNonBlockingP :: (LabelState l p s, HandleOps IO.Handle b IO) => p -> LHandle l -> Int -> LIO l p s b hGetNonBlockingP p' (LHandleTCB l h) n = withCombinedPrivs p' $ \p -> wguardP p l >> rtioTCB (hGetNonBlocking h n) -- | Read the entire labeled handle contents and close handle upon -- reading @EOF@. Privileges are used in the label comparisons -- and when raising the current label. hGetContentsP :: (LabelState l p s, HandleOps IO.Handle b IO) => p -> LHandle l -> LIO l p s b hGetContentsP p' (LHandleTCB l h) = withCombinedPrivs p' $ \p -> wguardP p l >> rtioTCB (hGetContents h) -- | Output the given (Byte)String to the specified labeled handle. -- Privileges are used in the label comparisons and when raising -- the current label. hPutP :: (LabelState l p s, HandleOps IO.Handle b IO) => p -> LHandle l -> b -> LIO l p s () hPutP p' (LHandleTCB l h) s = withCombinedPrivs p' $ \p -> wguardP p l >> rtioTCB (hPut h s) -- | Synonym for 'hPutP'. hPutStrP :: (LabelState l p s, HandleOps IO.Handle b IO) => p -> LHandle l -> b -> LIO l p s () hPutStrP = hPutP -- | Output the given (Byte)String with an appended newline to the -- specified labeled handle. Privileges are used in the label -- comparisons and when raising the current label. hPutStrLnP :: (LabelState l p s, HandleOps IO.Handle b IO) => p -> LHandle l -> b -> LIO l p s () hPutStrLnP p' (LHandleTCB l h) s = withCombinedPrivs p' $ \p -> wguardP p l >> rtioTCB (hPutStrLn h s) -- -- Special cases -- -- | Reads a file and returns the contents of the file as a (Byte)String. readFile :: (DirectoryOps h m, HandleOps h b m) => FilePath -> m b readFile path = openFile path ReadMode >>= hGetContents -- | Write a (Byte)String to a file. writeFile :: (DirectoryOps h m, HandleOps h b m, OnExceptionTCB m) => FilePath -> b -> m () writeFile path contents = bracketTCB (openFile path WriteMode) hClose (flip hPut contents) -- | Same as 'readFile' but uses privilege in opening the file. readFileP :: (HandleOps IO.Handle b IO, LabelState l p s, Serialize l) => p -> FilePath -> LIO l p s b readFileP p' path = withCombinedPrivs p' $ \p -> openFileP p Nothing path ReadMode >>= hGetContentsP p -- | Same as 'writeFile' but uses privilege in opening the file. writeFileP :: (HandleOps IO.Handle b IO, LabelState l p s, Serialize l) => p -> FilePath -> b -> LIO l p s () writeFileP p' path contents = withCombinedPrivs p' $ \privs -> do l <- getLabel bracketTCB (openFileP privs (Just l) path WriteMode) (hCloseP privs) (flip (hPutP privs) contents) -- | Same as 'writeFile' but also takes the label of the file. writeFileL :: (HandleOps IO.Handle b IO, LabelState l p s, Serialize l) => l -> FilePath -> b -> LIO l p s () writeFileL = writeFileLP noPrivs -- | Same as 'writeFileL' but uses privilege in opening the file. writeFileLP :: (HandleOps IO.Handle b IO, LabelState l p s, Serialize l) => p -> l -> FilePath -> b -> LIO l p s () writeFileLP p' l path contents = withCombinedPrivs p' $ \privs -> do bracketTCB (openFileP privs (Just l) path WriteMode) (hCloseP privs) (flip (hPutP privs) contents)