unix-time 0.5.0 → 0.5.1
raw patch · 6 files changed
+63/−11 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- ChangeLog.md +10/−0
- Data/UnixTime/Conv.hs +24/−7
- cbits/conv.c +3/−0
- cbits/win_patch.c +2/−2
- test/UnixTimeSpec.hs +22/−0
- unix-time.cabal +2/−2
ChangeLog.md view
@@ -1,5 +1,15 @@ # ChangeLog for unix-time +## v0.5.1++* Security: `formatUnixTime` and `formatUnixTimeGMT` read beyond a+ heap buffer when the output exceeded 80 bytes. Now the buffer grows.+* Security: concurrent calls of the GMT functions could leave the+ process-global `TZ` corrupted. Calls are now serialized and the old+ `TZ` value is copied before being restored.+* Windows: fix a heap overflow in `_patch_unsetenv`, which also failed+ to unset `TZ`.+ ## v0.5.0 * Remove deprecated `old-time` dependency by dropping `fromClockTime` and `toClockTime`
Data/UnixTime/Conv.hs view
@@ -13,6 +13,7 @@ ) where import Control.Applicative+import Control.Concurrent.MVar (MVar, newMVar, withMVar) import Data.ByteString.Char8 import Data.ByteString.Unsafe import Data.UnixTime.Types@@ -38,6 +39,15 @@ foreign import ccall unsafe "c_format_unix_time_gmt" c_format_unix_time_gmt :: CString -> CTime -> CString -> CInt -> IO CSize +-- The GMT functions temporarily change the process-global TZ.+-- Serialize all C calls so that concurrent calls cannot corrupt TZ.+tzLock :: MVar ()+tzLock = unsafePerformIO $ newMVar ()+{-# NOINLINE tzLock #-}++withTZLock :: IO a -> IO a+withTZLock = withMVar tzLock . const+ ---------------------------------------------------------------- -- |@@ -50,7 +60,7 @@ parseUnixTime fmt str = unsafePerformIO $ useAsCString fmt $ \cfmt -> useAsCString str $ \cstr -> do- sec <- c_parse_unix_time cfmt cstr+ sec <- withTZLock $ c_parse_unix_time cfmt cstr return $ UnixTime sec 0 -- |@@ -64,7 +74,7 @@ parseUnixTimeGMT fmt str = unsafePerformIO $ useAsCString fmt $ \cfmt -> useAsCString str $ \cstr -> do- sec <- c_parse_unix_time_gmt cfmt cstr+ sec <- withTZLock $ c_parse_unix_time_gmt cfmt cstr return $ UnixTime sec 0 ----------------------------------------------------------------@@ -106,12 +116,19 @@ -> UnixTime -> IO ByteString formatUnixTimeHelper formatFun fmt (UnixTime sec _) =- useAsCString fmt $ \cfmt -> do- let siz = 80+ useAsCString fmt $ \cfmt -> loop cfmt 80+ where+ -- strftime() returns 0 if the result does not fit in the buffer.+ -- Retry with a larger buffer but give up at a bound.+ limit = 80 + 64 * Data.ByteString.Char8.length fmt+ loop cfmt siz = do ptr <- mallocBytes siz- len <- fromIntegral <$> formatFun cfmt sec ptr (fromIntegral siz)- ptr' <- reallocBytes ptr (len + 1)- unsafePackMallocCString ptr' -- FIXME: Use unsafePackMallocCStringLen from bytestring-0.10.2.0+ len <- withTZLock $ fromIntegral <$> formatFun cfmt sec ptr (fromIntegral siz)+ if len == 0 && siz < limit+ then free ptr >> loop cfmt (siz * 2)+ else do+ ptr' <- reallocBytes ptr (max 1 len)+ unsafePackMallocCStringLen (ptr', len) ----------------------------------------------------------------
cbits/conv.c view
@@ -50,6 +50,8 @@ char *set_tz_utc() { char *tz; tz = getenv("TZ");+ /* setenv() may free the old string, so keep a copy. */+ if (tz) tz = strdup(tz); #if defined(_WIN32) _patch_setenv("TZ", "UTC", 1); #else@@ -72,6 +74,7 @@ #else setenv("TZ", local_tz, 1); #endif+ free(local_tz); } else { #if defined(_WIN32) _patch_unsetenv("TZ");
cbits/win_patch.c view
@@ -121,8 +121,8 @@ int len = strlen(name) + 2; char *sname = (char *)malloc(len); strcpy(sname, name);- sname[len] = '=';- sname[len + 1] = '\0';+ sname[len - 2] = '=';+ sname[len - 1] = '\0'; int r = _putenv(sname); free(sname); return r;
test/UnixTimeSpec.hs view
@@ -6,6 +6,9 @@ module UnixTimeSpec (main, spec) where +import Control.Concurrent (forkIO)+import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)+import Control.Monad (forM_, replicateM) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as BS import Data.Function (on)@@ -15,6 +18,7 @@ import Foreign.Marshal.Alloc (alloca) import Foreign.Ptr (Ptr) import Foreign.Storable (peek, poke)+import System.Environment (lookupEnv) import qualified Language.Haskell.TH as TH (runIO) import Test.Hspec import Test.Hspec.QuickCheck (prop)@@ -40,6 +44,24 @@ spec :: Spec spec = do+ describe "formatUnixTimeGMT (long output)" $+ it "does not truncate output longer than the initial buffer" $ do+ let fmt = BS.concat (replicate 12 "%Y-%m-%d ")+ formatUnixTimeGMT fmt (UnixTime 0 0)+ `shouldBe` BS.concat (replicate 12 "1970-01-01 ")++ describe "formatUnixTimeGMT (concurrency)" $+ it "does not corrupt TZ when called concurrently" $ do+ tz0 <- lookupEnv "TZ"+ vs <- replicateM 8 newEmptyMVar+ forM_ (zip [0 ..] vs) $ \(i, v) -> forkIO $ do+ forM_ [1 .. 2000 :: Int] $ \j ->+ BS.length (formatUnixTimeGMT webDateFormat (UnixTime (i * 100000 + fromIntegral j) 0))+ `seq` return ()+ putMVar v ()+ mapM_ takeMVar vs+ lookupEnv "TZ" `shouldReturn` tz0+ describe "formatUnixTime" $ prop "behaves like the model" $ \ut -> do let ours = formatUnixTime mailDateFormat ut
unix-time.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.18 name: unix-time-version: 0.5.0+version: 0.5.1 license: BSD3 license-file: LICENSE maintainer: Kazu Yamamoto <kazu@iij.ad.jp>@@ -69,7 +69,7 @@ hs-source-dirs: test other-modules: UnixTimeSpec default-language: Haskell2010- ghc-options: -Wall+ ghc-options: -Wall -threaded -with-rtsopts=-N build-depends: base, bytestring,