Open
fix(cve): 修复 CVE-2026-96638 在 python-pillow 中的漏洞 #322
infra_teamAdd on 8 days ago
fix(cve): 修复 CVE-2026-96638 在 python-pillow 中的漏洞 #322
Open
Total: 2 files changed+97-1
| @@ -0,0 +1,92 @@ | |||
| 1 | +From ef5b931d8b32676f8145b6ffda969fbc246ad6d4 Mon Sep 17 00:00:00 2001 | ||
| 2 | +From: Andrew Murray <3112309+radarhere@users.noreply.github.com> | ||
| 3 | +Date: Thu, 17 Sep 2026 21:49:45 +1000 | ||
| 4 | +Subject: [PATCH] Do not decode more BMP data than necessary (#10015) | ||
| 5 | + | ||
| 6 | +Backport of the upstream fix for CVE-2026-96638 / GHSA-24rc-8hj2-v98c | ||
| 7 | +(fixed in Pillow 13.0.0), adapted to Pillow 10.3.0. | ||
| 8 | + | ||
| 9 | +BmpRleDecoder.decode() appended delta-escape padding and absolute-mode | ||
| 10 | +runs to the output buffer without checking the remaining dest_length, | ||
| 11 | +so a small crafted BMP (RLE8/RLE4) could expand into gigabytes of | ||
| 12 | +zero-filled data (CWE-400/CWE-770). The fix caps every append at the | ||
| 13 | +remaining destination length and is the combination of two upstream | ||
| 14 | +commits, applied here in dependency order: | ||
| 15 | + | ||
| 16 | + 9af332ef (#9824) "Do not read any further than necessary" caps | ||
| 17 | + absolute-mode runs: data_count = min(dest_length - len(data), | ||
| 18 | + byte[0]), rle4 reads data_count // 2 bytes, non-rle4 reads | ||
| 19 | + data_count bytes, and the short-read break also stops once the | ||
| 20 | + destination buffer is full. | ||
| 21 | + https://github.com/python-pillow/Pillow/commit/9af332ef2b27bc54fcf34f6c84de21d27d8c0065 | ||
| 22 | + | ||
| 23 | + e7794835 (#10015, referenced by NVD as the fix commit) caps delta | ||
| 24 | + escapes: data += b"\x00" * min(dest_length - len(data), right + up * | ||
| 25 | + self.state.xsize). | ||
| 26 | + https://github.com/python-pillow/Pillow/commit/e7794835e323ff2691d9b3a974cade1a44301b9a | ||
| 27 | + | ||
| 28 | +Upstream-commit: https://github.com/python-pillow/Pillow/commit/e7794835e323ff2691d9b3a974cade1a44301b9a | ||
| 29 | + | ||
| 30 | +Adaptation to 10.3.0 (both hunks rewritten by hand; neither upstream | ||
| 31 | +pre-image matches 10.3.0, and the interdependent #9443/#9710 changes | ||
| 32 | +are not part of this CVE's scope and are not ported): | ||
| 33 | +- delta branch: the capped append matches e7794835's post-image | ||
| 34 | + verbatim; the surrounding 10.3.0 lines (including the pre-#9443 | ||
| 35 | + "right, up = self.fd.read(2)") are kept unchanged. | ||
| 36 | +- absolute branch: 10.3.0 has no data_count and its rle4 loop appends | ||
| 37 | + 2 pixels per byte unconditionally, so data_count is introduced at | ||
| 38 | + the branch entry and rle4 uses byte_count = data_count // 2, which | ||
| 39 | + is verbatim-equivalent to the original byte[0] // 2 whenever no | ||
| 40 | + truncation happens (13.0.0's (data_count + 1) // 2 and odd-pixel | ||
| 41 | + guard belong to #9710 and are intentionally not ported); the | ||
| 42 | + non-rle4 branch uses byte_count = data_count, and the break | ||
| 43 | + condition is extended with "or len(data) == dest_length" as in | ||
| 44 | + #9824. | ||
| 45 | + | ||
| 46 | +Verified on 10.3.0: crafted delta-escape and absolute-mode streams that | ||
| 47 | +previously overshot dest_length (25755 vs 10000, 255 vs 100, 254 vs | ||
| 48 | +100) now stop exactly at dest_length, while non-truncating streams | ||
| 49 | +decode byte-identically to the pristine decoder. | ||
| 50 | + | ||
| 51 | +Signed-off-by: infra_team <zhaiwenjie1@huawei.com> | ||
| 52 | +--- | ||
| 53 | + src/PIL/BmpImagePlugin.py | 11 +++++++---- | ||
| 54 | + 1 file changed, 7 insertions(+), 4 deletions(-) | ||
| 55 | + | ||
| 56 | +diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py | ||
| 57 | +index 9947f43..6ee5ff3 100644 | ||
| 58 | +--- a/src/PIL/BmpImagePlugin.py | ||
| 59 | ++++ b/src/PIL/BmpImagePlugin.py | ||
| 60 | +@@ -329,22 +329,25 @@ class BmpRleDecoder(ImageFile.PyDecoder): | ||
| 61 | + if len(bytes_read) < 2: | ||
| 62 | + break | ||
| 63 | + right, up = self.fd.read(2) | ||
| 64 | +- data += b"\x00" * (right + up * self.state.xsize) | ||
| 65 | ++ data += b"\x00" * min( | ||
| 66 | ++ dest_length - len(data), right + up * self.state.xsize | ||
| 67 | ++ ) | ||
| 68 | + x = len(data) % self.state.xsize | ||
| 69 | + else: | ||
| 70 | + # absolute mode | ||
| 71 | ++ data_count = min(dest_length - len(data), byte[0]) | ||
| 72 | + if rle4: | ||
| 73 | + # 2 pixels per byte | ||
| 74 | +- byte_count = byte[0] // 2 | ||
| 75 | ++ byte_count = data_count // 2 | ||
| 76 | + bytes_read = self.fd.read(byte_count) | ||
| 77 | + for byte_read in bytes_read: | ||
| 78 | + data += o8(byte_read >> 4) | ||
| 79 | + data += o8(byte_read & 0x0F) | ||
| 80 | + else: | ||
| 81 | +- byte_count = byte[0] | ||
| 82 | ++ byte_count = data_count | ||
| 83 | + bytes_read = self.fd.read(byte_count) | ||
| 84 | + data += bytes_read | ||
| 85 | +- if len(bytes_read) < byte_count: | ||
| 86 | ++ if len(bytes_read) < byte_count or len(data) == dest_length: | ||
| 87 | + break | ||
| 88 | + x += byte[0] | ||
| 89 | + | ||
| 90 | +-- | ||
| 91 | +2.39.5 | ||
| 92 | + | ||
| @@ -4,7 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | Name: python-pillow | 5 | Name: python-pillow |
| 6 | Version: 10.3.0 | 6 | Version: 10.3.0 |
| 7 | -Release: 14 | 7 | +Release: 15 |
| 8 | Summary: Python image processing library | 8 | Summary: Python image processing library |
| 9 | License: MIT | 9 | License: MIT |
| 10 | URL: http://python-pillow.github.io/ | 10 | URL: http://python-pillow.github.io/ |
| @@ -27,6 +27,7 @@ Patch14: backport-CVE-2026-59205.patch | |||
| 27 | Patch15: backport-CVE-2026-59199.patch | 27 | Patch15: backport-CVE-2026-59199.patch |
| 28 | Patch16: backport-CVE-2026-59198.patch | 28 | Patch16: backport-CVE-2026-59198.patch |
| 29 | Patch17: backport-CVE-2026-112395.patch | 29 | Patch17: backport-CVE-2026-112395.patch |
| 30 | +Patch18: backport-CVE-2026-96638.patch | ||
| 30 | 31 | ||
| 31 | BuildRequires: freetype-devel ghostscript lcms2-devel libimagequant-devel libjpeg-devel libtiff-devel | 32 | BuildRequires: freetype-devel ghostscript lcms2-devel libimagequant-devel libjpeg-devel libtiff-devel |
| 32 | BuildRequires: libwebp-devel openjpeg2-devel tk-devel zlib-devel python3-cffi python3-devel python3-numpy python3-olefile | 33 | BuildRequires: libwebp-devel openjpeg2-devel tk-devel zlib-devel python3-cffi python3-devel python3-numpy python3-olefile |
| @@ -165,6 +166,9 @@ pytest --ignore=_build.python2 --ignore=_build.python3 --ignore=_build.pypy3 -v | |||
| 165 | %{python3_sitearch}/PIL/__pycache__/ImageQt* | 166 | %{python3_sitearch}/PIL/__pycache__/ImageQt* |
| 166 | 167 | ||
| 167 | %changelog | 168 | %changelog |
| 169 | +* Sat Oct 31 2026 infra_team <zhaiwenjie1@huawei.com> - 10.3.0-15 | ||
| 170 | +- Fix CVE-2026-96638: cap BmpRleDecoder delta escapes and absolute-mode runs at dest_length (#9824, #10015) | ||
| 171 | + | ||
| 168 | * Tue Oct 20 2026 infra_team <zhaiwenjie1@huawei.com> - 10.3.0-14 | 172 | * Tue Oct 20 2026 infra_team <zhaiwenjie1@huawei.com> - 10.3.0-14 |
| 169 | - Fix CVE-2026-112395: Reject TIFFs whose unpacker row is wider than libtiff decoded row (#10201) | 173 | - Fix CVE-2026-112395: Reject TIFFs whose unpacker row is wider than libtiff decoded row (#10201) |
| 170 | 174 | ||