Skip to content

Commit c27c42c

Browse files
authored
Merge pull request #117 from clue-labs/incomplete
Reject parsing malformed record data and incomplete DNS response messages
2 parents 33b026a + 901ba58 commit c27c42c

File tree

2 files changed

+290
-64
lines changed

2 files changed

+290
-64
lines changed

src/Protocol/Parser.php

Lines changed: 76 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ private function parse($data, Message $message)
6666

6767
public function parseHeader(Message $message)
6868
{
69-
if (strlen($message->data) < 12) {
69+
if (!isset($message->data[12 - 1])) {
7070
return;
7171
}
7272

@@ -97,19 +97,11 @@ public function parseHeader(Message $message)
9797

9898
public function parseQuestion(Message $message)
9999
{
100-
if (strlen($message->data) < 2) {
101-
return;
102-
}
103-
104100
$consumed = $message->consumed;
105101

106102
list($labels, $consumed) = $this->readLabels($message->data, $consumed);
107103

108-
if (null === $labels) {
109-
return;
110-
}
111-
112-
if (strlen($message->data) - $consumed < 4) {
104+
if ($labels === null || !isset($message->data[$consumed + 4 - 1])) {
113105
return;
114106
}
115107

@@ -133,19 +125,11 @@ public function parseQuestion(Message $message)
133125

134126
public function parseAnswer(Message $message)
135127
{
136-
if (strlen($message->data) < 2) {
137-
return;
138-
}
139-
140128
$consumed = $message->consumed;
141129

142-
list($labels, $consumed) = $this->readLabels($message->data, $consumed);
130+
list($name, $consumed) = $this->readDomain($message->data, $consumed);
143131

144-
if (null === $labels) {
145-
return;
146-
}
147-
148-
if (strlen($message->data) - $consumed < 10) {
132+
if ($name === null || !isset($message->data[$consumed + 10 - 1])) {
149133
return;
150134
}
151135

@@ -163,68 +147,85 @@ public function parseAnswer(Message $message)
163147
list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2)));
164148
$consumed += 2;
165149

166-
$rdata = null;
150+
if (!isset($message->data[$consumed + $rdLength - 1])) {
151+
return;
152+
}
167153

168-
if (Message::TYPE_A === $type || Message::TYPE_AAAA === $type) {
169-
$ip = substr($message->data, $consumed, $rdLength);
170-
$consumed += $rdLength;
154+
$rdata = null;
155+
$expected = $consumed + $rdLength;
171156

172-
$rdata = inet_ntop($ip);
157+
if (Message::TYPE_A === $type) {
158+
if ($rdLength === 4) {
159+
$rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
160+
$consumed += $rdLength;
161+
}
162+
} elseif (Message::TYPE_AAAA === $type) {
163+
if ($rdLength === 16) {
164+
$rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
165+
$consumed += $rdLength;
166+
}
173167
} elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
174-
list($bodyLabels, $consumed) = $this->readLabels($message->data, $consumed);
175-
176-
$rdata = implode('.', $bodyLabels);
168+
list($rdata, $consumed) = $this->readDomain($message->data, $consumed);
177169
} elseif (Message::TYPE_TXT === $type) {
178170
$rdata = array();
179-
$remaining = $rdLength;
180-
while ($remaining) {
171+
while ($consumed < $expected) {
181172
$len = ord($message->data[$consumed]);
182-
$rdata[] = substr($message->data, $consumed + 1, $len);
173+
$rdata[] = (string)substr($message->data, $consumed + 1, $len);
183174
$consumed += $len + 1;
184-
$remaining -= $len + 1;
185175
}
186176
} elseif (Message::TYPE_MX === $type) {
187-
list($priority) = array_values(unpack('n', substr($message->data, $consumed, 2)));
188-
list($bodyLabels, $consumed) = $this->readLabels($message->data, $consumed + 2);
189-
190-
$rdata = array(
191-
'priority' => $priority,
192-
'target' => implode('.', $bodyLabels)
193-
);
177+
if ($rdLength > 2) {
178+
list($priority) = array_values(unpack('n', substr($message->data, $consumed, 2)));
179+
list($target, $consumed) = $this->readDomain($message->data, $consumed + 2);
180+
181+
$rdata = array(
182+
'priority' => $priority,
183+
'target' => $target
184+
);
185+
}
194186
} elseif (Message::TYPE_SRV === $type) {
195-
list($priority, $weight, $port) = array_values(unpack('n*', substr($message->data, $consumed, 6)));
196-
list($bodyLabels, $consumed) = $this->readLabels($message->data, $consumed + 6);
197-
198-
$rdata = array(
199-
'priority' => $priority,
200-
'weight' => $weight,
201-
'port' => $port,
202-
'target' => implode('.', $bodyLabels)
203-
);
187+
if ($rdLength > 6) {
188+
list($priority, $weight, $port) = array_values(unpack('n*', substr($message->data, $consumed, 6)));
189+
list($target, $consumed) = $this->readDomain($message->data, $consumed + 6);
190+
191+
$rdata = array(
192+
'priority' => $priority,
193+
'weight' => $weight,
194+
'port' => $port,
195+
'target' => $target
196+
);
197+
}
204198
} elseif (Message::TYPE_SOA === $type) {
205-
list($primaryLabels, $consumed) = $this->readLabels($message->data, $consumed);
206-
list($mailLabels, $consumed) = $this->readLabels($message->data, $consumed);
207-
list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($message->data, $consumed, 20)));
208-
$consumed += 20;
209-
210-
$rdata = array(
211-
'mname' => implode('.', $primaryLabels),
212-
'rname' => implode('.', $mailLabels),
213-
'serial' => $serial,
214-
'refresh' => $refresh,
215-
'retry' => $retry,
216-
'expire' => $expire,
217-
'minimum' => $minimum
218-
);
199+
list($mname, $consumed) = $this->readDomain($message->data, $consumed);
200+
list($rname, $consumed) = $this->readDomain($message->data, $consumed);
201+
202+
if ($mname !== null && $rname !== null && isset($message->data[$consumed + 20 - 1])) {
203+
list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($message->data, $consumed, 20)));
204+
$consumed += 20;
205+
206+
$rdata = array(
207+
'mname' => $mname,
208+
'rname' => $rname,
209+
'serial' => $serial,
210+
'refresh' => $refresh,
211+
'retry' => $retry,
212+
'expire' => $expire,
213+
'minimum' => $minimum
214+
);
215+
}
219216
} else {
220217
// unknown types simply parse rdata as an opaque binary string
221218
$rdata = substr($message->data, $consumed, $rdLength);
222219
$consumed += $rdLength;
223220
}
224221

222+
// ensure parsing record data consumes expact number of bytes indicated in record length
223+
if ($consumed !== $expected || $rdata === null) {
224+
return;
225+
}
226+
225227
$message->consumed = $consumed;
226228

227-
$name = implode('.', $labels);
228229
$record = new Record($name, $type, $class, $ttl, $rdata);
229230

230231
$message->answers[] = $record;
@@ -236,6 +237,17 @@ public function parseAnswer(Message $message)
236237
return $message;
237238
}
238239

240+
private function readDomain($data, $consumed)
241+
{
242+
list ($labels, $consumed) = $this->readLabels($data, $consumed);
243+
244+
if ($labels === null) {
245+
return array(null, null);
246+
}
247+
248+
return array(implode('.', $labels), $consumed);
249+
}
250+
239251
private function readLabels($data, $consumed)
240252
{
241253
$labels = array();
@@ -262,6 +274,7 @@ private function readLabels($data, $consumed)
262274

263275
$consumed += 2;
264276
list($newLabels) = $this->readLabels($data, $offset);
277+
265278
if ($newLabels === null) {
266279
return array(null, null);
267280
}

0 commit comments

Comments
 (0)