Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 56 additions & 4 deletions src/parser/hwpx/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -2690,6 +2694,54 @@ mod tests {
assert_eq!(ps.spacing_after, 1136);
}

/// 손상 HWPX 의 HwpUnitChar `<hp:case>` 값이 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##"<?xml version="1.0" encoding="UTF-8"?>
<hh:head xmlns:hh="http://www.hancom.co.kr/hwpml/2011/head"
xmlns:hc="http://www.hancom.co.kr/hwpml/2011/core"
xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph">
<hh:refList>
<hh:paraProperties itemCnt="1">
<hh:paraPr id="1">
<hp:switch>
<hp:case hp:required-namespace="http://www.hancom.co.kr/hwpml/2016/HwpUnitChar">
<hh:margin>
<hc:left value="2000000000" unit="HWPUNIT"/>
<hc:right value="2000000000" unit="HWPUNIT"/>
</hh:margin>
<hh:lineSpacing type="FIXED" value="2000000000"/>
</hp:case>
</hp:switch>
</hh:paraPr>
</hh:paraProperties>
<hh:tabProperties itemCnt="1">
<hh:tabPr id="0" autoTabLeft="0" autoTabRight="0">
<hp:switch>
<hp:case hp:required-namespace="http://www.hancom.co.kr/hwpml/2016/HwpUnitChar">
<hh:tabItem pos="2147483648" type="LEFT" leader="NONE" unit="HWPUNIT"/>
</hp:case>
</hp:switch>
</hh:tabPr>
</hh:tabProperties>
</hh:refList>
</hh:head>"##;

// 핵심: 패닉하지 않고 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)은 <hp:case>(HwpUnitChar) 의 정수 나눗셈으로
Expand Down