CVE-2026-33636

CVE-2026-33636

Area
Native / Embedded
Credit
Public Attribution
Credit Note
CVSS
7.6
CVSS Source
CNA / Vendor
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:H
CWE
CWE-125 · CWE-787
Doc QA
Reviewed
Evidence Level
Dynamic Isolated
Evidence Ref
772f151c2a4804bea59fbfc8d9b536816c60de48
Fixed
Fixed Version
1.6.56 · 1.8.0
Impact
Memory Corruption
Key Finding
마지막 partial chunk에서도 NEON loop가 실행돼 crafted PNG 처리 중 row buffer 앞쪽 OOB read/write가 발생했습니다.
Published
Mar 26, 2026
Released
Reported
Reviewed At
Sep 4, 2026
Role
Discovery
Root Cause
PoC
Dynamic Validation
Disclosure
Fix Review
Evidence Curation
Score Note
Severity
High
Status
Public
Target
libpng · ARM/AArch64 NEON palette expansion
Template Version
v2

01 Summary

🔎
ARM/AArch64 NEON 최적화가 paletted PNG의 partial tail을 처리할 때 reverse pointer를 잘못 미리 이동하고 loop bound도 잘못 계산해 row buffer 앞쪽을 읽고 썼습니다.
구분
확인 내용
Component
libpng ARM/AArch64 NEON palette expansion
Attack Input
tail width가 남는 crafted paletted PNG
Preconditions
영향받는 NEON 경로가 활성화된 ARM/AArch64 환경
Sink
reverse pointer pre-adjust와 full-chunk loop bound의 불일치
Verified
RGBA width 5·RGB width 9 harness에서 ASan OOB read/write 확인
Impact
process crash 및 memory corruption 가능성
Fix Principle
vector loop는 완전한 chunk만 처리하고 scalar tail 전에 pointer 복원
목차 · 섹션으로 이동

Attack Chain

검증 결론: 서로 다른 RGB·RGBA tail harness가 같은 pointer/loop 결함을 재현해 최적화 경로의 out-of-bounds primitive를 확인했습니다.

Disclosure Timeline

Date
Event
Evidence
2026-03-26
CVE 공개
2026-03-26
libpng 1.6.56 및 1.8.0 수정 정보 공개
2026-08-23
Docker/로컬 원본 대조와 GitHub push 감사

02 Vulnerability

libpng는 팔레트 인덱스 한 바이트를 RGB 또는 RGBA 픽셀로 확장합니다. ARM/AArch64 NEON 최적화 구현은 행의 뒤에서 앞으로 고정 크기 묶음을 처리했습니다. RGBA는 4픽셀, RGB는 8픽셀 단위였습니다.
행 너비가 처리 단위의 배수가 아니면 마지막 반복에 충분한 입력이 남지 않았지만 루프는 그대로 실행됐습니다. 역방향 처리 때문에 단순한 tail 초과가 아니라 입력 포인터와 출력 포인터가 모두 행 시작 전으로 이동했습니다.
공격자가 제어하는 값
PNG 팔레트 행 너비와 픽셀 데이터
취약 경로
arm/palette_neon_intrinsics.c
아키텍처
ARM/AArch64에서 NEON 최적화가 활성화된 경우
영향
행 버퍼 앞쪽 OOB read와 OOB write

03 Approach

이미지 decoder의 SIMD 경로를 볼 때 scalar 구현과 벡터 구현의 tail 처리 차이를 우선 비교했습니다. 고정 폭 벡터 연산은 마지막 블록이 꽉 차지 않을 때 별도 처리나 루프 상한이 필요합니다.
NEON 코드는 출력 버퍼 끝에서 시작하도록 포인터를 미리 15바이트 또는 23바이트 조정한 뒤, 루프마다 4개 또는 8개의 인덱스를 읽었습니다. i < row_width 조건은 남은 픽셀 수가 한 블록보다 적은 경우를 막지 못했습니다.
행 끝의 partial chunk에서도 NEON 루프가 고정 크기 load와 store를 실행하는가?

04 Root Cause

