Corrupt PNG throws uncatchable error

オープン
#1,747 コメント 2 件 リアクション 3 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

  • #1788 @afonsojanu による — マージされずにクローズ

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
55/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
静か
技術スタック
javascript, node.js
領域
backend

調査の方向性

doc.image エントリーポイントから開始し、PNG の透明度処理の経路を png-js decodePixels まで追跡します。そこでは非同期の zlib エラーが再スローされます。issue に記載された壊れた PNG のケースを再現し、プロセスをクラッシュさせることなく、失敗が呼び出し元で捕捉可能になるか、document のエラーパスを通じて発行されることを確認します。

索引モデルが issue の本文から書いたものです。

説明

Bug Report

Description

When embedding a PNG which IDAT data is corrupted or truncated, pdfkit crashes the process with an uncaught exception. The error cannot be caught by the caller: wrapping inside try/catch does not work, and no error event is emitted on the document.

The root cause is in the png-js dependency. In decodePixels, it calls zlib.inflate and re-throws the error from inside the asynchronous callback:

decodePixels(fn) {
  return zlib.inflate(this.imgData, (err, data) => {
    if (err) {
      throw err;
    }
    ...

This happens specifically when pdfkit needs to access the raw pixels, for example for PNGs with transparency.

How to reproduce

Create a script as follows:

const PDFDocument = require('pdfkit');
const fs = require('fs');
const zlib = require('zlib');

// Construct a PNG which IDAT payload is not a valid zlib stream
function chunk(type, data) {
  const len = Buffer.alloc(4);
  len.writeUInt32BE(data.length);
  const body = Buffer.concat([Buffer.from(type, 'ascii'), data]);
  const crc = Buffer.alloc(4);
  crc.writeUInt32BE(zlib.crc32 ? zlib.crc32(body) : 0);
  return Buffer.concat([len, body, crc]);
}

const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(1, 0); // width
ihdr.writeUInt32BE(1, 4); // height
ihdr[8] = 8; // bit depth
ihdr[9] = 6; // color type: RGBA (forces pdfkit to decode pixels)
const corruptPng = Buffer.concat([
  Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
  chunk('IHDR', ihdr),
  chunk('IDAT', Buffer.from([0xde, 0xad, 0xbe, 0xef])), // garbage deflate data
  chunk('IEND', Buffer.alloc(0)),
]);

const doc = new PDFDocument();
doc.pipe(fs.createWriteStream('/dev/null'));

try {
  doc.image(corruptPng, 0, 0);
  doc.end();
} catch (err) {
  console.log('Caught:', err.message); // never reached
}

Result: despite the try-catch, the Node process crashes.

Environment: pdfkit 0.19.1, png-js 1.1.0, Node.js 22

Suggested fix

The cleanest option is probably to replace png-js with a maintained decoder such as fast-png, which decodes synchronously, throws normal catchable errors, and seems to be more actively maintained.

Alternatively, fixing png-js itself would be fine too, of course, and I think that the fix would be small. However, this exact problem has already been reported in that repo many years ago (foliojs/png.js#14) and the repository shows little activity, so a fix on the pdfkit side seems more realistic.

主要言語
JavaScript
スター
10.7k
フォーク
1.2k
平均マージ
21時間 41分
マージ済み PR(30日)
27

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

foliojs/pdfkit のほかの issue

foliojs/pdfkit の issue をすべて見る

似ている issue

JavaScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。