on master help sign in
Merge
commit552027fefcd89bd22ded74dae496a04e4a9e04b1 authorBryan O'Sullivan <bos@serpentine.com> dateWed 14 May 2014, 21:06 (12 years ago) parente27ec467e1881b
2 files changed+17−95
Data/Aeson/Encode/ByteString.hs+3−2 Data/Aeson/Types/Instances.hs+14−93
Data/Aeson/Encode/ByteString.hs+3−2
@@ -19,11 +19,12 @@ module Data.Aeson.Encode.ByteString
1919import Prelude hiding (null)
2020import Data.Aeson.Types (ToJSON(..), Value(..))
2121import Data.Char (ord)
22−import Data.Scientific (Scientific, coefficient, base10Exponent, formatScientific, FPFormat(Generic))
22+import Data.Scientific (Scientific, coefficient, base10Exponent)
2323import Data.Word (Word8)
2424import Data.Monoid (mappend)
2525import Data.ByteString.Builder as B
2626import Data.ByteString.Builder.Prim as BP
27+import Data.ByteString.Builder.Scientific (scientificBuilder)
2728import qualified Data.ByteString.Lazy as L
2829import qualified Data.HashMap.Strict as HMS
2930import qualified Data.Text as T
@@ -95,7 +96,7 @@ string t =
9596
9697number :: Scientific -> Builder
9798number s
98− | e < 0 = B.string8 $ formatScientific Generic Nothing s
99+ | e < 0 = scientificBuilder s
99100 | otherwise = B.integerDec (coefficient s * 10 ^ e)
100101 where
101102 e = base10Exponent s
Data/Aeson/Types/Instances.hs+14−93
@@ -56,7 +56,7 @@ import Data.Aeson.Functions
5656import Data.Aeson.Types.Class
5757import Data.Aeson.Types.Internal
5858import Data.Scientific (Scientific)
59−import qualified Data.Scientific as Scientific (coefficient, base10Exponent, fromFloatDigits)
59+import qualified Data.Scientific as Scientific (coefficient, base10Exponent, fromFloatDigits, toRealFloat)
6060import Data.Attoparsec.Number (Number(..))
6161import Data.Fixed
6262import Data.Hashable (Hashable(..))
@@ -168,7 +168,7 @@ instance ToJSON Double where
168168 {-# INLINE toJSON #-}
169169
170170instance FromJSON Double where
171− parseJSON = parseFractional "Double"
171+ parseJSON = parseRealFloat "Double"
172172 {-# INLINE parseJSON #-}
173173
174174instance ToJSON Number where
@@ -187,7 +187,7 @@ instance ToJSON Float where
187187 {-# INLINE toJSON #-}
188188
189189instance FromJSON Float where
190− parseJSON = parseFractional "Float"
190+ parseJSON = parseRealFloat "Float"
191191 {-# INLINE parseJSON #-}
192192
193193instance ToJSON (Ratio Integer) where
@@ -206,6 +206,10 @@ instance HasResolution a => ToJSON (Fixed a) where
206206 toJSON = Number . realToFrac
207207 {-# INLINE toJSON #-}
208208
209+-- | /WARNING:/ Only parse fixed-precision numbers from trusted input
210+-- since an attacker could easily fill up the memory of the target
211+-- system by specifying a scientific number with a big exponent like
212+-- @1e1000000000@.
209213instance HasResolution a => FromJSON (Fixed a) where
210214 parseJSON = withScientific "Fixed" $ pure . realToFrac
211215 {-# INLINE parseJSON #-}
@@ -786,102 +790,19 @@ realFloatToJSON d
786790
787791scientificToNumber :: Scientific -> Number
788792scientificToNumber s
789− | e < 0 = D $ realToFrac s
793+ | e < 0 = D $ Scientific.toRealFloat s
790794 | otherwise = I $ c * 10 ^ e
791795 where
792796 e = Scientific.base10Exponent s
793797 c = Scientific.coefficient s
794798{-# INLINE scientificToNumber #-}
795799
796−parseFractional :: Fractional a => String -> Value -> Parser a
797−parseFractional _ (Number s) = pure $ scientificToFractional s
798−parseFractional _ Null = pure (0/0)
799−parseFractional expected v = typeMismatch expected v
800−{-# INLINE parseFractional #-}
801−
802−-- | Convert an /untrusted/ scientific value to a fractional.
803−scientificToFractional :: Fractional a => Scientific -> a
804−scientificToFractional s = realToFrac s
805− -- TODO: Using realToFrac is unsafe here. Do something similar as
806− -- scientificToIntegral. The following might work but I'm not sure
807− -- this doesn't introduce rounding errors:
808− --
809− -- fromInteger c * 10 ^ e
810− -- where
811− -- e = Scientific.base10Exponent s
812− -- c = Scientific.coefficient s
800+parseRealFloat :: RealFloat a => String -> Value -> Parser a
801+parseRealFloat _ (Number s) = pure $ Scientific.toRealFloat s
802+parseRealFloat _ Null = pure (0/0)
803+parseRealFloat expected v = typeMismatch expected v
804+{-# INLINE parseRealFloat #-}
813805
814806parseIntegral :: Integral a => String -> Value -> Parser a
815−parseIntegral expected = withScientific expected $ pure . scientificToIntegral
807+parseIntegral expected = withScientific expected $ pure . floor
816808{-# INLINE parseIntegral #-}
817−
818−-- | Safely convert an /untrusted/ scientific value to an integral.
819−--
820−-- This function is safe in the sense that the amount of space used is
821−-- bounded by the size of the target type.
822−scientificToIntegral :: Integral a => Scientific -> a
823−scientificToIntegral s
824− -- When the exponent is positive (we're dealing with an integer)
825− -- we convert the coefficient to the desired integral type and
826− -- multiply it by the magnitude (10^e). Note that the magnitude is
827− -- also represented in the target type which means that the space
828− -- usage is bounded by the size of that type (8/16/32/64
829− -- bits). Computing the magnitude could take a long time but it's
830− -- easy to protect against that by using a timeout.
831− | e >= 0 = fromInteger c * 10 ^ e
832−
833− -- When the exponent is negative we divide the Integer coefficient
834− -- by the magnitude (10^(-e)) and convert the resulting Integer to
835− -- the target integral type.
836− --
837− -- Do note that the magnitude (which is an Integer) will consume
838− -- lots of space if the exponent is a big negative number. If left
839− -- unguarded this allows an attacker to supply a scientific number
840− -- with a big negative exponent like 1e-1000000000 and crash the
841− -- target system by filling up all memory.
842− --
843− -- We guard against this by only computing the magnitude when the
844− -- exponent is between -limit and 0 (in which case the magnitude
845− -- doesn't consume much space). If the exponent is smaller than
846− -- -limit we return 0 but only when it's smaller than
847− -- -nrOfCoefficientDigits (the number of decimal digits in the
848− -- coefficient).
849− --
850− -- Note that this still allows an attacker to trigger the unsafe
851− -- magnitude computation. However, he can only trigger it by
852− -- supplying a coefficient consisting of more decimal digits than
853− -- 10^(-e). It's easy to protect against this by limiting the
854− -- amount of bytes read from an untrusted source.
855− --
856− -- Also note that nrOfCoefficientDigits is only computed when the
857− -- exponent is smaller than -limit.
858− | e < (-limit) && e < (-nrOfCoefficientDigits) = 0
859−
860− | otherwise = fromInteger (c `div` (10^(-e)))
861− where
862− e = Scientific.base10Exponent s
863− c = Scientific.coefficient s
864−
865− limit = 20
866−
867− -- The number of decimal digits of the coefficient.
868− nrOfCoefficientDigits = integerLogBase 10 (abs c)
869−
870−-- | Calculate the integer logarithm for an arbitrary base.
871−-- The base must be greater than 1, the second argument, the number
872−-- whose logarithm is sought, should be positive, otherwise the
873−-- result is meaningless.
874−--
875−-- > base ^ integerLogBase base m <= m < base ^ (integerLogBase base m + 1)
876−--
877−-- for @base > 1@ and @m > 0@.
878−--
879−-- TODO: Use integerLogBase# from integer-gmp which is probably more efficient!
880−integerLogBase :: Integer -> Integer -> Int
881−integerLogBase b m = case step b of (_, e) -> e
882− where
883− step pw | m < pw = (m, 0)
884− | q < pw = (q, 2 * e)
885− | otherwise = (q `quot` pw, 2 * e + 1)
886− where
887− (q, e) = step (pw * pw)