From 368494dd07ad3da3c79a3ea0e0612a521e12f187 Mon Sep 17 00:00:00 2001 From: kevin9327 <5299031+kevin9327@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:57:54 +0900 Subject: [PATCH] =?UTF-8?q?fix(parser/hwpx):=20HwpUnitChar=20=EA=B0=92=202?= =?UTF-8?q?=C3=97=20=EC=8A=A4=EC=BC=80=EC=9D=BC=20=EC=A0=95=EC=88=98=20?= =?UTF-8?q?=EC=98=A4=EB=B2=84=ED=94=8C=EB=A1=9C=20=ED=8C=A8=EB=8B=89=20?= =?UTF-8?q?=ED=95=98=EB=93=9C=EB=8B=9D=20(#4839)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 손상 .hwpx 의 header.xml HwpUnitChar 안 극단 정수값이 2× IR 스케일 변환(* 2)에서 곱셈 오버플로 패닉을 유발해 info/export-text/ export-structure/convert 전부가 손상 문서 한 건에 프로세스째 죽던 DoS 를 수정한다. header.rs 세 곳의 * 2 를 saturating_mul(2) 로 교체: - 1288: 문단 여백 left/right/intent/prev/next (i32) - 1357: 문단 lineSpacing (i32) - 1907: 탭 position (u32) 정상 문서 값은 수천 HWPUNIT 수준이라 무영향이고, 극단값만 i32::MAX/ u32::MAX 로 포화되어 그레이스풀하게 처리된다. 회귀 테스트 추가. 정상 샘플 3종 info/export-text/export-structure 출력 바이트 동일, cargo test --lib 3703 passed/0 failed. Co-Authored-By: Claude Opus 4.8 --- src/parser/hwpx/header.rs | 60 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/src/parser/hwpx/header.rs b/src/parser/hwpx/header.rs index b83b733331..2a55359817 100644 --- a/src/parser/hwpx/header.rs +++ b/src/parser/hwpx/header.rs @@ -1285,7 +1285,9 @@ fn parse_para_shape_switch( // HWP3 암호 원본의 별도 spacing 계약은 HWP3 parser // 안에서만 처리한다. HWPX 전체에 반감 적용하면 // 일반 HWPX 문단 흐름과 기준 HWP3 변환본이 함께 밀린다. - let val2x = val * 2; + // 손상 문서의 극단 value 는 i32 곱셈 오버플로 패닉을 + // 유발하므로 saturating 으로 막는다(정상값은 무영향). + let val2x = val.saturating_mul(2); match tag_name { b"left" => { ps.margin_left = val2x; @@ -1354,7 +1356,8 @@ fn parse_para_shape_switch( let effective_type = ls_type.unwrap_or(ps.line_spacing_type); ps.line_spacing = match effective_type { LineSpacingType::Percent => v, - _ => v * 2, + // 손상 value 의 i32 곱셈 오버플로 패닉 차단. + _ => v.saturating_mul(2), }; case_line_spacing = Some(v); } @@ -1903,8 +1906,9 @@ fn parse_tab_def( let mut item = parse_tab_item(ce); if in_hwpunitchar_case { // HwpUnitChar 값은 실제 HWPUNIT(1× 스케일)이므로 - // HWP 바이너리와 동일한 2× 스케일로 변환 - item.position *= 2; + // HWP 바이너리와 동일한 2× 스케일로 변환. + // 손상 pos 의 u32 곱셈 오버플로 패닉 차단(정상값은 무영향). + item.position = item.position.saturating_mul(2); td.tabs.push(item); found_case = true; } else if in_default { @@ -2690,6 +2694,54 @@ mod tests { assert_eq!(ps.spacing_after, 1136); } + /// 손상 HWPX 의 HwpUnitChar `` 값이 2× IR 스케일 변환에서 정수 + /// 곱셈 오버플로 패닉(DoS)을 일으키지 않고 saturating 으로 안전 처리되는지 + /// 검증한다. 종전에는 `value`/`pos` 극단값이 `parse_hwpx_header` 를 패닉시켜 + /// info/export-text/export-structure/convert 전부가 손상 문서 한 건에 + /// 죽었다(header.rs:1288/1357/1907, `attempt to multiply with overflow`). + #[test] + fn hwpunitchar_oversized_value_saturates_without_panic() { + let xml = r##" + + + + + + + + + + + + + + + + + + + + + + + + + +"##; + + // 핵심: 패닉하지 않고 Ok 를 돌려준다. + let (doc_info, _) = parse_hwpx_header(xml).expect("손상 HwpUnitChar 값은 패닉 없이 파싱"); + let ps = &doc_info.para_shapes[1]; + // 2_000_000_000 × 2 는 i32 를 넘으므로 i32::MAX 로 포화된다. + assert_eq!(ps.margin_left, i32::MAX); + assert_eq!(ps.margin_right, i32::MAX); + assert_eq!(ps.line_spacing, i32::MAX); + // 2_147_483_648(u32) × 2 는 u32 를 넘으므로 u32::MAX 로 포화된다. + assert_eq!(doc_info.tab_defs[0].tabs[0].position, u32::MAX); + } + #[test] fn odd_para_margin_survives_hwpx_serialize_parse_roundtrip() { // [#3368] 홀수 여백(ml=101)은 (HwpUnitChar) 의 정수 나눗셈으로