git.iko.soy/typeable/timezone-olson
on master help sign in
Enforce size limits specified in tzfile.h.
commit1652538b1b85b0077f4ccf875e2cd964b84a55fb authorYitzchak Gale <gale@sefer.org> dateTue 18 May 2010, 12:28 (16 years ago) parent17149d0
3 files changed+93−10
Data/Time/LocalTime/TimeZone/Olson/Parse.hs+25−7 Data/Time/LocalTime/TimeZone/Olson/Render.hs+30−2 Data/Time/LocalTime/TimeZone/Olson/Types.hs+38−1
Data/Time/LocalTime/TimeZone/Olson/Parse.hs+25−7
@@ -74,36 +74,47 @@ olsonToTimeZoneSeries _ = Nothing
7474-- it into a @TimeZoneSeries@ for use together with the types and
7575-- fucntions of "Data.Time". This is the function from this module
7676-- 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.
7782getTimeZoneSeriesFromOlsonFile :: FilePath -> IO TimeZoneSeries
7883getTimeZoneSeriesFromOlsonFile fp = getOlsonFromFile fp >>=
7984 maybe (throwOlson fp "no timezone found in OlsonData") return .
8085 olsonToTimeZoneSeries
8186
8287-- | 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.
8393getOlsonFromFile :: FilePath -> IO OlsonData
8494getOlsonFromFile fp = do
85− e <- try . fmap (runGet getOlson) $ L.readFile fp
95+ e <- try . fmap (runGet $ getOlson defaultLimits) $ L.readFile fp
8696 either (formatError fp) return e
8797
8898formatError :: FilePath -> ErrorCall -> IO a
8999formatError fp e = throwOlson fp $ show e
90100
91101-- | A binary parser for binary Olson timezone files
92−getOlson :: Get OlsonData
93−getOlson = do
94− (version, part1) <- getOlsonPart True get32bitInteger
102+getOlson :: SizeLimits -> Get OlsonData
103+getOlson limits = do
104+ (version, part1) <- getOlsonPart True limits get32bitInteger
95105 -- There is one part for Version 1 format data, and two parts and a POSIX
96106 -- TZ string for Version 2 format data
97107 case version of
98108 0 -> return part1
99− 50 -> do (_, part2) <- getOlsonPart False get64bitInteger
109+ 50 -> do (_, part2) <- getOlsonPart False limits get64bitInteger
100110 posixTZ <- getPosixTZ
101111 return $ part1 `mappend` part2 `mappend` posixTZ
102112 _ -> verify (const False) "invalid version number" undefined
103113
104114-- Parse the part of an Olson file that contains timezone data
105−getOlsonPart :: Integral a => Bool -> Get a -> Get (Word8, OlsonData)
106−getOlsonPart verifyMagic getTime = do
115+getOlsonPart :: Integral a => Bool -> SizeLimits -> Get a ->
116+ Get (Word8, OlsonData)
117+getOlsonPart verifyMagic limits getTime = do
107118 magic <- fmap (toASCII . B.unpack) $ getByteString 4
108119 when verifyMagic $ verify_ (== "TZif") "missing magic number" magic
109120 version <- getWord8
@@ -111,9 +122,15 @@ getOlsonPart verifyMagic getTime = do
111122 tzh_ttisgmtcnt <- get32bitInt
112123 tzh_ttisstdcnt <- get32bitInt
113124 tzh_leapcnt <- get32bitInt
125+ >>= verify (withinLimit maxLeaps) "too many leap second specifications"
114126 tzh_timecnt <- get32bitInt
127+ >>= verify (withinLimit maxTimes) "too many timezone transitions"
115128 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
116132 tzh_charcnt <- get32bitInt
133+ >>= verify (withinLimit maxAbbrChars) "too many tilezone specifiers"
117134 times <- fmap (map toInteger) $ replicateM tzh_timecnt getTime
118135 indexes <- replicateM tzh_timecnt get8bitInt
119136 ttinfos <- replicateM tzh_typecnt getTtInfo
@@ -131,6 +148,7 @@ getOlsonPart verifyMagic getTime = do
131148 Nothing
132149 )
133150 where
151+ withinLimit limit value = maybe True (value <=) $ limit limits
134152 lookupAbbr (TtInfo gmtoff isdst ttype abbrind) =
135153 TtInfo gmtoff isdst ttype . takeWhile (/= '\NUL') . drop abbrind
136154 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
2828 renderTimeZoneSeriesToOlsonFile,
2929 timeZoneSeriesToOlson,
3030 renderOlsonToFile,
31+ verifyOlsonLimits,
3132 putOlson,
3233 splitOlson
3334)
@@ -46,9 +47,15 @@ import Data.Ord (comparing)
4647import Data.Word (Word8)
4748import Data.Maybe (listToMaybe, maybeToList, isNothing, fromMaybe, catMaybes)
4849import Data.Monoid (mempty)
49−import Control.Monad (guard, replicateM_)
50+import Control.Monad (guard, replicateM_, unless)
5051
5152-- | 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.
5259renderTimeZoneSeriesToOlsonFile :: FilePath -> TimeZoneSeries -> IO ()
5360renderTimeZoneSeriesToOlsonFile fp = renderOlsonToFile fp .
5461 fromMaybe (error "Cannot render TimeZoneSeries: default is summer time") .
@@ -76,9 +83,30 @@ timeZoneSeriesToOlson (TimeZoneSeries dflt pairs)
7683 zip (uniq . sort . filter (/= dfltTT) $ map (mkTT . snd) pairs) [1..]
7784 ttinfos = map fst ttAssocs
7885
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+
7999-- | 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.
80105renderOlsonToFile :: FilePath -> OlsonData -> IO ()
81−renderOlsonToFile fp = L.writeFile fp . runPut . putOlson
106+renderOlsonToFile fp olson = do
107+ unless (verifyOlsonLimits defaultLimits olson) $
108+ error "Olson timezone data exceeds size limits"
109+ L.writeFile fp . runPut . putOlson $ olson
82110
83111-- | Render Olson timezone data in binary Olson timezone file format
84112-- as a lazy @ByteString@
Data/Time/LocalTime/TimeZone/Olson/Types.hs+38−1
@@ -26,7 +26,13 @@ module Data.Time.LocalTime.TimeZone.Olson.Types
2626 Transition(..),
2727 TransitionType(..),
2828 TtInfo(..),
29− LeapInfo(..)
29+ LeapInfo(..),
30+
31+ -- ** Size limits for Olson timezone data
32+ SizeLimits(..),
33+ defaultLimits,
34+ limitsNoSolar,
35+ noLimits
3036)
3137where
3238
@@ -109,3 +115,34 @@ data LeapInfo =
109115 -- after this leap second
110116 }
111117 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