Skip to content

ext/phar: Fix ZIP extra field length underflow in Phar#22330

Open
LamentXU123 wants to merge 1 commit into
php:PHP-8.4from
LamentXU123:phar-int-overflow-fix
Open

ext/phar: Fix ZIP extra field length underflow in Phar#22330
LamentXU123 wants to merge 1 commit into
php:PHP-8.4from
LamentXU123:phar-int-overflow-fix

Conversation

@LamentXU123

@LamentXU123 LamentXU123 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Now the parser used a uint16_t remaining-length counter and subtracted the extra field payload size plus header size without first validating that the parsing data past the extra field boundary.

So, a malformed zip .phar file can trigger an underflow. See

  <?php
  function u16($v) { return pack('v', $v); }
  function u32($v) { return pack('V', $v); }

  $f = __DIR__ . '/poc.zip';
  $name = 'a';
  $data = 'x';
  $crc = crc32($data);

  $local = u32(0x04034b50) . u16(20) . u16(0) . u16(0) . u16(0) . u16(0)
      . u32($crc) . u32(strlen($data)) . u32(strlen($data))
      . u16(strlen($name)) . u16(0) . $name . $data;

  /* extra_len = 4, but sub-header says payload size = 1. */
  $extra = 'XX' . u16(1);

  /* Old parser seeks 1 byte past the extra field, then parses this comment data. */
  $prefix = 'A' . 'UT' . u16(5) . "\x01" . u32(114514000) . 'ZZ' . u16(65522);
  $comment = $prefix . str_repeat('B', 65535 - strlen($prefix));

  $central = u32(0x02014b50) . u16(20) . u16(20) . u16(0) . u16(0) . u16(0) . u16(0)
      . u32($crc) . u32(strlen($data)) . u32(strlen($data))
      . u16(strlen($name)) . u16(strlen($extra)) . u16(strlen($comment))
      . u16(0) . u16(0) . u32(0) . u32(0)
      . $name . $extra . $comment;

  $eocd = u32(0x06054b50) . u16(0) . u16(0) . u16(1) . u16(1)
      . u32(strlen($central)) . u32(strlen($local)) . u16(0);

  file_put_contents($f, $local . $central . $eocd);

  try {
      $p = new PharData($f);
      echo "BUG: corrupt zip loaded\n";
      echo "mtime: ", $p[$name]->getMTime(), "\n";
  } catch (Exception $e) {
      echo "fixed: ", $e->getMessage(), "\n";
  }

the mtime will become a controlled 114514191. The malformed ZIP has central extra_len = 4, but the first extra sub-header declares size = 1. The old parser subtracts 5 from a uint16_t length counter, underflows, then continues parsing past the extra field boundary. So it parses file comment bytes as a UT extra field, which makes the mtime 114514191.

BUG: corrupt zip loaded
mtime: 114514000

The fix is to validate each extra field sub-header before consuming it.

p.s. the gpg sign is now fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant