Skip to content
Closed
Show file tree
Hide file tree
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
8 changes: 4 additions & 4 deletions src/model/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,19 +222,19 @@ impl PageAreas {
if page_def.binding == BindingMethod::DuplexSided && is_even_page {
(
page_def.margin_right,
page_def.margin_left + page_def.margin_gutter,
page_def.margin_left.saturating_add(page_def.margin_gutter),
)
} else {
(
page_def.margin_left + page_def.margin_gutter,
page_def.margin_left.saturating_add(page_def.margin_gutter),
page_def.margin_right,
)
};

let mut content_left = effective_left;
let mut content_right = page_width.saturating_sub(effective_right);
// HWP 본문 시작 = margin_header + margin_top (한컴 도움말 기준)
let mut content_top = page_def.margin_header + page_def.margin_top;
let mut content_top = page_def.margin_header.saturating_add(page_def.margin_top);
// HWP 본문 끝 = height - margin_footer - margin_bottom
let mut content_bottom = page_height
.saturating_sub(page_def.margin_footer)
Expand Down Expand Up @@ -268,7 +268,7 @@ impl PageAreas {
left: content_left as i32,
top: content_bottom as i32,
right: content_right as i32,
bottom: (page_height - page_def.margin_footer) as i32,
bottom: page_height.saturating_sub(page_def.margin_footer) as i32,
};

PageAreas {
Expand Down
6 changes: 5 additions & 1 deletion src/wmf/converter/graphics_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ impl GraphicsObjects {
}

pub fn delete(&mut self, i: usize) {
self.0[i] = GraphicsObject::Null;
// 손상 WMF 의 DELETEOBJECT 가 개체 표 범위를 벗어난 인덱스를 주면 직접 색인은
// 패닉(DoS)한다. `get` 과 같은 관용 규약으로 범위 밖 삭제는 무시한다.
if let Some(slot) = self.0.get_mut(i) {
*slot = GraphicsObject::Null;
}
}

pub fn get(&self, i: usize) -> &GraphicsObject {
Expand Down
62 changes: 62 additions & 0 deletions tests/cli_exit_codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,68 @@ fn page_write_failure_is_counted_and_reported() {
let _ = std::fs::remove_file(&blocker);
}

// --- 손상 입력 DoS 패닉 방어 (writer/convert 경로) -----------------------

/// CARGO_BIN_EXE_rhwp(런타임 우선, #3289) 로 rhwp 를 실행해 Output 을 돌려준다.
fn run_cli(args: &[&str]) -> std::process::Output {
let bin = std::env::var("CARGO_BIN_EXE_rhwp")
.unwrap_or_else(|_| env!("CARGO_BIN_EXE_rhwp").to_string());
std::process::Command::new(bin)
.args(args)
.output()
.expect("rhwp 실행 실패")
}

/// 손상된 HWP3 의 footer_length 를 과대(0xFFFF)로 만들면 margin_footer 가 용지
/// 높이를 넘어 `PageAreas::from_page_def_for_page` 의 본문 영역 계산에서 u32
/// 뺄셈이 언더플로해 convert/export-hwpx/export-markdown 이 패닉(종료 101)하던
/// DoS 를 막는다. HWP3 DocInfo 는 파일 오프셋 30 에서 시작하고 footer_length(u16)
/// 는 그 안 오프셋 20 → 파일 바이트 50..52 다. saturating 화라 정상 데이터 동작은
/// 불변이고, 손상 입력은 패닉 대신 우아하게(101 이 아닌 코드) 끝나야 한다.
#[test]
fn corrupt_page_margin_does_not_panic_in_writer() {
let mut data = std::fs::read(sample_path()).expect("hwp3 샘플 읽기");
assert!(
data.len() > 52,
"샘플이 DocInfo(footer_length) 를 포함할 만큼 커야 한다"
);
// footer_length = 0xFFFF → margin_footer 과대 → page_height - margin_footer 언더플로.
data[50] = 0xFF;
data[51] = 0xFF;

let corrupt = unique_temp_path("corrupt-footer.hwp");
std::fs::write(&corrupt, &data).expect("손상 샘플 쓰기");
let corrupt = corrupt.to_str().expect("utf-8 경로").to_string();

let mut out_hwp = unique_temp_path("corrupt-footer-out");
out_hwp.set_extension("hwp");
let out_hwp = out_hwp.to_str().expect("utf-8 경로").to_string();
let mut out_hwpx = unique_temp_path("corrupt-footer-out");
out_hwpx.set_extension("hwpx");
let out_hwpx = out_hwpx.to_str().expect("utf-8 경로").to_string();
let md_dir = unique_temp_path("corrupt-footer-md");
let md_dir = md_dir.to_str().expect("utf-8 경로").to_string();

for args in [
vec!["convert", &corrupt, &out_hwp],
vec!["export-hwpx", &corrupt, &out_hwpx],
vec!["export-markdown", &corrupt, "-o", &md_dir],
] {
let output = run_cli(&args);
assert_ne!(
output.status.code(),
Some(101),
"손상 입력이 패닉(101)하면 안 된다 — 우아하게 처리해야 한다\n{}",
describe(&args, &output)
);
}

let _ = std::fs::remove_file(&corrupt);
let _ = std::fs::remove_file(&out_hwp);
let _ = std::fs::remove_file(&out_hwpx);
let _ = std::fs::remove_dir_all(&md_dir);
}

// --- 0: 성공 경로 회귀 방지 ----------------------------------------------

#[test]
Expand Down