Skip to content

fix(hwpx): cap ZIP entry decompression to defeat zip bombs#153

Merged
edwardkim merged 1 commit into
edwardkim:mainfrom
seunghan91:fix/hwpx-zip-bomb-protection
Apr 16, 2026
Merged

fix(hwpx): cap ZIP entry decompression to defeat zip bombs#153
edwardkim merged 1 commit into
edwardkim:mainfrom
seunghan91:fix/hwpx-zip-bomb-protection

Conversation

@seunghan91

Copy link
Copy Markdown
Contributor

요약

HWPX 파서의 HwpxReader가 ZIP 엔트리를 읽을 때 크기 제한 없이 read_to_string / read_to_end를 호출합니다. ZIP은 압축률이 매우 높을 수 있어 수 KB짜리 악성 HWPX가 단일 section.xml 엔트리만으로 수 GB까지 팽창 — 호스트 프로세스를 OOM으로 몰 수 있습니다 (decompression bomb).

영향 경로: parse_hwpxHwpxReader::read_file*
영향 범위: CLI (rhwp export-svg 등), WASM, Chrome/Safari 확장, VS Code 확장, rhwp-studio 모두.

변경

src/parser/hwpx/reader.rs에 작은 유틸 read_limited(reader, max)를 추가하고, read_file / read_file_bytes가 이를 통해 읽도록 전환했습니다.

엔트리 종류 상한 상수
XML (section / header / content.hpf 등) 32 MB MAX_XML_SIZE
BinData (이미지·폰트 등) 64 MB MAX_BINDATA_SIZE

Read::take(max + 1)을 사용해 오버플로를 감지하되, 버퍼는 실제 읽은 크기 + 1 이상으로 자라지 않습니다. 상한 초과 시 HwpxError::ZipError("... decompression bomb ...")로 surface.

임계값 근거

실제 정부 보도자료·판례·법령 HWPX에서 section.xml이 32 MB를 넘는 사례를 본 적 없습니다. 이미지도 64 MB 단일 파일을 넘는 경우는 없습니다 (대부분 개별 이미지는 수 MB 이하).

저희 쪽 MDM (markdown-media) 파서는 이 32/64 MB 임계값을 ~500개 실제 HWPX 픽스처 (MOIS 보도자료 전량 + 법원 판결문 샘플) 에서 돌려왔고 false positive는 0건입니다. 원본 패턴은 MDM commit a94b459 에서 HWP/HWPX/PDF 세 파서 경로에 함께 적용했습니다.

공개 API 영향

  • HwpxError 변형 추가 없음 — 초과는 기존 ZipError에 실림
  • 메서드 시그니처 변경 없음
  • MAX_XML_SIZE / MAX_BINDATA_SIZEpub const로 노출 (통합자가 현재 상한을 읽을 수 있음). 필요하면 후속 PR에서 open_with_limits() 생성자도 추가 가능.

테스트

parser::hwpx::reader 5개 유닛 테스트 추가:

  • test_read_limited_under_cap — 상한 미만
  • test_read_limited_at_cap — 상한과 정확히 같은 크기
  • test_read_limited_over_cap — 1 바이트 초과 → InvalidData
  • test_zip_bomb_xml_entry_rejectedMAX_XML_SIZE + 1 바이트의 반복 패턴을 deflate 압축하면 결과물이 1 MB 미만이지만, 실제 해제 시도는 상한에 걸려 ZipError로 거부됨
  • test_open_invalid_zip (기존)

전체: cargo test --lib → 789 passed, 0 failed (기존 784 + 신규 5).

관련 맥락

  • MDM 프로젝트는 HWP/HWPX → Markdown 추출 엔진입니다. rhwp와 파싱 층에서 상호보완적이므로, 저희가 운영 중에 발견·수정한 보안·파싱 이슈를 역으로 기여하고 있습니다. 이게 첫 PR이고, 추후 shape="3D" strikeout 화이트리스트 PR도 보낼 예정입니다.
  • cfb_lenient.rs가 rhwp src/parser/cfb_reader.rs::LenientCfbReader에서 MDM으로 포팅된 것이어서, 이번에는 반대 방향으로 기여합니다.

🙇 감사합니다!

Problem
-------
HwpxReader::read_file and read_file_bytes called read_to_string /
read_to_end directly on zip::ZipFile readers with no size ceiling.
ZIP allows extreme compression ratios, so a few-KB .hwpx can claim a
single section.xml entry that expands to multi-GB — OOMing the host.
Any downstream (CLI, WASM, browser extension, VS Code) inherits the
risk since all of them funnel through parse_hwpx → HwpxReader.

Fix
---
Introduce a small read_limited(reader, max) helper using Read::take
so we never allocate past max+1 bytes. Apply:

  - MAX_XML_SIZE     = 32 MB  for text entries (section/header/hpf)
  - MAX_BINDATA_SIZE = 64 MB  for binary entries (images, fonts)

Real-world Korean government HWPX (보도자료·법령·판례) stays well under
these, so legitimate files are unaffected. Over-limit entries surface
as HwpxError::ZipError with "decompression bomb" in the message —
callers see a clean parse error instead of a crash.

Rationale for thresholds
------------------------
MDM (https://github.com/seunghan91/markdown-media) has been running
the same 32/64 MB caps in production across ~500 real HWPX fixtures
with zero false positives; see MDM commit a94b459 for the original
application of this pattern across HWP/HWPX/PDF parser paths.

Tests
-----
Five unit tests in parser::hwpx::reader:
  - under / at / over cap boundary for read_limited
  - a synthetic zip-bomb entry (MAX_XML_SIZE + 1 bytes of 'A',
    compressed to <1 MB) is rejected with ZipError

Full suite: 789 passed, 0 failed (baseline + 5 new).

No public API change: HwpxError variants unchanged, method signatures
unchanged. MAX_XML_SIZE / MAX_BINDATA_SIZE are exposed as pub const
so integrators can read the current caps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@edwardkim edwardkim left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로컬 검증 완료 ✅

  • cargo test 789개 전체 통과 (ZIP bomb 테스트 4개 포함)
  • read_limited() 함수로 XML(32MB)/BinData(64MB) 엔트리별 압축 해제 크기 제한
  • zip bomb 탐지 테스트가 실제 deflate 압축으로 검증 — 신뢰성 높음
  • 보안 문서화도 잘 되어 있습니다

중요한 보안 기여 감사합니다! 🛡️

@edwardkim edwardkim merged commit 93eb0b0 into edwardkim:main Apr 16, 2026
edwardkim added a commit that referenced this pull request Apr 19, 2026
본 v0.5.0 → v0.7.3 (라이브러리) / 0.2.0 (확장) 배포 주기에
머지된 외부 기여자 6명을 README 3개 변경 이력에 추가:

- @ahnbu — Ctrl+S file handle (PR #189, 기명시)
- @bapdodi — 회전 도형 리사이즈 + Flip (PR #192)
- @dreamworker0 — Windows CFB 경로 (PR #152)
- @marsimon — HWP 그림 효과 SVG (PR #149)
- @postmelee — 썸네일 + options CSP (PR #168)
- @seunghan91 — HWPX Serializer + 다수 (PR #170, #161, #163, #153, #154)

각 README 끝에 "기여자 감사" 섹션 추가 — 6명 일괄 인정.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants