diff --git a/packages/multicast_dns/CHANGELOG.md b/packages/multicast_dns/CHANGELOG.md index 4b23be31ce2..7a7d48a6936 100644 --- a/packages/multicast_dns/CHANGELOG.md +++ b/packages/multicast_dns/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.3.2+8 +* Fixes stack overflows ocurring during the parsing of domain names in MDNS messages. * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. ## 0.3.2+7 diff --git a/packages/multicast_dns/lib/src/packet.dart b/packages/multicast_dns/lib/src/packet.dart index dcf1402a04f..330397deaa8 100644 --- a/packages/multicast_dns/lib/src/packet.dart +++ b/packages/multicast_dns/lib/src/packet.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:math'; import 'dart:typed_data'; import 'constants.dart'; @@ -134,76 +135,55 @@ _FQDNReadResult _readFQDN( final List parts = []; final int prevOffset = offset; - while (true) { - // At least one byte is required. - checkLength(offset + 1); - - // Check for compressed. - if (data[offset] & 0xc0 == 0xc0) { - // At least two bytes are required for a compressed FQDN. - checkLength(offset + 2); - - // A compressed FQDN has a new offset in the lower 14 bits. - final _FQDNReadResult result = _readFQDN( - data, byteData, byteData.getUint16(offset) & ~0xc000, length); - parts.addAll(result.fqdnParts); - offset += 2; - break; - } else { - // A normal FQDN part has a length and a UTF-8 encoded name - // part. If the length is 0 this is the end of the FQDN. - final int partLength = data[offset]; - offset++; - if (partLength > 0) { - checkLength(offset + partLength); - final Uint8List partBytes = - Uint8List.view(data.buffer, offset, partLength); - offset += partLength; - // According to the RFC, this is supposed to be utf-8 encoded, but - // we should continue decoding even if it isn't to avoid dropping the - // rest of the data, which might still be useful. - parts.add(utf8.decode(partBytes, allowMalformed: true)); - } else { + final List offsetsToVisit = [offset]; + int upperLimitOffset = offset; + int highestOffsetRead = offset; + + while (offsetsToVisit.isNotEmpty) { + offset = offsetsToVisit.removeLast(); + + while (true) { + // At least one byte is required. + checkLength(offset + 1); + // Check for compressed. + if (data[offset] & 0xc0 == 0xc0) { + // At least two bytes are required for a compressed FQDN (see RFC1035 section 4.1.4). + checkLength(offset + 2); + + // A compressed FQDN has a new offset in the lower 14 bits. + final int pointerDest = byteData.getUint16(offset) & ~0xc000; + // Pointers can only point to prior occurances of some name. + // This check also guards against pointers that form loops. + if (pointerDest >= upperLimitOffset) { + throw MDnsDecodeException(offset); + } + upperLimitOffset = pointerDest; + offsetsToVisit.add(pointerDest); + highestOffsetRead = max(highestOffsetRead, offset + 2); break; + } else { + // A normal FQDN part has a length and a UTF-8 encoded name + // part. If the length is 0 this is the end of the FQDN. + final int partLength = data[offset]; + offset++; + if (partLength > 0) { + checkLength(offset + partLength); + final Uint8List partBytes = + Uint8List.view(data.buffer, offset, partLength); + offset += partLength; + // According to the RFC, this is supposed to be utf-8 encoded, but + // we should continue decoding even if it isn't to avoid dropping the + // rest of the data, which might still be useful. + parts.add(utf8.decode(partBytes, allowMalformed: true)); + highestOffsetRead = max(highestOffsetRead, offset); + } else { + highestOffsetRead = max(highestOffsetRead, offset); + break; + } } } } - return _FQDNReadResult(parts, offset - prevOffset); -} - -/// Decode an mDNS query packet. -/// -/// If decoding fails (e.g. due to an invalid packet), `null` is returned. -/// -/// See https://tools.ietf.org/html/rfc1035 for format. -ResourceRecordQuery? decodeMDnsQuery(List packet) { - final int length = packet.length; - if (length < _kHeaderSize) { - return null; - } - - final Uint8List data = - packet is Uint8List ? packet : Uint8List.fromList(packet); - final ByteData packetBytes = ByteData.view(data.buffer); - - // Check whether it's a query. - final int flags = packetBytes.getUint16(_kFlagsOffset); - if (flags != 0) { - return null; - } - final int questionCount = packetBytes.getUint16(_kQdcountOffset); - if (questionCount == 0) { - return null; - } - - final _FQDNReadResult fqdn = - _readFQDN(data, packetBytes, _kHeaderSize, data.length); - - int offset = _kHeaderSize + fqdn.bytesRead; - final int type = packetBytes.getUint16(offset); - offset += 2; - final int queryType = packetBytes.getUint16(offset) & 0x8000; - return ResourceRecordQuery(type, fqdn.fqdn, queryType); + return _FQDNReadResult(parts, highestOffsetRead - prevOffset); } /// Decode an mDNS response packet. diff --git a/packages/multicast_dns/pubspec.yaml b/packages/multicast_dns/pubspec.yaml index 6f0f3052cc0..3303a8fb944 100644 --- a/packages/multicast_dns/pubspec.yaml +++ b/packages/multicast_dns/pubspec.yaml @@ -2,7 +2,7 @@ name: multicast_dns description: Dart package for performing mDNS queries (e.g. Bonjour, Avahi). repository: https://github.com/flutter/packages/tree/main/packages/multicast_dns issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+multicast_dns%22 -version: 0.3.2+7 +version: 0.3.2+8 environment: sdk: ^3.4.0 diff --git a/packages/multicast_dns/test/decode_test.dart b/packages/multicast_dns/test/decode_test.dart index 32d7c2fd0c8..358f764eba9 100644 --- a/packages/multicast_dns/test/decode_test.dart +++ b/packages/multicast_dns/test/decode_test.dart @@ -192,6 +192,10 @@ void testBadPackages() { } } }); + + test('Detects cyclic pointers and returns null', () { + expect(decodeMDnsResponse(cycle), isNull); + }); } void testPTRRData() { @@ -584,6 +588,42 @@ const List package3 = [ 0x2c ]; +/// Contains compressed domain names where a there is a cycle amongst the +/// offset pointers. +const List cycle = [ + 0x00, + 0x00, + 0x84, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, // "example" + 0xC0, 0x16, // Pointer to "com" + 0x03, 0x63, 0x6f, 0x6d, // "com" + 0xC0, 0x0c, // Pointer to "example" + 0x00, + 0x00, + 0x01, + 0x80, + 0x01, + 0x00, + 0x00, + 0x00, + 0x78, + 0x00, + 0x04, + 0xc0, + 0xa8, + 0x01, + 0xbf +]; + const List packagePtrResponse = [ 0x00, 0x00,