Skip to content

손상 입력의 과다 line_seg O(n²) 무한정지 DoS 방어 (#4813) - #4842

Closed
kevin9327 wants to merge 2 commits into
edwardkim:develfrom
kevin9327:gym_parser_infinite_loop
Closed

손상 입력의 과다 line_seg O(n²) 무한정지 DoS 방어 (#4813)#4842
kevin9327 wants to merge 2 commits into
edwardkim:develfrom
kevin9327:gym_parser_infinite_loop

Conversation

@kevin9327

Copy link
Copy Markdown
Contributor

문제 (#4813)

퍼징(gym 손상-강건성 감사)이 rhwp 가 손상된 문서에서 유한 시간에 끝나지 않는(무한정지급) DoS 를 실측했다. 결정적 재현:

data = open("samples/hwp3-sample14.hwp", "rb").read()
b = bytearray(data); b[len(b)*10//100] ^= 0xFF   # 10% 지점 바이트 플립
open("hang.hwp", "wb").write(bytes(b))
# rhwp info hang.hwp --json    → 60초 초과, 종료 안 함
# rhwp export-text hang.hwp    → 종료 안 함

원인 — 계측으로 확정 (파서 아님, 렌더 경로)

이슈의 원인 추정은 HWP3 파서 순회 루프였으나, 임시 반복 카운터와 단계 마커로 계측한 결과 실제 폭주 지점은 파서가 아니라 렌더 경로였다:

  • parse 는 정상 종료한다. infocompose(compose_lines) 단계에서, export-textlayout(build_page_tree) 단계에서 멈춘다 (둘 다 from_bytes → paginate/build_page_tree 경로).
  • 손상본의 한 문단이 line_seg 25,856 개 > 문자 21,454 개(text_start=[0, 9203, 0, 0, …] 비단조)를 갖는다. line_seg 하나는 화면상 한 줄이고 한 줄은 문자를 최소 1개 담으므로, 정상 문서는 언제나 line_seg 수 ≤ 문자 수 + 1 이다.
  • compose_lines·layout 은 line_seg 마다 para.text 를 다시 슬라이싱·배치하므로, 이 배열이 부풀면 O(line_seg 수 × 문단 길이) ≈ 5.5억 회 로 폭주한다 — 사실상 종료하지 않는 DoS.

수정

from_bytes 파싱 직후(compose/pagination/layout 이 저장 line_seg 를 소비하기 전) 물리적으로 불가능한 과다 line_seg 배열을 비우는 포맷 무관 가드 drop_corrupt_oversized_linesegs() 를 추가한다. 비운 문단은 기존 리플로우/합성 폴백(compose_lines 의 line_seg 부재 경로 등)이 텍스트로부터 정상 재구성한다. 상한을 문자 수 + 64 로 넉넉히 잡아 정상 문서(줄바꿈만 있는 문단 포함)는 절대 걸리지 않으므로 동작이 완전히 동일하다.

이슈는 HWP3 파서로 지목했으나, 근본 원인은 특정 포맷이 아니라 렌더러가 소비하는 손상 line_seg 배열이므로 로드 직후 공통 지점에 가드를 둔다(HWP3 전용 분기 없음).

검증

  • 재현 해소: 손상본 info 무한정지 → 0.23s, export-text1.78s 종료(정상 오류/성공, 패닉 없음).
  • 정상 회귀 없음: 정상 문서 10종(HWP3 원본 hwp3-sample14·hwp3-sample10, HWP5, HWPX)의 info·export-text 출력이 수정 전/후 바이트 동일(SHA-256 비교).
  • 회귀 테스트 2건 추가: 손상 배열 제거 / 정상 문단 보존.
  • cargo test --lib 전량 통과, rustfmt --edition 2021 --check·cargo clippy --lib 무경고.

비고

같은 퍼징 세트의 다른 표본(issue2063_huge_cellbreak_table.hwp, hwp3-sample10-hwp5.hwp)은 90초 계측 결과 유한 시간에 종료(디버그 빌드에서 느릴 뿐)했다 — 과다 line_seg 가 아닌 별개 원인이라 본 PR 범위 밖이다.

Closes #4813

파싱 직후, 저장된 line_seg 수가 문단 문자 수를 크게 초과하는(물리적으로
불가능한) 문단의 line_seg 배열을 비운다. line_seg 하나는 화면상 한 줄이고
한 줄은 문자를 최소 1개 담으므로 정상 문서는 언제나 line_seg 수 ≤ 문자 수 + 1
이다. 손상된 HWP/HWPX 는 길이·개수 필드 훼손으로 이 배열을 수만 개까지 부풀릴
수 있고(퍼징 실측 samples/hwp3-sample14.hwp 10% 바이트 플립본: 한 문단
line_seg 25,856 개 > 문자 21,454 개), 그러면 compose_lines·layout 이 line_seg
마다 문단 전체 텍스트를 다시 슬라이싱·배치해 O(line_seg 수 × 문단 길이)로
폭주한다 — info·export-text 가 유한 시간에 끝나지 않는 서비스 거부(DoS)다.

- drop_corrupt_oversized_linesegs(): 상한을 문자 수 + 64 로 넉넉히 잡아
  정상 문단(줄바꿈만 있는 문단 포함)은 절대 걸리지 않으므로 동작이 동일하다.
  포맷 무관 가드다. 비운 뒤 기존 리플로우/합성 폴백이 문단을 정상 재구성한다.
- 회귀 테스트 2건 추가(손상 배열 제거 / 정상 문단 보존).
- 실측: 손상본 info·export-text 무한정지 → 0.2s·1.8s 종료. 정상 문서 10종
  (HWP3·HWP5·HWPX)의 info·export-text 출력 바이트 동일(전/후 SHA-256 비교).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

CI Lint 의 "Check workspace members (FFI bindings, tools)" 가 실패했다
(PR edwardkim#4842). Build & Test 실패는 그 롤업이다.

src/document_core/commands/document.rs 의 경계 테스트가
`std::iter::repeat('\n').take(300).collect()` 로 문자열을 만드는데,
clippy manual_str_repeat 이 -D warnings 에 걸린다. `"\n".repeat(300)` 은
같은 값을 내면서 의도도 더 분명하다.

검증: cargo clippy --workspace --all-targets -- -D warnings 통과,
drop_corrupt_oversized_linesegs 테스트 2/2 통과, rustfmt 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jangster77
jangster77 self-requested a review August 15, 2026 13:29
jangster77 pushed a commit that referenced this pull request Aug 15, 2026
CI Lint 의 "Check workspace members (FFI bindings, tools)" 가 실패했다
(PR #4842). Build & Test 실패는 그 롤업이다.

src/document_core/commands/document.rs 의 경계 테스트가
`std::iter::repeat('\n').take(300).collect()` 로 문자열을 만드는데,
clippy manual_str_repeat 이 -D warnings 에 걸린다. `"\n".repeat(300)` 은
같은 값을 내면서 의도도 더 분명하다.

검증: cargo clippy --workspace --all-targets -- -D warnings 통과,
drop_corrupt_oversized_linesegs 테스트 2/2 통과, rustfmt 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jangster77

Copy link
Copy Markdown
Collaborator

통합 PR #4883(4412546)로 병합 완료했습니다.

원 head와 CI를 다시 확인해 누적 반영했고, 상세 검토·메인터너 보정·검증 근거는 archive 검토 기록에 남겼습니다.

중복 병합을 막기 위해 이 원 PR을 닫습니다. 감사합니다.

@jangster77 jangster77 closed this Aug 15, 2026
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