summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Network/BitTorrent/Exchange/Message.hs123
1 files changed, 96 insertions, 27 deletions
diff --git a/src/Network/BitTorrent/Exchange/Message.hs b/src/Network/BitTorrent/Exchange/Message.hs
index c3f6818f..3fe3f1bf 100644
--- a/src/Network/BitTorrent/Exchange/Message.hs
+++ b/src/Network/BitTorrent/Exchange/Message.hs
@@ -84,6 +84,7 @@ module Network.BitTorrent.Exchange.Message
84 84
85import Control.Applicative 85import Control.Applicative
86import Control.Arrow ((&&&), (***)) 86import Control.Arrow ((&&&), (***))
87import Control.Monad (when)
87import Data.Attoparsec.ByteString.Char8 as BS 88import Data.Attoparsec.ByteString.Char8 as BS
88import Data.BEncode as BE 89import Data.BEncode as BE
89import Data.BEncode.BDict as BE 90import Data.BEncode.BDict as BE
@@ -642,7 +643,7 @@ data ExtendedHandshake = ExtendedHandshake
642 , ehsCaps :: ExtendedCaps 643 , ehsCaps :: ExtendedCaps
643 644
644 -- | Size of 'Data.Torrent.InfoDict' in bytes. This field should 645 -- | Size of 'Data.Torrent.InfoDict' in bytes. This field should
645 -- be added if ExtMetadata is enabled in current session /and/ 646 -- be added if 'ExtMetadata' is enabled in current session /and/
646 -- peer have the torrent file. 647 -- peer have the torrent file.
647 , ehsMetadataSize :: Maybe Int 648 , ehsMetadataSize :: Maybe Int
648 649
@@ -757,7 +758,8 @@ data ExtendedMetadata
757 -- factor. 758 -- factor.
758 | MetadataReject PieceIx 759 | MetadataReject PieceIx
759 760
760 -- | Reserved. 761 -- | Reserved. By specification we should ignore unknown metadata
762 -- messages.
761 | MetadataUnknown BValue 763 | MetadataUnknown BValue
762 deriving (Show, Eq, Typeable) 764 deriving (Show, Eq, Typeable)
763 765
@@ -839,20 +841,40 @@ getMetadataPayload :: ExtendedMetadata -> Maybe BS.ByteString
839getMetadataPayload (MetadataData (Data.Piece _ bs) _) = Just bs 841getMetadataPayload (MetadataData (Data.Piece _ bs) _) = Just bs
840getMetadataPayload _ = Nothing 842getMetadataPayload _ = Nothing
841 843
844-- | Metadata BDict usually contain only 'msg_type_key', 'piece_key'
845-- and 'total_size_key' fields so it normally should take less than
846-- 100 bytes. This limit is two order of magnitude larger to be
847-- permissive to 'MetadataUnknown' messages.
848--
849-- See 'maxMessageSize' for further explanation.
850--
851maxMetadataBDictSize :: Int
852maxMetadataBDictSize = 16 * 1024
853
854maxMetadataSize :: Int
855maxMetadataSize = maxMetadataBDictSize + metadataPieceSize
856
842-- to make MetadataData constructor fields a little bit prettier we 857-- to make MetadataData constructor fields a little bit prettier we
843-- cheat here: first we read empty 'pieceData' from bdict, but then we 858-- cheat here: first we read empty 'pieceData' from bdict, but then we
844-- fill that field with the actual piece data — trailing bytes of 859-- fill that field with the actual piece data — trailing bytes of
845-- the message 860-- the message
846getMetadata :: Int -> S.Get ExtendedMetadata 861getMetadata :: Int -> S.Get ExtendedMetadata
847getMetadata len = do 862getMetadata len
848 bs <- getByteString len 863 | len > maxMetadataSize = fail $ parseError "size exceeded limit"
849 case BS.parse BE.parser bs of 864 | otherwise = do
850 BS.Fail _ _ _ -> fail "unable to parse metadata bdict: possible corrupted" 865 bs <- getByteString len
851 BS.Partial c -> fail "unable to parse metadata bdict: not enough bytes" 866 parseRes $ BS.parse BE.parser bs
852 BS.Done piece bvalueBS -> do 867 where
853 let msg = "metadata dictionary is invalid" 868 parseError reason = "unable to parse metadata message: " ++ reason
854 metadata <- either (fail msg) pure $ fromBEncode bvalueBS 869
855 pure $ setMetadataPayload piece metadata 870 parseRes (BS.Fail _ _ _) = fail $ parseError "bdict: possible corrupted"
871 parseRes (BS.Partial c) = fail $ parseError "bdict: not enough bytes"
872 parseRes (BS.Done piece bvalueBS)
873 | BS.length piece > metadataPieceSize
874 = fail "infodict piece: size exceeded limit"
875 | otherwise = do
876 metadata <- either (fail . parseError) pure $ fromBEncode bvalueBS
877 return $ setMetadataPayload piece metadata
856 878
857putMetadata :: ExtendedMetadata -> BL.ByteString 879putMetadata :: ExtendedMetadata -> BL.ByteString
858putMetadata msg 880putMetadata msg
@@ -972,9 +994,48 @@ putInt :: S.Putter Int
972putInt = S.putWord32be . fromIntegral 994putInt = S.putWord32be . fromIntegral
973{-# INLINE putInt #-} 995{-# INLINE putInt #-}
974 996
997-- | This limit should protect against "out-of-memory" attacks: if a
998-- malicious peer have sent a long varlength message then receiver can
999-- accumulate too long bytestring in the 'Get'.
1000--
1001-- Normal messages should never exceed this limits.
1002--
1003-- See also 'maxBitfieldSize', 'maxBlockSize' limits.
1004--
1005maxMessageSize :: Int
1006maxMessageSize = 20 + 1024 * 1024
1007
1008-- | This also limit max torrent size to:
1009--
1010-- max_bitfield_size * piece_ix_per_byte * max_piece_size =
1011-- 2 ^ 20 * 8 * 1MB =
1012-- 8TB
1013--
1014maxBitfieldSize :: Int
1015maxBitfieldSize = 1024 * 1024
1016
1017getBitfield :: Int -> S.Get Bitfield
1018getBitfield len
1019 | len > maxBitfieldSize = fail "BITFIELD message size exceeded limit"
1020 | otherwise = fromBitmap <$> getByteString len
1021
1022maxBlockSize :: Int
1023maxBlockSize = 4 * defaultTransferSize
1024
1025getBlock :: Int -> S.Get (Block BL.ByteString)
1026getBlock len
1027 | len > maxBlockSize = fail "BLOCK message size exceeded limit"
1028 | otherwise = Block <$> getInt <*> getInt
1029 <*> getLazyByteString (fromIntegral len)
1030{-# INLINE getBlock #-}
1031
975instance Serialize Message where 1032instance Serialize Message where
976 get = do 1033 get = do
977 len <- getInt 1034 len <- getInt
1035
1036 when (len > maxMessageSize) $ do
1037 fail "message body size exceeded the limit"
1038
978 if len == 0 then return KeepAlive 1039 if len == 0 then return KeepAlive
979 else do 1040 else do
980 mid <- S.getWord8 1041 mid <- S.getWord8
@@ -983,12 +1044,11 @@ instance Serialize Message where
983 0x01 -> return $ Status (Choking False) 1044 0x01 -> return $ Status (Choking False)
984 0x02 -> return $ Status (Interested True) 1045 0x02 -> return $ Status (Interested True)
985 0x03 -> return $ Status (Interested False) 1046 0x03 -> return $ Status (Interested False)
986 0x04 -> (Available . Have) <$> getInt 1047 0x04 -> (Available . Have) <$> getInt
987 0x05 -> (Available . Bitfield . fromBitmap) 1048 0x05 -> (Available . Bitfield) <$> getBitfield (pred len)
988 <$> S.getByteString (pred len) 1049 0x06 -> (Transfer . Request) <$> S.get
989 0x06 -> (Transfer . Request) <$> S.get 1050 0x07 -> (Transfer . Piece) <$> getBlock (len - 9)
990 0x07 -> (Transfer . Piece) <$> getBlock (len - 9) 1051 0x08 -> (Transfer . Cancel) <$> S.get
991 0x08 -> (Transfer . Cancel) <$> S.get
992 0x09 -> Port <$> S.get 1052 0x09 -> Port <$> S.get
993 0x0D -> (Fast . SuggestPiece) <$> getInt 1053 0x0D -> (Fast . SuggestPiece) <$> getInt
994 0x0E -> return $ Fast HaveAll 1054 0x0E -> return $ Fast HaveAll
@@ -1001,12 +1061,6 @@ instance Serialize Message where
1001 fail $ "unknown message ID: " ++ show mid ++ "\n" 1061 fail $ "unknown message ID: " ++ show mid ++ "\n"
1002 ++ "remaining available bytes: " ++ show rm 1062 ++ "remaining available bytes: " ++ show rm
1003 1063
1004 where
1005 getBlock :: Int -> S.Get (Block BL.ByteString)
1006 getBlock len = Block <$> getInt <*> getInt
1007 <*> S.getLazyByteString (fromIntegral len)
1008 {-# INLINE getBlock #-}
1009
1010 put KeepAlive = putInt 0 1064 put KeepAlive = putInt 0
1011 put (Status msg) = putStatus msg 1065 put (Status msg) = putStatus msg
1012 put (Available msg) = putAvailable msg 1066 put (Available msg) = putAvailable msg
@@ -1061,10 +1115,24 @@ putFast (SuggestPiece pix) = putInt 5 >> putWord8 0x0D >> putInt pix
1061putFast (RejectRequest i ) = putInt 13 >> putWord8 0x10 >> put i 1115putFast (RejectRequest i ) = putInt 13 >> putWord8 0x10 >> put i
1062putFast (AllowedFast i ) = putInt 5 >> putWord8 0x11 >> putInt i 1116putFast (AllowedFast i ) = putInt 5 >> putWord8 0x11 >> putInt i
1063 1117
1118maxEHandshakeSize :: Int
1119maxEHandshakeSize = 16 * 1024
1120
1064getExtendedHandshake :: Int -> S.Get ExtendedHandshake 1121getExtendedHandshake :: Int -> S.Get ExtendedHandshake
1065getExtendedHandshake messageSize = do 1122getExtendedHandshake messageSize
1066 bs <- getByteString messageSize 1123 | messageSize > maxEHandshakeSize
1067 either fail pure $ BE.decode bs 1124 = fail "extended handshake size exceeded limit"
1125 | otherwise = do
1126 bs <- getByteString messageSize
1127 either fail pure $ BE.decode bs
1128
1129maxEUnknownSize :: Int
1130maxEUnknownSize = 64 * 1024
1131
1132getExtendedUnknown :: Int -> S.Get BS.ByteString
1133getExtendedUnknown len
1134 | len > maxEUnknownSize = fail "unknown extended message size exceeded limit"
1135 | otherwise = getByteString len
1068 1136
1069getExtendedMessage :: Int -> S.Get ExtendedMessage 1137getExtendedMessage :: Int -> S.Get ExtendedMessage
1070getExtendedMessage messageSize = do 1138getExtendedMessage messageSize = do
@@ -1073,8 +1141,9 @@ getExtendedMessage messageSize = do
1073 case msgId of 1141 case msgId of
1074 0 -> EHandshake <$> getExtendedHandshake msgBodySize 1142 0 -> EHandshake <$> getExtendedHandshake msgBodySize
1075 1 -> EMetadata msgId <$> getMetadata msgBodySize 1143 1 -> EMetadata msgId <$> getMetadata msgBodySize
1076 _ -> EUnknown msgId <$> getByteString msgBodySize 1144 _ -> EUnknown msgId <$> getExtendedUnknown msgBodySize
1077 1145
1146-- | By spec.
1078extendedMessageId :: MessageId 1147extendedMessageId :: MessageId
1079extendedMessageId = 20 1148extendedMessageId = 20
1080 1149