packages feed

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 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,