[multicast_dns] Make decode more resiliant against non-utf8 fields (#38)

This commit is contained in:
Zachary Anderson
2019-10-03 09:27:12 -07:00
committed by GitHub
parent c7787ff9a4
commit 00f15da533
2 changed files with 197 additions and 23 deletions

View File

@ -162,7 +162,10 @@ _FQDNReadResult _readFQDN(
final Uint8List partBytes =
Uint8List.view(data.buffer, offset, partLength);
offset += partLength;
parts.add(utf8.decode(partBytes));
// 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 {
break;
}
@ -329,14 +332,26 @@ List<ResourceRecord> decodeMDnsResponse(List<int> packet) {
);
case ResourceRecordType.text:
checkLength(offset + readDataLength);
final Uint8List rawText = Uint8List.view(
data.buffer,
offset,
readDataLength,
);
final String text = utf8.decode(rawText);
// The first byte of the buffer is the length of the first string of
// the TXT record. Further length-prefixed strings may follow. We
// concatenate them with newlines.
final StringBuffer strings = StringBuffer();
int index = 0;
while (index < readDataLength) {
final int txtLength = data[offset + index];
index++;
if (txtLength == 0) {
continue;
}
final String text = utf8.decode(
Uint8List.view(data.buffer, offset + index, txtLength),
allowMalformed: true,
);
strings.writeln(text);
index += txtLength;
}
offset += readDataLength;
return TxtResourceRecord(fqdn, validUntil, text: text);
return TxtResourceRecord(fqdn, validUntil, text: strings.toString());
default:
checkLength(offset + readDataLength);
offset += readDataLength;
@ -366,9 +381,6 @@ List<ResourceRecord> decodeMDnsResponse(List<int> packet) {
} on MDnsDecodeException {
// If decoding fails return null.
return null;
} on FormatException {
// If decoding fails on a non-utf8 packet, return null.
return null;
}
return result;
}

View File

@ -13,7 +13,7 @@ const int _kSrvHeaderSize = 6;
void main() {
testValidPackages();
testBadPackages();
testNonUtf8Packages();
testNonUtf8DomainName();
// testHexDumpList();
testPTRRData();
testSRVRData();
@ -43,7 +43,7 @@ void testValidPackages() {
TxtResourceRecord(
'raspberrypi [b8:27:eb:03:92:4b]._workstation._tcp.local',
result[0].validUntil,
text: '\x00',
text: '',
),
PtrResourceRecord(
'_udisks-ssh._tcp.local',
@ -61,7 +61,7 @@ void testValidPackages() {
TxtResourceRecord(
'raspberrypi._udisks-ssh._tcp.local',
result[3].validUntil,
text: '\x00',
text: '',
),
PtrResourceRecord('_services._dns-sd._udp.local', result[4].validUntil,
domainName: '_udisks-ssh._tcp.local'),
@ -96,7 +96,7 @@ void testValidPackages() {
TxtResourceRecord(
'fletch-agent on raspberrypi._fletch_agent._tcp.local',
result[1].validUntil,
text: '\x00',
text: '',
),
SrvResourceRecord(
'fletch-agent on raspberrypi._fletch_agent._tcp.local',
@ -160,7 +160,25 @@ void testValidPackages() {
TxtResourceRecord(
'_______________.____._____',
result[1].validUntil,
text: '\u{14}model=MacBookPro14,3\nosxvers=18\u{12}ecolor=225,225,223',
text: 'model=MacBookPro14,3\nosxvers=18\necolor=225,225,223\n',
),
]);
});
test('Can decode packages with a long text resource', () {
final List<ResourceRecord> result = decodeMDnsResponse(packetWithLongTxt);
expect(result, isNotNull);
expect(result.length, 2);
expect(result, <ResourceRecord>[
PtrResourceRecord(
'_______________.____._____',
result[0].validUntil,
domainName: '______________________._______________.____._____',
),
TxtResourceRecord(
'_______________.____._____',
result[1].validUntil,
text: (')' * 129) + '\n',
),
]);
});
@ -176,12 +194,6 @@ void testBadPackages() {
});
}
void testNonUtf8Packages() {
test('Returns null for non-utf8 text resource', () {
expect(decodeMDnsResponse(nonUtf8Package), isNull);
});
}
void testPTRRData() {
test('Can read FQDN from PTR data', () {
expect('sgjesse-macbookpro2 [78:31:c1:b8:55:38]._workstation._tcp.local',
@ -196,6 +208,16 @@ void testSRVRData() {
});
}
void testNonUtf8DomainName() {
test('Returns non-null for non-utf8 domain name', () {
final List<ResourceRecord> result = decodeMDnsResponse(nonUtf8Package);
expect(result, isNotNull);
expect(result[0] is TxtResourceRecord, isTrue);
final TxtResourceRecord txt = result[0];
expect(txt.name, contains('<EFBFBD>'));
});
}
// One address.
const List<int> package1 = <int>[
0x00,
@ -1186,7 +1208,147 @@ const List<int> packetWithoutQuestionWithAnArCount = <int>[
51,
];
// Package with a text resource that is not valid utf8.
// This is the same as packetWithoutQuestionWithAnArCount, but the text
// resource just has a single long string. If the length isn't decoded
// separately from the string, there will be utf8 decoding failures.
const List<int> packetWithLongTxt = <int>[
0,
0,
132,
0,
0,
0,
0,
1,
0,
0,
0,
1,
15,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
4,
95,
95,
95,
95,
5,
95,
95,
95,
95,
95,
0,
0,
12,
0,
1,
0,
0,
17,
148,
0,
25,
22,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
192,
12,
22,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
12,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
95,
192,
28,
0,
16,
0,
1,
0,
0,
17,
148,
0,
51,
// Long string starts here.
129,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, // 16
41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, // 32
41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, //
41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, // 64
41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, //
41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, //
41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, //
41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, // 128,
41, // 129
];
// Package with a domain name that is not valid utf-8.
const List<int> nonUtf8Package = <int>[
0x00,
0x00,