@@ -632,6 +632,10 @@ def test_dns_compression_rollback_for_corruption():
632632 # ensure there is no corruption with the dns compression
633633 incoming = r .DNSIncoming (packet )
634634 assert incoming .valid is True
635+ assert (
636+ len (incoming .answers )
637+ == incoming .num_answers + incoming .num_authorities + incoming .num_additionals
638+ )
635639
636640
637641def test_tc_bit_in_query_packet ():
@@ -761,3 +765,225 @@ def test_records_same_packet_share_fate():
761765 first_time = dnsin .answers [0 ].created
762766 for answer in dnsin .answers :
763767 assert answer .created == first_time
768+
769+
770+ def test_dns_compression_invalid_skips_bad_name_compress_in_question ():
771+ """Test our wire parser can skip bad compression in questions."""
772+ packet = (
773+ b'\x00 \x00 \x00 \x00 \x00 \x04 \x00 \x00 \x00 \x07 \x00 \x00 \x11 homeassistant1128\x05 l'
774+ b'ocal\x00 \x00 \xff \x00 \x01 4homeassistant1128 [534a4794e5ed41879ecf012252d3e02'
775+ b'a]\x0c _workstation\x04 _tcp\xc0 \x1e \x00 \xff \x00 \x01 4homeassistant1127 [534a47'
776+ b'94e5ed41879ecf012252d3e02a]\xc0 ^\x00 \xff \x00 \x01 4homeassistant1123 [534a479'
777+ b'4e5ed41879ecf012252d3e02a]\xc0 ^\x00 \xff \x00 \x01 4homeassistant1118 [534a4794'
778+ b'e5ed41879ecf012252d3e02a]\xc0 ^\x00 \xff \x00 \x01 \xc0 \x0c \x00 \x01 \x80 '
779+ b'\x01 \x00 \x00 \x00 x\x00 \x04 \xc0 \xa8 <\xc3 \xc0 v\x00 \x10 \x80 \x01 \x00 \x00 \x00 '
780+ b'x\x00 \x01 \x00 \xc0 v\x00 !\x80 \x01 \x00 \x00 \x00 x\x00 \x1f \x00 \x00 \x00 \x00 '
781+ b'\x00 \x00 \x11 homeassistant1127\x05 local\x00 \xc0 \xb1 \x00 \x10 \x80 '
782+ b'\x01 \x00 \x00 \x00 x\x00 \x01 \x00 \xc0 \xb1 \x00 !\x80 \x01 \x00 \x00 \x00 x\x00 \x1f '
783+ b'\x00 \x00 \x00 \x00 \x00 \x00 \x11 homeassistant1123\x05 local\x00 \xc0 )\x00 \x10 \x80 '
784+ b'\x01 \x00 \x00 \x00 x\x00 \x01 \x00 \xc0 )\x00 !\x80 \x01 \x00 \x00 \x00 x\x00 \x1f '
785+ b'\x00 \x00 \x00 \x00 \x00 \x00 \x11 homeassistant1128\x05 local\x00 '
786+ )
787+ parsed = r .DNSIncoming (packet )
788+ assert len (parsed .questions ) == 4
789+
790+
791+ def test_dns_compression_all_invalid ():
792+ """Test our wire parser can skip all invalid data."""
793+ packet = (
794+ b'\x00 \x00 \x84 \x00 \x00 \x00 \x00 \x01 \x00 \x00 \x00 \x00 !roborock-vacuum-s5e_miio416'
795+ b'112328\x00 \x00 /\x80 \x01 \x00 \x00 \x00 x\x00 \t \xc0 P\x00 \x05 @\x00 \x00 \x00 \x00 '
796+ )
797+ parsed = r .DNSIncoming (packet )
798+ assert len (parsed .questions ) == 0
799+ assert len (parsed .answers ) == 0
800+
801+
802+ def test_invalid_next_name_ignored ():
803+ """Test our wire parser does not throw an an invalid next name.
804+
805+ The RFC states it should be ignored when used with mDNS.
806+ """
807+ packet = (
808+ b'\x00 \x00 \x00 \x00 \x00 \x01 \x00 \x02 \x00 \x00 \x00 \x00 \x07 Android\x05 local\x00 \x00 '
809+ b'\xff \x00 \x01 \xc0 \x0c \x00 /\x00 \x01 \x00 \x00 \x00 x\x00 \x08 \xc0 2\x00 \x04 @'
810+ b'\x00 \x00 \x08 \xc0 \x0c \x00 \x01 \x00 \x01 \x00 \x00 \x00 x\x00 \x04 \xc0 \xa8 X<'
811+ )
812+ parsed = r .DNSIncoming (packet )
813+ assert len (parsed .questions ) == 1
814+ assert len (parsed .answers ) == 2
815+
816+
817+ def test_dns_compression_invalid_skips_record ():
818+ """Test our wire parser can skip records we do not know how to parse."""
819+ packet = (
820+ b"\x00 \x00 \x84 \x00 \x00 \x00 \x00 \x06 \x00 \x00 \x00 \x00 \x04 _hap\x04 _tcp\x05 local\x00 \x00 \x0c "
821+ b"\x00 \x01 \x00 \x00 \x11 \x94 \x00 \x16 \x13 eufy HomeBase2-2464\xc0 \x0c \x04 Eufy\xc0 \x16 \x00 /"
822+ b"\x80 \x01 \x00 \x00 \x00 x\x00 \x08 \xc0 \xa6 \x00 \x04 @\x00 \x00 \x08 \xc0 '\x00 /\x80 \x01 \x00 \x00 "
823+ b"\x11 \x94 \x00 \t \xc0 '\x00 \x05 \x00 \x00 \x80 \x00 @\xc0 =\x00 \x01 \x80 \x01 \x00 \x00 \x00 x\x00 \x04 "
824+ b"\xc0 \xa8 Dp\xc0 '\x00 !\x80 \x01 \x00 \x00 \x00 x\x00 \x08 \x00 \x00 \x00 \x00 \xd1 _\xc0 =\xc0 '\x00 "
825+ b"\x10 \x80 \x01 \x00 \x00 \x11 \x94 \x00 K\x04 c#=1\x04 ff=2\x14 id=38:71:4F:6B:76:00\x08 md=T8010"
826+ b"\x06 pv=1.1\x05 s#=75\x04 sf=1\x04 ci=2\x0b sh=xaQk4g=="
827+ )
828+ parsed = r .DNSIncoming (packet )
829+ answer = r .DNSNsec (
830+ 'eufy HomeBase2-2464._hap._tcp.local.' ,
831+ const ._TYPE_NSEC ,
832+ const ._CLASS_IN | const ._CLASS_UNIQUE ,
833+ const ._DNS_OTHER_TTL ,
834+ 'eufy HomeBase2-2464._hap._tcp.local.' ,
835+ [const ._TYPE_TXT , const ._TYPE_SRV ],
836+ )
837+ assert answer in parsed .answers
838+
839+
840+ def test_dns_compression_points_forward ():
841+ """Test our wire parser can unpack nsec records with compression."""
842+ packet = (
843+ b"\x00 \x00 \x84 \x00 \x00 \x00 \x00 \x07 \x00 \x00 \x00 \x00 \x0e TV Beneden (2)"
844+ b"\x10 _androidtvremote\x04 _tcp\x05 local\x00 \x00 \x10 \x80 \x01 \x00 \x00 \x11 "
845+ b"\x94 \x00 \x15 \x14 bt=D8:13:99:AC:98:F1\xc0 \x0c \x00 /\x80 \x01 \x00 \x00 \x11 "
846+ b"\x94 \x00 \t \xc0 \x0c \x00 \x05 \x00 \x00 \x80 \x00 @\t Android-3\xc0 1\x00 /\x80 "
847+ b"\x01 \x00 \x00 \x00 x\x00 \x08 \xc0 \x9c \x00 \x04 @\x00 \x00 \x08 \xc0 l\x00 \x01 \x80 "
848+ b"\x01 \x00 \x00 \x00 x\x00 \x04 \xc0 \xa8 X\x0f \xc0 \x0c \x00 !\x80 \x01 \x00 \x00 \x00 "
849+ b"x\x00 \x08 \x00 \x00 \x00 \x00 \x19 B\xc0 l\xc0 \x1b \x00 \x0c \x00 \x01 \x00 \x00 \x11 "
850+ b"\x94 \x00 \x02 \xc0 \x0c \t _services\x07 _dns-sd\x04 _udp\xc0 1\x00 \x0c \x00 \x01 "
851+ b"\x00 \x00 \x11 \x94 \x00 \x02 \xc0 \x1b "
852+ )
853+ parsed = r .DNSIncoming (packet )
854+ answer = r .DNSNsec (
855+ 'TV Beneden (2)._androidtvremote._tcp.local.' ,
856+ const ._TYPE_NSEC ,
857+ const ._CLASS_IN | const ._CLASS_UNIQUE ,
858+ const ._DNS_OTHER_TTL ,
859+ 'TV Beneden (2)._androidtvremote._tcp.local.' ,
860+ [const ._TYPE_TXT , const ._TYPE_SRV ],
861+ )
862+ assert answer in parsed .answers
863+
864+
865+ def test_dns_compression_points_to_itself ():
866+ """Test our wire parser does not loop forever when a compression pointer points to itself."""
867+ packet = (
868+ b"\x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x02 \x06 domain\x05 local\x00 \x00 \x01 "
869+ b"\x80 \x01 \x00 \x00 \x00 \x01 \x00 \x04 \xc0 \xa8 \xd0 \x05 \xc0 (\x00 \x01 \x80 \x01 \x00 \x00 \x00 "
870+ b"\x01 \x00 \x04 \xc0 \xa8 \xd0 \x06 "
871+ )
872+ parsed = r .DNSIncoming (packet )
873+ assert len (parsed .answers ) == 1
874+
875+
876+ def test_dns_compression_points_beyond_packet ():
877+ """Test our wire parser does not fail when the compression pointer points beyond the packet."""
878+ packet = (
879+ b'\x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x02 \x06 domain\x05 local\x00 \x00 \x01 '
880+ b'\x80 \x01 \x00 \x00 \x00 \x01 \x00 \x04 \xc0 \xa8 \xd0 \x05 \xe7 \x0f \x00 \x01 \x80 \x01 \x00 \x00 '
881+ b'\x00 \x01 \x00 \x04 \xc0 \xa8 \xd0 \x06 '
882+ )
883+ parsed = r .DNSIncoming (packet )
884+ assert len (parsed .answers ) == 1
885+
886+
887+ def test_dns_compression_generic_failure ():
888+ """Test our wire parser does not loop forever when dns compression is corrupt."""
889+ packet = (
890+ b'\x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x02 \x06 domain\x05 local\x00 \x00 \x01 '
891+ b'\x80 \x01 \x00 \x00 \x00 \x01 \x00 \x04 \xc0 \xa8 \xd0 \x05 -\x0c \x00 \x01 \x80 \x01 \x00 \x00 '
892+ b'\x00 \x01 \x00 \x04 \xc0 \xa8 \xd0 \x06 '
893+ )
894+ parsed = r .DNSIncoming (packet )
895+ assert len (parsed .answers ) == 1
896+
897+
898+ def test_label_length_attack ():
899+ """Test our wire parser does not loop forever when the name exceeds 253 chars."""
900+ packet = (
901+ b'\x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x02 \x01 d\x01 d\x01 d\x01 d\x01 d\x01 d'
902+ b'\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d'
903+ b'\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d'
904+ b'\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d'
905+ b'\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d'
906+ b'\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d'
907+ b'\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d'
908+ b'\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d'
909+ b'\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x01 d\x00 \x00 \x01 \x80 '
910+ b'\x01 \x00 \x00 \x00 \x01 \x00 \x04 \xc0 \xa8 \xd0 \x05 \xc0 \x0c \x00 \x01 \x80 \x01 \x00 \x00 \x00 '
911+ b'\x01 \x00 \x04 \xc0 \xa8 \xd0 \x06 '
912+ )
913+ parsed = r .DNSIncoming (packet )
914+ assert len (parsed .answers ) == 0
915+
916+
917+ def test_label_compression_attack ():
918+ """Test our wire parser does not loop forever when exceeding the maximum number of labels."""
919+ packet = (
920+ b'\x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x02 \x03 atk\x00 \x00 \x01 \x80 '
921+ b'\x01 \x00 \x00 \x00 \x01 \x00 \x04 \xc0 \xa8 \xd0 \x05 \x03 atk\x03 atk\x03 atk\x03 atk\x03 '
922+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
923+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
924+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
925+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
926+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
927+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
928+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
929+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
930+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
931+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
932+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
933+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
934+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
935+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
936+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
937+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
938+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
939+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 '
940+ b'atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\x03 atk\xc0 '
941+ b'\x0c \x00 \x01 \x80 \x01 \x00 \x00 \x00 \x01 \x00 \x04 \xc0 \xa8 \xd0 \x06 '
942+ )
943+ parsed = r .DNSIncoming (packet )
944+ assert len (parsed .answers ) == 1
945+
946+
947+ def test_dns_compression_loop_attack ():
948+ """Test our wire parser does not loop forever when dns compression is in a loop."""
949+ packet = (
950+ b'\x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x07 \x03 atk\x03 dns\x05 loc'
951+ b'al\xc0 \x10 \x00 \x01 \x80 \x01 \x00 \x00 \x00 \x01 \x00 \x04 \xc0 \xa8 \xd0 \x05 \x04 a'
952+ b'tk2\x04 dns2\xc0 \x14 \x00 \x01 \x80 \x01 \x00 \x00 \x00 \x01 \x00 \x04 \xc0 \xa8 \xd0 \x05 '
953+ b'\x04 atk3\xc0 \x10 \x00 \x01 \x80 \x01 \x00 \x00 \x00 \x01 \x00 \x04 \xc0 \xa8 \xd0 '
954+ b'\x05 \x04 atk4\x04 dns5\xc0 \x14 \x00 \x01 \x80 \x01 \x00 \x00 \x00 \x01 \x00 \x04 \xc0 '
955+ b'\xa8 \xd0 \x05 \x04 atk5\x04 dns2\xc0 ^\x00 \x01 \x80 \x01 \x00 \x00 \x00 \x01 \x00 '
956+ b'\x04 \xc0 \xa8 \xd0 \x05 \xc0 s\x00 \x01 \x80 \x01 \x00 \x00 \x00 \x01 \x00 '
957+ b'\x04 \xc0 \xa8 \xd0 \x05 \xc0 s\x00 \x01 \x80 \x01 \x00 \x00 \x00 \x01 \x00 '
958+ b'\x04 \xc0 \xa8 \xd0 \x05 '
959+ )
960+ parsed = r .DNSIncoming (packet )
961+ assert len (parsed .answers ) == 0
962+
963+
964+ def test_txt_after_invalid_nsec_name_still_usable ():
965+ """Test that we can see the txt record after the invalid nsec record."""
966+ packet = (
967+ b'\x00 \x00 \x84 \x00 \x00 \x00 \x00 \x06 \x00 \x00 \x00 \x00 \x06 _sonos\x04 _tcp\x05 loc'
968+ b'al\x00 \x00 \x0c \x00 \x01 \x00 \x00 \x11 \x94 \x00 \x15 \x12 Sonos-542A1BC9220E'
969+ b'\xc0 \x0c \x12 Sonos-542A1BC9220E\xc0 \x18 \x00 /\x80 \x01 \x00 \x00 \x00 x\x00 '
970+ b'\x08 \xc1 t\x00 \x04 @\x00 \x00 \x08 \xc0 )\x00 /\x80 \x01 \x00 \x00 \x11 \x94 \x00 '
971+ b'\t \xc0 )\x00 \x05 \x00 \x00 \x80 \x00 @\xc0 )\x00 !\x80 \x01 \x00 \x00 \x00 x'
972+ b'\x00 \x08 \x00 \x00 \x00 \x00 \x05 \xa3 \xc0 >\xc0 >\x00 \x01 \x80 \x01 \x00 \x00 \x00 x'
973+ b'\x00 \x04 \xc0 \xa8 \x02 :\xc0 )\x00 \x10 \x80 \x01 \x00 \x00 \x11 \x94 \x01 *2info=/api'
974+ b'/v1/players/RINCON_542A1BC9220E01400/info\x06 vers=3\x10 protovers=1.24.1\n bo'
975+ b'otseq=11%hhid=Sonos_rYn9K9DLXJe0f3LP9747lbvFvh;mhhid=Sonos_rYn9K9DLXJe0f3LP9'
976+ b'747lbvFvh.Q45RuMaeC07rfXh7OJGm<location=http://192.168.2.58:1400/xml/device_'
977+ b'description.xml\x0c sslport=1443\x0e hhsslport=1843\t variant=2\x0e mdnssequen'
978+ b'ce=0'
979+ )
980+ parsed = r .DNSIncoming (packet )
981+ # The NSEC record with the invalid name compression should be skipped
982+ assert parsed .answers [4 ].text == (
983+ b'2info=/api/v1/players/RINCON_542A1BC9220E01400/info\x06 vers=3\x10 protovers'
984+ b'=1.24.1\n bootseq=11%hhid=Sonos_rYn9K9DLXJe0f3LP9747lbvFvh;mhhid=Sonos_rYn'
985+ b'9K9DLXJe0f3LP9747lbvFvh.Q45RuMaeC07rfXh7OJGm<location=http://192.168.2.58:14'
986+ b'00/xml/device_description.xml\x0c sslport=1443\x0e hhsslport=1843\t varian'
987+ b't=2\x0e mdnssequence=0'
988+ )
989+ assert len (parsed .answers ) == 5
0 commit comments