FIX #1258: document that openTempFile is secure(ish)
[ghc-base.git] / System / IO.hs
index a5b9d7d..a887d99 100644 (file)
@@ -95,7 +95,7 @@ module System.IO (
     hIsReadable, hIsWritable,  -- :: Handle -> IO Bool
     hIsSeekable,               -- :: Handle -> IO Bool
 
-    -- ** Terminal operations
+    -- ** Terminal operations (not portable: GHC\/Hugs only)
 
 #if !defined(__NHC__)
     hIsTerminalDevice,         -- :: Handle -> IO Bool
@@ -104,7 +104,7 @@ module System.IO (
     hGetEcho,                  -- :: Handle -> IO Bool
 #endif
 
-    -- ** Showing handle state
+    -- ** Showing handle state (not portable: GHC only)
 
 #ifdef __GLASGOW_HASKELL__
     hShow,                     -- :: Handle -> IO String
@@ -155,18 +155,22 @@ module System.IO (
     hGetBufNonBlocking,               -- :: Handle -> Ptr a -> Int -> IO Int
 #endif
 
-    -- * Temporary files
+    -- * Temporary files (not portable: GHC\/Hugs only)
 
+#if !defined(__NHC__)
     openTempFile,
     openBinaryTempFile,
+#endif
   ) where
 
+#ifndef __NHC__
 import Data.Bits
 import Data.List
 import Data.Maybe
 import Foreign.C.Error
 import Foreign.C.String
 import System.Posix.Internals
+#endif
 
 #ifdef __GLASGOW_HASKELL__
 import GHC.Exception    as ExceptionBase hiding (catch)
@@ -412,11 +416,25 @@ openBinaryFile = openFile
 hSetBinaryMode _ _ = return ()
 #endif
 
+#ifndef __NHC__
 -- | The function creates a temporary file in ReadWrite mode.
 -- The created file isn\'t deleted automatically, so you need to delete it manually.
+--
+-- The file is creates with permissions such that only the current
+-- user can read/write it.
+--
+-- With some exceptions (see below), the file will be created securely
+-- in the sense that an attacker should not be able to cause
+-- openTempFile to overwrite another file on the filesystem using your
+-- credentials, by putting symbolic links (on Unix) in the place where
+-- the temporary file is to be created.  On Unix the @O_CREAT@ and
+-- @O_EXCL@ flags are used to prevent this attack, but note that
+-- @O_EXCL@ is sometimes not supported on NFS filesystems, so if you
+-- rely on this behaviour it is best to use local filesystems only.
+--
 openTempFile :: FilePath   -- ^ Directory in which to create the file
              -> String     -- ^ File name template. If the template is \"foo.ext\" then
-                           -- the create file will be \"fooXXX.ext\" where XXX is some
+                           -- the created file will be \"fooXXX.ext\" where XXX is some
                            -- random number.
              -> IO (FilePath, Handle)
 openTempFile tmp_dir template = openTempFile' "openTempFile" tmp_dir template False
@@ -433,9 +451,19 @@ openTempFile' loc tmp_dir template binary = do
     -- We split off the last extension, so we can use .foo.ext files
     -- for temporary files (hidden on Unix OSes). Unfortunately we're
     -- below filepath in the hierarchy here.
-    (prefix,suffix) = case break (== '.') $ reverse template of
-                          (rev_suffix, rev_prefix) ->
-                              (reverse rev_prefix, reverse rev_suffix)
+    (prefix,suffix) = 
+       case break (== '.') $ reverse template of
+         -- First case: template contains no '.'s. Just re-reverse it.
+         (rev_suffix, "")       -> (reverse rev_suffix, "")
+         -- Second case: template contains at least one '.'. Strip the
+         -- dot from the prefix and prepend it to the suffix (if we don't
+         -- do this, the unique number will get added after the '.' and
+         -- thus be part of the extension, which is wrong.)
+         (rev_suffix, '.':rest) -> (reverse rest, '.':reverse rev_suffix)
+         -- Otherwise, something is wrong, because (break (== '.')) should
+         -- always return a pair with either the empty string or a string
+         -- beginning with '.' as the second component.
+         _                      -> error "bug in System.IO.openTempFile"
 
     oflags1 = rw_flags .|. o_EXCL
 
@@ -447,7 +475,7 @@ openTempFile' loc tmp_dir template binary = do
 
     findTempName x = do
       fd <- withCString filepath $ \ f ->
-              c_open f oflags 0o666
+              c_open f oflags 0o600
       if fd < 0 
        then do
          errno <- getErrno
@@ -463,7 +491,15 @@ openTempFile' loc tmp_dir template binary = do
         return (filepath, h)
       where
         filename        = prefix ++ show x ++ suffix
-        filepath        = tmp_dir ++ [pathSeparator] ++ filename
+        filepath        = tmp_dir `combine` filename
+
+        -- XXX bits copied from System.FilePath, since that's not available here
+        combine a b
+                  | null b = a
+                  | null a = b
+                  | last a == pathSeparator = a ++ b
+                  | otherwise = a ++ [pathSeparator] ++ b
+
 #if __HUGS__
         fdToHandle fd   = openFd (fromIntegral fd) False ReadWriteMode binary
 #endif
@@ -483,6 +519,7 @@ read_flags   = std_flags    .|. o_RDONLY
 write_flags  = output_flags .|. o_WRONLY
 rw_flags     = output_flags .|. o_RDWR
 append_flags = write_flags  .|. o_APPEND
+#endif
 
 -- $locking
 -- Implementations should enforce as far as possible, at least locally to the