git.iko.soy/typeable/timezone-olson
on master help sign in
Enforce size limits specified in tzfile.h.
1652538Yitzchak Gale
Data/Time/LocalTime/TimeZone/Olson/Parse.hs+25−7
@@ -74,36 +74,47 @@ olsonToTimeZoneSeries _ = Nothing
74-- it into a @TimeZoneSeries@ for use together with the types and
74-- it into a @TimeZoneSeries@ for use together with the types and
75-- fucntions of "Data.Time". This is the function from this module
75-- fucntions of "Data.Time". This is the function from this module
76-- for which you are most likely to have use.
76-- for which you are most likely to have use.
77+--
78+-- If the values in the Olson timezone file exceed the standard size
79+-- limits (see 'defaultLimits'), this function throws an
80+-- exception. For other behavior, use 'getOlson' and
81+-- 'Data.Binary.Get.runGet' directly.
77getTimeZoneSeriesFromOlsonFile :: FilePath -> IO TimeZoneSeries
82getTimeZoneSeriesFromOlsonFile :: FilePath -> IO TimeZoneSeries
78getTimeZoneSeriesFromOlsonFile fp = getOlsonFromFile fp >>=
83getTimeZoneSeriesFromOlsonFile fp = getOlsonFromFile fp >>=
79 maybe (throwOlson fp "no timezone found in OlsonData") return .
84 maybe (throwOlson fp "no timezone found in OlsonData") return .
80 olsonToTimeZoneSeries
85 olsonToTimeZoneSeries
81
86
82-- | Parse a binary Olson timezone file.
87-- | Parse a binary Olson timezone file.
88+--
89+-- If the values in the Olson timezone file exceed the standard size
90+-- limits (see 'defaultLimits'), this function throws an
91+-- exception. For other behavior, use 'getOlson' and
92+-- 'Data.Binary.Get.runGet' directly.
83getOlsonFromFile :: FilePath -> IO OlsonData
93getOlsonFromFile :: FilePath -> IO OlsonData
84getOlsonFromFile fp = do
94getOlsonFromFile fp = do
85− e <- try . fmap (runGet getOlson) $ L.readFile fp
95+ e <- try . fmap (runGet $ getOlson defaultLimits) $ L.readFile fp
86 either (formatError fp) return e
96 either (formatError fp) return e
87
97
88formatError :: FilePath -> ErrorCall -> IO a
98formatError :: FilePath -> ErrorCall -> IO a
89formatError fp e = throwOlson fp $ show e
99formatError fp e = throwOlson fp $ show e
90
100
91-- | A binary parser for binary Olson timezone files
101-- | A binary parser for binary Olson timezone files
92−getOlson :: Get OlsonData
102+getOlson :: SizeLimits -> Get OlsonData
93−getOlson = do
103+getOlson limits = do
94− (version, part1) <- getOlsonPart True get32bitInteger
104+ (version, part1) <- getOlsonPart True limits get32bitInteger
95 -- There is one part for Version 1 format data, and two parts and a POSIX
105 -- There is one part for Version 1 format data, and two parts and a POSIX
96 -- TZ string for Version 2 format data
106 -- TZ string for Version 2 format data
97 case version of
107 case version of
98 0 -> return part1
108 0 -> return part1
99− 50 -> do (_, part2) <- getOlsonPart False get64bitInteger
109+ 50 -> do (_, part2) <- getOlsonPart False limits get64bitInteger
100 posixTZ <- getPosixTZ
110 posixTZ <- getPosixTZ
101 return $ part1 `mappend` part2 `mappend` posixTZ
111 return $ part1 `mappend` part2 `mappend` posixTZ
102 _ -> verify (const False) "invalid version number" undefined
112 _ -> verify (const False) "invalid version number" undefined
103
113
104-- Parse the part of an Olson file that contains timezone data
114-- Parse the part of an Olson file that contains timezone data
105−getOlsonPart :: Integral a => Bool -> Get a -> Get (Word8, OlsonData)
115+getOlsonPart :: Integral a => Bool -> SizeLimits -> Get a ->
116+ Get (Word8, OlsonData)
106−getOlsonPart verifyMagic getTime = do
117+getOlsonPart verifyMagic limits getTime = do
107 magic <- fmap (toASCII . B.unpack) $ getByteString 4
118 magic <- fmap (toASCII . B.unpack) $ getByteString 4
108 when verifyMagic $ verify_ (== "TZif") "missing magic number" magic
119 when verifyMagic $ verify_ (== "TZif") "missing magic number" magic
109 version <- getWord8
120 version <- getWord8
@@ -111,9 +122,15 @@ getOlsonPart verifyMagic getTime = do
111 tzh_ttisgmtcnt <- get32bitInt
122 tzh_ttisgmtcnt <- get32bitInt
112 tzh_ttisstdcnt <- get32bitInt
123 tzh_ttisstdcnt <- get32bitInt
113 tzh_leapcnt <- get32bitInt
124 tzh_leapcnt <- get32bitInt
125+ >>= verify (withinLimit maxLeaps) "too many leap second specifications"
114 tzh_timecnt <- get32bitInt
126 tzh_timecnt <- get32bitInt
127+ >>= verify (withinLimit maxTimes) "too many timezone transitions"
115 tzh_typecnt <- get32bitInt
128 tzh_typecnt <- get32bitInt
129+ >>= verify (withinLimit maxTypes) "too many timezone type specifications"
130+ verify (withinLimit maxTypes) "too many isgmt specifiers" tzh_ttisgmtcnt
131+ verify (withinLimit maxTypes) "too many isstd specifiers" tzh_ttisstdcnt
116 tzh_charcnt <- get32bitInt
132 tzh_charcnt <- get32bitInt
133+ >>= verify (withinLimit maxAbbrChars) "too many tilezone specifiers"
117 times <- fmap (map toInteger) $ replicateM tzh_timecnt getTime
134 times <- fmap (map toInteger) $ replicateM tzh_timecnt getTime
118 indexes <- replicateM tzh_timecnt get8bitInt
135 indexes <- replicateM tzh_timecnt get8bitInt
119 ttinfos <- replicateM tzh_typecnt getTtInfo
136 ttinfos <- replicateM tzh_typecnt getTtInfo
@@ -131,6 +148,7 @@ getOlsonPart verifyMagic getTime = do
131 Nothing
148 Nothing
132 )
149 )
133 where
150 where
151+ withinLimit limit value = maybe True (value <=) $ limit limits
134 lookupAbbr (TtInfo gmtoff isdst ttype abbrind) =
152 lookupAbbr (TtInfo gmtoff isdst ttype abbrind) =
135 TtInfo gmtoff isdst ttype . takeWhile (/= '\NUL') . drop abbrind
153 TtInfo gmtoff isdst ttype . takeWhile (/= '\NUL') . drop abbrind
136 setTtype ttinfo ttype = ttinfo {tt_ttype = ttype}
154 setTtype ttinfo ttype = ttinfo {tt_ttype = ttype}
Data/Time/LocalTime/TimeZone/Olson/Render.hs+30−2
@@ -28,6 +28,7 @@ module Data.Time.LocalTime.TimeZone.Olson.Render
28 renderTimeZoneSeriesToOlsonFile,
28 renderTimeZoneSeriesToOlsonFile,
29 timeZoneSeriesToOlson,
29 timeZoneSeriesToOlson,
30 renderOlsonToFile,
30 renderOlsonToFile,
31+ verifyOlsonLimits,
31 putOlson,
32 putOlson,
32 splitOlson
33 splitOlson
33)
34)
@@ -46,9 +47,15 @@ import Data.Ord (comparing)
46import Data.Word (Word8)
47import Data.Word (Word8)
47import Data.Maybe (listToMaybe, maybeToList, isNothing, fromMaybe, catMaybes)
48import Data.Maybe (listToMaybe, maybeToList, isNothing, fromMaybe, catMaybes)
48import Data.Monoid (mempty)
49import Data.Monoid (mempty)
49−import Control.Monad (guard, replicateM_)
50+import Control.Monad (guard, replicateM_, unless)
50
51
51-- | Render a @TimeZoneSeries@ as a binary Olson timezone file.
52-- | Render a @TimeZoneSeries@ as a binary Olson timezone file.
53+--
54+-- If the values in the Olson timezone data exceed the standard size
55+-- limits (see 'defaultLimits'), this function throws an
56+-- exception. For other behavior, use 'timeZoneSeriesToOlson',
57+-- 'verifyOlsonLimits', 'putOlson' and 'Data.Binary.Put.runPut'
58+-- directly.
52renderTimeZoneSeriesToOlsonFile :: FilePath -> TimeZoneSeries -> IO ()
59renderTimeZoneSeriesToOlsonFile :: FilePath -> TimeZoneSeries -> IO ()
53renderTimeZoneSeriesToOlsonFile fp = renderOlsonToFile fp .
60renderTimeZoneSeriesToOlsonFile fp = renderOlsonToFile fp .
54 fromMaybe (error "Cannot render TimeZoneSeries: default is summer time") .
61 fromMaybe (error "Cannot render TimeZoneSeries: default is summer time") .
@@ -76,9 +83,30 @@ timeZoneSeriesToOlson (TimeZoneSeries dflt pairs)
76 zip (uniq . sort . filter (/= dfltTT) $ map (mkTT . snd) pairs) [1..]
83 zip (uniq . sort . filter (/= dfltTT) $ map (mkTT . snd) pairs) [1..]
77 ttinfos = map fst ttAssocs
84 ttinfos = map fst ttAssocs
78
85
86+-- | Check whether @OlsonData@ is within size limits.
87+verifyOlsonLimits :: SizeLimits -> OlsonData -> Bool
88+verifyOlsonLimits limits (OlsonData transs ttinfos leaps _) =
89+ withinLimit maxTimes transs &&
90+ withinLimit maxTypes ttinfos &&
91+ withinLimit maxLeaps leaps &&
92+ withinLimit maxAbbrChars abbrChars
93+ where
94+ withinLimit limit items = maybe True (null . flip drop items) $
95+ limit limits
96+ abbrChars = concat abbrStrs ++ map (const '\NUL') abbrStrs
97+ abbrStrs = map tt_abbr ttinfos
98+
79-- | Render Olson timezone data as a binary Olson timezone file
99-- | Render Olson timezone data as a binary Olson timezone file
100+--
101+-- If the values in the Olson timezone data exceed the standard size
102+-- limits (see 'defaultLimits'), this function throws an
103+-- exception. For other behavior, use 'verifyOlsonLimits', 'putOlson'
104+-- and 'Data.Binary.Put.runPut' directly.
80renderOlsonToFile :: FilePath -> OlsonData -> IO ()
105renderOlsonToFile :: FilePath -> OlsonData -> IO ()
106+renderOlsonToFile fp olson = do
107+ unless (verifyOlsonLimits defaultLimits olson) $
108+ error "Olson timezone data exceeds size limits"
81−renderOlsonToFile fp = L.writeFile fp . runPut . putOlson
109+ L.writeFile fp . runPut . putOlson $ olson
82
110
83-- | Render Olson timezone data in binary Olson timezone file format
111-- | Render Olson timezone data in binary Olson timezone file format
84-- as a lazy @ByteString@
112-- as a lazy @ByteString@
Data/Time/LocalTime/TimeZone/Olson/Types.hs+38−1
@@ -26,7 +26,13 @@ module Data.Time.LocalTime.TimeZone.Olson.Types
26 Transition(..),
26 Transition(..),
27 TransitionType(..),
27 TransitionType(..),
28 TtInfo(..),
28 TtInfo(..),
29− LeapInfo(..)
29+ LeapInfo(..),
30+
31+ -- ** Size limits for Olson timezone data
32+ SizeLimits(..),
33+ defaultLimits,
34+ limitsNoSolar,
35+ noLimits
30)
36)
31where
37where
32
38
@@ -109,3 +115,34 @@ data LeapInfo =
109 -- after this leap second
115 -- after this leap second
110 }
116 }
111 deriving (Eq, Show)
117 deriving (Eq, Show)
118+
119+-- | The reference C implentation imposes size limits on the data
120+-- structures in a timezone file.
121+data SizeLimits = SizeLimits
122+ {maxTimes :: Maybe Int, -- ^ The maximum number of transition times
123+ maxTypes :: Maybe Int, -- ^ The maximum number of TtInfo
124+ -- clock settings
125+ maxAbbrChars :: Maybe Int, -- ^ The maximum total number of bytes in
126+ -- all timezone abbreviations.
127+ maxLeaps :: Maybe Int -- ^ The maximum number of leap second
128+ -- specifications.
129+ }
130+
131+-- | The size limits in @defaultLimits@ are taken from the file
132+-- tzfile.h from tzcode version 2010f. These are the limits for the C
133+-- implementation on many platforms.
134+defaultLimits :: SizeLimits
135+defaultLimits = SizeLimits { maxTimes = Just 1200, maxTypes = Just 256,
136+ maxAbbrChars = Just 50, maxLeaps = Just 50 }
137+
138+-- | @limitsNoSolar@ contains the tighter size limits imposed on some
139+-- platforms that do not allow timezones that are based on solar time.
140+limitsNoSolar :: SizeLimits
141+limitsNoSolar = defaultLimits { maxTypes = Just 20 }
142+
143+-- | @noLimits@ imoses no size limits. If you use @noLimits@ when parsing,
144+-- you may exhaust all available memory when reading a faulty or
145+-- malicious timezone file. If you use @noLimits@ when rendering, the
146+-- rendered timezone file might not be readable on some sytems.
147+noLimits :: SizeLimits
148+noLimits = SizeLimits Nothing Nothing Nothing Nothing