Skip to content

Commit 9e9adf5

Browse files
committed
add security hardening tests — bounds, depth, malformed input
1 parent 81408a6 commit 9e9adf5

File tree

1 file changed

+77
-0
lines changed

1 file changed

+77
-0
lines changed

tests/TtlvTest.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,4 +460,81 @@ public function testDecodedTextStringLengthMatchesContent(): void
460460
$this->assertSame(5, $decoded['length']);
461461
$this->assertSame(16, $decoded['total_length']); // 8 header + 8 padded
462462
}
463+
464+
// -----------------------------------------------------------------------
465+
// Security hardening tests
466+
// -----------------------------------------------------------------------
467+
468+
public function testRejectsDeclaredLengthExceedingBuffer(): void
469+
{
470+
// Header claiming 1000 bytes of value, but only 10 bytes provided
471+
$header = pack('C3C', 0x42, 0x00, 0x01, 0x07); // tag=0x420001, type=TextString
472+
$header .= pack('N', 1000); // length = 1000
473+
$body = str_repeat("\x00", 10);
474+
$buf = $header . $body;
475+
$this->expectException(\RuntimeException::class);
476+
$this->expectExceptionMessageMatches('/exceeds buffer/');
477+
Ttlv::decode($buf);
478+
}
479+
480+
public function testAcceptsDeclaredLengthThatExactlyFitsBuffer(): void
481+
{
482+
$encoded = Ttlv::encodeInteger(0x420001, 42);
483+
$decoded = Ttlv::decode($encoded);
484+
$this->assertSame(42, $decoded['value']);
485+
}
486+
487+
public function testRejectsZeroLengthBuffer(): void
488+
{
489+
$this->expectException(\RuntimeException::class);
490+
$this->expectExceptionMessageMatches('/too short/');
491+
Ttlv::decode('');
492+
}
493+
494+
public function testRejectsStructuresNestedDeeperThan32Levels(): void
495+
{
496+
// Build 33 levels of nesting
497+
$inner = Ttlv::encodeInteger(0x420001, 42);
498+
for ($i = 0; $i < 33; $i++) {
499+
$inner = Ttlv::encodeStructure(0x420001, [$inner]);
500+
}
501+
$this->expectException(\RuntimeException::class);
502+
$this->expectExceptionMessageMatches('/depth/');
503+
Ttlv::decode($inner);
504+
}
505+
506+
public function testAcceptsStructuresNestedExactly32LevelsDeep(): void
507+
{
508+
// Build 31 wrapping levels (root is depth 0, innermost is depth 31)
509+
$inner = Ttlv::encodeInteger(0x420001, 42);
510+
for ($i = 0; $i < 31; $i++) {
511+
$inner = Ttlv::encodeStructure(0x420001, [$inner]);
512+
}
513+
$decoded = Ttlv::decode($inner);
514+
$this->assertSame(Ttlv::TYPE_STRUCTURE, $decoded['type']);
515+
}
516+
517+
public function testRejectsTruncatedHeader(): void
518+
{
519+
$buf = pack('C4', 0x42, 0x00, 0x01, 0x02);
520+
$this->expectException(\RuntimeException::class);
521+
$this->expectExceptionMessageMatches('/too short/');
522+
Ttlv::decode($buf);
523+
}
524+
525+
public function testHandlesIntegerWithWrongLengthSafely(): void
526+
{
527+
// Header: tag=0x420001, type=Integer(0x02), length=3 (should be 4)
528+
$buf = str_repeat("\x00", 16);
529+
$buf[0] = chr(0x42); $buf[1] = chr(0x00); $buf[2] = chr(0x01);
530+
$buf[3] = chr(0x02); // type = Integer
531+
$buf = substr_replace($buf, pack('N', 3), 4, 4); // length = 3
532+
// Should either throw or handle safely — must not crash
533+
try {
534+
Ttlv::decode($buf);
535+
} catch (\Exception $e) {
536+
// Any exception is acceptable
537+
}
538+
$this->assertTrue(true, 'decoder did not crash on malformed integer length');
539+
}
463540
}

0 commit comments

Comments
 (0)