🔴 [H2] mmap 与索引构建之间存在 TOCTOU 竞态 #2

Closed
opened 2026-06-03 13:52:50 +08:00 by dailz · 1 comment
Owner

文件: core/io/file_reader.rs:23-42
分类: 并发安全

问题: FileReader::open() 先 mmap 文件,再通过 BufReader 读取文件构建行索引。如果文件在两步之间被修改,line_index 描述的字节范围可能与 mmap 数据不一致。

let mmap = unsafe { memmap2::Mmap::map(&file) }?;  // 快照 A
let line_index = LineIndex::from_reader(&mut reader)?;  // 快照 B(可能已变)

影响: get_line() 可能返回错误行或 panic。

建议修复: 从 mmap 字节直接构建索引,确保数据和索引来自同一快照。

**文件**: `core/io/file_reader.rs:23-42` **分类**: 并发安全 **问题**: `FileReader::open()` 先 mmap 文件,再通过 BufReader 读取文件构建行索引。如果文件在两步之间被修改,`line_index` 描述的字节范围可能与 `mmap` 数据不一致。 ```rust let mmap = unsafe { memmap2::Mmap::map(&file) }?; // 快照 A let line_index = LineIndex::from_reader(&mut reader)?; // 快照 B(可能已变) ``` **影响**: `get_line()` 可能返回错误行或 panic。 **建议修复**: 从 mmap 字节直接构建索引,确保数据和索引来自同一快照。
dailz added the bugseverity/higharea/io labels 2026-06-03 13:52:50 +08:00
dailz closed this issue 2026-06-03 15:08:36 +08:00
Author
Owner

修复方案

根因: FileReader::open()reload() 中,mmap 映射(快照 A)和 LineIndex::from_reader(BufReader) (快照 B)是两个独立的文件读取操作。如果文件在两步之间被外部修改,索引偏移量与 mmap 字节数据不一致,导致 get_line() 返回错误内容或越界 panic。

修复: 将 LineIndex::from_reader(BufReader<&File>) 替换为 LineIndex::from_bytes(&mmap),让行索引直接从 mmap 内存快照构建,确保数据和索引来自同一个不可变的快照。

变更文件: crates/core/src/io/file_reader.rs

  • open(): from_readerfrom_bytes(&mmap)
  • reload(): from_readerfrom_bytes(&mmap)
  • update_for_append(): 无需改动(已从 mmap 切片构建)

新增测试 (4 个):

  • test_open_from_bytes_matches_from_reader: 验证 from_bytes 与 from_reader 产出完全一致的索引(空文件/单行/跨块 256/300/512 行)
  • test_reload_after_external_modify_returns_correct_content: 外部覆盖文件后 reload 返回新内容
  • test_reload_after_truncate_then_rewrite_no_stale_data: 截断后写入更短内容,旧行不可访问
  • test_open_reload_idempotent_cross_block: 600 行跨块场景 open 与 reload 逐行一致

提交: 24fe97a

## 修复方案 **根因**: `FileReader::open()` 和 `reload()` 中,mmap 映射(快照 A)和 `LineIndex::from_reader(BufReader)` (快照 B)是两个独立的文件读取操作。如果文件在两步之间被外部修改,索引偏移量与 mmap 字节数据不一致,导致 `get_line()` 返回错误内容或越界 panic。 **修复**: 将 `LineIndex::from_reader(BufReader<&File>)` 替换为 `LineIndex::from_bytes(&mmap)`,让行索引直接从 mmap 内存快照构建,确保数据和索引来自同一个不可变的快照。 **变更文件**: `crates/core/src/io/file_reader.rs` - `open()`: `from_reader` → `from_bytes(&mmap)` - `reload()`: `from_reader` → `from_bytes(&mmap)` - `update_for_append()`: 无需改动(已从 mmap 切片构建) **新增测试** (4 个): - `test_open_from_bytes_matches_from_reader`: 验证 from_bytes 与 from_reader 产出完全一致的索引(空文件/单行/跨块 256/300/512 行) - `test_reload_after_external_modify_returns_correct_content`: 外部覆盖文件后 reload 返回新内容 - `test_reload_after_truncate_then_rewrite_no_stale_data`: 截断后写入更短内容,旧行不可访问 - `test_open_reload_idempotent_cross_block`: 600 行跨块场景 open 与 reload 逐行一致 **提交**: 24fe97a
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dailz/logViewer#2