Skip to content

Commit 9952957

Browse files
committed
Fix interpretation of TTL as UINT32 with most significant bit unset
1 parent f25be07 commit 9952957

3 files changed

Lines changed: 86 additions & 2 deletions

File tree

src/Model/Record.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ class Record
2020
public $class;
2121

2222
/**
23-
* @var int maximum TTL in seconds (UINT16)
23+
* @var int maximum TTL in seconds (UINT32, most significant bit always unset)
24+
* @link https://tools.ietf.org/html/rfc2181#section-8
2425
*/
2526
public $ttl;
2627

src/Protocol/Parser.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ public function parseAnswer(Message $message)
154154
list($ttl) = array_values(unpack('N', substr($message->data, $consumed, 4)));
155155
$consumed += 4;
156156

157+
// TTL is a UINT32 that must not have most significant bit set for BC reasons
158+
if ($ttl < 0 || $ttl >= 1 << 31) {
159+
$ttl = 0;
160+
}
161+
157162
list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2)));
158163
$consumed += 2;
159164

@@ -219,7 +224,6 @@ public function parseAnswer(Message $message)
219224
$message->consumed = $consumed;
220225

221226
$name = implode('.', $labels);
222-
$ttl = $this->signedLongToUnsignedLong($ttl);
223227
$record = new Record($name, $type, $class, $ttl, $rdata);
224228

225229
$message->answers[] = $record;
@@ -291,6 +295,10 @@ public function getCompressedLabelOffset($data, $consumed)
291295
return array($peek & $mask, $consumed + 2);
292296
}
293297

298+
/**
299+
* @deprecated unused, exists for BC only
300+
* @codeCoverageIgnore
301+
*/
294302
public function signedLongToUnsignedLong($i)
295303
{
296304
return $i & 0x80000000 ? $i - 0xffffffff : $i;

tests/Protocol/ParserTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,81 @@ public function testParseAnswerWithInlineData()
157157
$this->assertSame('178.79.169.131', $response->answers[0]->data);
158158
}
159159

160+
public function testParseAnswerWithExcessiveTtlReturnsZeroTtl()
161+
{
162+
$data = "";
163+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
164+
$data .= "00 01 00 01"; // answer: type A, class IN
165+
$data .= "ff ff ff ff"; // answer: ttl 2^32 - 1
166+
$data .= "00 04"; // answer: rdlength 4
167+
$data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
168+
169+
$data = $this->convertTcpDumpToBinary($data);
170+
171+
$response = new Message();
172+
$response->header->set('anCount', 1);
173+
$response->data = $data;
174+
175+
$this->parser->parseAnswer($response);
176+
177+
$this->assertCount(1, $response->answers);
178+
$this->assertSame('igor.io', $response->answers[0]->name);
179+
$this->assertSame(Message::TYPE_A, $response->answers[0]->type);
180+
$this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
181+
$this->assertSame(0, $response->answers[0]->ttl);
182+
$this->assertSame('178.79.169.131', $response->answers[0]->data);
183+
}
184+
185+
public function testParseAnswerWithTtlExactlyBoundaryReturnsZeroTtl()
186+
{
187+
$data = "";
188+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
189+
$data .= "00 01 00 01"; // answer: type A, class IN
190+
$data .= "80 00 00 00"; // answer: ttl 2^31
191+
$data .= "00 04"; // answer: rdlength 4
192+
$data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
193+
194+
$data = $this->convertTcpDumpToBinary($data);
195+
196+
$response = new Message();
197+
$response->header->set('anCount', 1);
198+
$response->data = $data;
199+
200+
$this->parser->parseAnswer($response);
201+
202+
$this->assertCount(1, $response->answers);
203+
$this->assertSame('igor.io', $response->answers[0]->name);
204+
$this->assertSame(Message::TYPE_A, $response->answers[0]->type);
205+
$this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
206+
$this->assertSame(0, $response->answers[0]->ttl);
207+
$this->assertSame('178.79.169.131', $response->answers[0]->data);
208+
}
209+
210+
public function testParseAnswerWithMaximumTtlReturnsExactTtl()
211+
{
212+
$data = "";
213+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
214+
$data .= "00 01 00 01"; // answer: type A, class IN
215+
$data .= "7f ff ff ff"; // answer: ttl 2^31 - 1
216+
$data .= "00 04"; // answer: rdlength 4
217+
$data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
218+
219+
$data = $this->convertTcpDumpToBinary($data);
220+
221+
$response = new Message();
222+
$response->header->set('anCount', 1);
223+
$response->data = $data;
224+
225+
$this->parser->parseAnswer($response);
226+
227+
$this->assertCount(1, $response->answers);
228+
$this->assertSame('igor.io', $response->answers[0]->name);
229+
$this->assertSame(Message::TYPE_A, $response->answers[0]->type);
230+
$this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
231+
$this->assertSame(0x7fffffff, $response->answers[0]->ttl);
232+
$this->assertSame('178.79.169.131', $response->answers[0]->data);
233+
}
234+
160235
public function testParseAnswerWithUnknownType()
161236
{
162237
$data = "";

0 commit comments

Comments
 (0)