RGBA 너비가 5이면 첫 반복은 4픽셀을 정상 처리하지만 두 번째 반복에는 1픽셀만 남습니다. 코드는 여전히 4픽셀을 읽고 16바이트를 쓰기 위해 포인터를 뒤로 이동하므로 row - 3 부근을 읽고 행 시작 전 출력 위치에 씁니다.
RGB 너비 9에서도 같은 구조가 8픽셀 단위로 나타났습니다. 이 문제는 generic C, SSE, VSX, LSX 구현 전체의 공통 문제가 아니라 ARM NEON 경로의 역방향 포인터 산술과 loop bound의 조합이었습니다.
palette_neon_intrinsics.c · 취약 루프의 구조
// RGBA: 4 pixels per iteration for (i = 0; i < row_width; i += 4) { sp -= 4; dp -= 16; // fixed-size load and 16-byte store } // row_width=5일 때 두 번째 반복도 실행됨
입력에서 영향까지의 경로
  1. 공격자 PNG가 8-bit palette row를 제공합니다.
  1. libpng가 ARM NEON palette expansion을 선택합니다.
  1. 전체 블록을 처리한 뒤 partial tail이 남습니다.
  1. 고정 크기 load/store가 다시 실행됩니다.
  1. 입력과 출력 포인터가 row 시작 전을 참조합니다.

05 Reproduction & Validation

x86 호스트에서 코드만 모사하지 않고 AArch64 바이너리를 만들어 QEMU user-mode에서 실행했습니다. 일반 harness로 crash를 확인한 뒤 ASan용 harness로 guard 영역을 둬 read와 write 위치를 구분했습니다.
RGBA 너비 4와 RGB 너비 8을 정상 대조군으로, 각각 한 픽셀 더 긴 5와 9를 경계 입력으로 두었습니다. 폭이 정확히 처리 단위일 때는 통과하고 한 픽셀을 더했을 때 실패하는지 확인했습니다.
$ qemu-aarch64 ./neon_palette_asan_harness rgba 5 $ qemu-aarch64 ./neon_palette_asan_harness rgb 9
검사
입력·조건
관찰
의미
RGBA 대조군
width 4
정상 종료
완전한 4-pixel block은 정상
RGBA 경계
width 5
row 앞쪽 접근
1-pixel tail에서 OOB
RGB 대조군
width 8
정상 종료
완전한 8-pixel block은 정상
RGB 경계
width 9
row 앞쪽 접근
1-pixel tail에서 OOB
AArch64 재현에서 고정한 경계 조건
RGBA chunk = 4 pixels width 4 -> complete block width 5 -> second iteration with 1 pixel remaining read begins before row / write begins before row RGB chunk = 8 pixels width 8 -> complete block width 9 -> second iteration with 1 pixel remaining

06 Impact & Fix

공격자가 만든 PNG를 정상 decode하는 과정에서 ARM NEON 경로가 행 버퍼 바깥을 읽고 쓸 수 있습니다. 이미지 처리 프로세스의 crash와 인접 메모리 훼손이 확인된 영향입니다.

확인한 범위

  • AArch64 NEON 경로의 partial tail 실행
  • RGBA와 RGB 양쪽의 row-before-start 접근
  • 정상 폭과 경계 폭의 차이

제외한 범위

  • x86 SIMD 경로의 영향
  • 안정적인 정보 유출
  • 원격 코드 실행 exploit

수정

수정은 전체 블록만 NEON 루프에서 처리하고 남은 tail은 안전한 경로로 넘겼습니다. 동시에 역방향 destination pointer의 사전 조정을 되돌려, loop bound와 pointer 기준을 함께 맞췄습니다.

07 Turning Points

🧭
  • 처음에는 loop condition만 바꾸면 된다고 봤지만 reverse destination pointer의 사전 보정까지 함께 맞춰야 수정이 완전함을 확인했습니다.
  • scalar와 다른 SIMD 구현을 대조해 일반 libpng 문제가 아니라 ARM/AArch64 NEON 활성화 조건으로 범위를 좁혔습니다.
  • OOB write를 확인했어도 exploitability를 입증하지 않았으므로 원격 코드 실행 주장은 제외했습니다.
Stage
Prior Belief
New Evidence
Revised Judgment
Trigger
Tail 처리
loop condition만 i + chunk <= row_width로 바꾸면 충분
destination pointer가 사전에 15 또는 23바이트 이동
loop bound와 pointer 기준을 함께 수정해야 함
pointer 산술 검토
아키텍처 범위
ASan 경고를 decoder 전체 문제로 확대할 수 있음
scalar와 다른 SIMD 구현 대조
ARM/AArch64 NEON 활성화 조건으로 한정
구현 비교
영향 범위
OOB write 확인
보존 결과는 OOB read/write와 crash, exploitability 미입증
원격 코드 실행을 결론에서 제외
증거 수준 검토

