diff options
author | Sam Truzjan <pxqr.sta@gmail.com> | 2013-12-11 02:12:08 +0400 |
---|---|---|
committer | Sam Truzjan <pxqr.sta@gmail.com> | 2013-12-11 02:12:08 +0400 |
commit | 2defefa35ba45ca773160c8c4b01c73c61fc3fc0 (patch) | |
tree | e983cdf3cca48ba6031b7b5c955e03fe50e4849f /src/Network/BitTorrent/Exchange/Message.hs | |
parent | caa5e1b2eed5488eac08e6e4b7f129fe6ba2ae1b (diff) |
Add protection against too long messages
Diffstat (limited to 'src/Network/BitTorrent/Exchange/Message.hs')
-rw-r--r-- | src/Network/BitTorrent/Exchange/Message.hs | 123 |
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 | ||
85 | import Control.Applicative | 85 | import Control.Applicative |
86 | import Control.Arrow ((&&&), (***)) | 86 | import Control.Arrow ((&&&), (***)) |
87 | import Control.Monad (when) | ||
87 | import Data.Attoparsec.ByteString.Char8 as BS | 88 | import Data.Attoparsec.ByteString.Char8 as BS |
88 | import Data.BEncode as BE | 89 | import Data.BEncode as BE |
89 | import Data.BEncode.BDict as BE | 90 | import 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 | |||
839 | getMetadataPayload (MetadataData (Data.Piece _ bs) _) = Just bs | 841 | getMetadataPayload (MetadataData (Data.Piece _ bs) _) = Just bs |
840 | getMetadataPayload _ = Nothing | 842 | getMetadataPayload _ = 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 | -- | ||
851 | maxMetadataBDictSize :: Int | ||
852 | maxMetadataBDictSize = 16 * 1024 | ||
853 | |||
854 | maxMetadataSize :: Int | ||
855 | maxMetadataSize = 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 |
846 | getMetadata :: Int -> S.Get ExtendedMetadata | 861 | getMetadata :: Int -> S.Get ExtendedMetadata |
847 | getMetadata len = do | 862 | getMetadata 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 | ||
857 | putMetadata :: ExtendedMetadata -> BL.ByteString | 879 | putMetadata :: ExtendedMetadata -> BL.ByteString |
858 | putMetadata msg | 880 | putMetadata msg |
@@ -972,9 +994,48 @@ putInt :: S.Putter Int | |||
972 | putInt = S.putWord32be . fromIntegral | 994 | putInt = 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 | -- | ||
1005 | maxMessageSize :: Int | ||
1006 | maxMessageSize = 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 | -- | ||
1014 | maxBitfieldSize :: Int | ||
1015 | maxBitfieldSize = 1024 * 1024 | ||
1016 | |||
1017 | getBitfield :: Int -> S.Get Bitfield | ||
1018 | getBitfield len | ||
1019 | | len > maxBitfieldSize = fail "BITFIELD message size exceeded limit" | ||
1020 | | otherwise = fromBitmap <$> getByteString len | ||
1021 | |||
1022 | maxBlockSize :: Int | ||
1023 | maxBlockSize = 4 * defaultTransferSize | ||
1024 | |||
1025 | getBlock :: Int -> S.Get (Block BL.ByteString) | ||
1026 | getBlock len | ||
1027 | | len > maxBlockSize = fail "BLOCK message size exceeded limit" | ||
1028 | | otherwise = Block <$> getInt <*> getInt | ||
1029 | <*> getLazyByteString (fromIntegral len) | ||
1030 | {-# INLINE getBlock #-} | ||
1031 | |||
975 | instance Serialize Message where | 1032 | instance 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 | |||
1061 | putFast (RejectRequest i ) = putInt 13 >> putWord8 0x10 >> put i | 1115 | putFast (RejectRequest i ) = putInt 13 >> putWord8 0x10 >> put i |
1062 | putFast (AllowedFast i ) = putInt 5 >> putWord8 0x11 >> putInt i | 1116 | putFast (AllowedFast i ) = putInt 5 >> putWord8 0x11 >> putInt i |
1063 | 1117 | ||
1118 | maxEHandshakeSize :: Int | ||
1119 | maxEHandshakeSize = 16 * 1024 | ||
1120 | |||
1064 | getExtendedHandshake :: Int -> S.Get ExtendedHandshake | 1121 | getExtendedHandshake :: Int -> S.Get ExtendedHandshake |
1065 | getExtendedHandshake messageSize = do | 1122 | getExtendedHandshake 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 | |||
1129 | maxEUnknownSize :: Int | ||
1130 | maxEUnknownSize = 64 * 1024 | ||
1131 | |||
1132 | getExtendedUnknown :: Int -> S.Get BS.ByteString | ||
1133 | getExtendedUnknown len | ||
1134 | | len > maxEUnknownSize = fail "unknown extended message size exceeded limit" | ||
1135 | | otherwise = getByteString len | ||
1068 | 1136 | ||
1069 | getExtendedMessage :: Int -> S.Get ExtendedMessage | 1137 | getExtendedMessage :: Int -> S.Get ExtendedMessage |
1070 | getExtendedMessage messageSize = do | 1138 | getExtendedMessage 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. | ||
1078 | extendedMessageId :: MessageId | 1147 | extendedMessageId :: MessageId |
1079 | extendedMessageId = 20 | 1148 | extendedMessageId = 20 |
1080 | 1149 | ||