Investigation Log

전체 판단 변화 원문

Tail 처리

처음에는 단순히 loop condition을 i + chunk <= row_width로 바꾸면 끝날 것으로 봤습니다. 하지만 이 함수는 역방향 처리를 위해 destination pointer를 미리 15 또는 23바이트 당겨 둡니다. 루프만 줄이면 남은 픽셀을 처리할 포인터 기준 자체가 달라져 수정이 불완전했습니다.

아키텍처

ASan 경고를 일반적인 libpng decoder 전체의 문제로 확대하지 않았습니다. scalar와 다른 SIMD 구현을 대조한 결과 문제의 포인터 이동은 NEON 파일에 국한됐습니다. 영향 조건에 ARM/AArch64와 NEON 활성화를 명시했습니다.

코드 실행

OOB write가 있다는 사실만으로 원격 코드 실행을 주장하지 않았습니다. 보존된 결과는 경계 밖 읽기·쓰기와 crash였습니다. 할당 배치와 호출 환경을 통제해 exploitability를 입증하지 않았기 때문에 그 범위는 결론에서 제외했습니다.

Role & Credit

  • Credit — Public Attribution

08 Provenance

항목
최종 감사 결과
소스 기준
libpng checkout 28cb99fe65f09e79703ac2c3008649e14c7b0844
원본 검증
AArch64 QEMU guard harness에서 RGBA 4·RGB 8 정상 종료, RGBA 5·RGB 9 SIGSEGV 재확인
GitHub 보존 상태
Historical audit commit c04cacf 당시 evidence 6개와 root manifest.json에 size·SHA-256을 고정했습니다. 현재 main은 evidence 5개이며 root manifest.json은 제거됐습니다.
무결성
사례별 SHA256SUMS 검증 통과, root manifest의 모든 file hash와 실제 파일 일치, README 상대 링크 확인 완료
보존 경계·제약
보존 ASan binary는 정상 대조군도 QEMU에서 실패해 이번 fresh control로 채택하지 않음. 원 공개 advisory의 ASan 증거와 fresh guard 관찰을 구분하며 core·binary는 제외
감사 완료
2026-08-23 · Docker/로컬 원본 대조 후 GitHub push 완료
📌
링크 기준: 현재 main에 남아 있는 사례·evidence 링크는 main을 유지하고, 현재 main에서 삭제된 SHA256SUMSSOURCE-PROVENANCE.md 링크는 검증된 historical snapshot 322da5d0e45b3e13e6487c4a8c9eb04124233f56에 고정했습니다.
이전 소개문 · 원문 보존
8-bit 팔레트 행의 남은 픽셀이 NEON 처리 단위보다 짧을 때 역방향 포인터가 행 앞쪽으로 넘어갔습니다. AArch64 harness와 ASan으로 읽기와 쓰기 경계를 따로 확인했습니다.
Taegu Ha · libpng · CWE-125 · CWE-787 · CVSS 7.6
2026년 3월 26일 공개 · libpng 1.6.56 및 1.8.0에서 수정 · 공식 기록

09 Artifacts

2026-08-23 최종 감사

AArch64용 일반·guard harness, fresh QEMU 경계 관찰 기록, 공개 advisory와 CVE 레코드를 포함했습니다. 보존 ASan binary의 정상 대조군 문제를 명시했고 core dump·binary는 제외했습니다.

10 References

11 Takeaways

Original Takeaways

SIMD 취약점은 loop condition 한 줄보다 포인터가 어느 방향으로 움직이고 사전에 얼마나 보정됐는지를 함께 봐야 했습니다. 특히 ‘마지막 블록이 짧다’는 익숙한 문제도 역방향 구현에서는 buffer end가 아니라 start 아래로 나타날 수 있었습니다.
수정안을 검토할 때도 crash를 없애는 입력 하나가 아니라 정상 폭, 한 픽셀 tail, RGB와 RGBA를 같이 고정해야 했습니다. 이후 벡터화 코드는 chunk 크기와 tail policy를 표로 만든 뒤 포인터 산술을 추적하는 습관이 생겼습니다.