Infinite loop in ChunkHeader._load_strings / _load_templates on a crafted chunk (cyclic next_offset)
评估
调研方向
阅读 Evtx/Evtx.py,重点查看 ChunkHeader._load_strings、_load_templates 以及 Nodes.py 中的 next_offset 字段。先在超时限制下重现提供的循环偏移概念验证,然后检查两个加载器是否会遇到重复偏移。循环链能够返回而不是一直挂起,并且回归测试覆盖循环情况后,即为完成。
由索引模型根据 Issue 内容生成。
描述
Summary
Evtx.Evtx.ChunkHeader._load_strings and ._load_templates walk each hash-bucket's on-disk next_offset chain with no guard against an offset that repeats. Because next_offset is read straight from the file, a crafted chunk whose string (or template) chain is cyclic — node A's next_offset points to B and B's points back to A — makes the while ofs > 0: loop run forever. Parsing an untrusted .evtx then hangs the process indefinitely (CWE-835, uncontrolled loop / denial of service).
This is separate from the length-0 records() loop fixed in #17; these two chunk loaders have no cycle/visited protection, and the problem is still present on master (c000744).
Affected code
Evtx/Evtx.py:
def _load_strings(self):
...
for i in range(64):
ofs = self.unpack_dword(0x80 + (i * 4))
while ofs > 0:
string_node = self.add_string(ofs)
ofs = string_node.next_offset() # dword read straight from the file
next_offset is a dword read directly from the buffer (Nodes.py, NameStringNode / TemplateNode: declare_field("dword", "next_offset", 0x0)), so an attacker fully controls it. _load_templates has the same shape — it checks a 0x0C token and a self-pointer per node, but two mutually-pointing templates satisfy both checks, so it can cycle too.
Reachability
Evtx(path).records() iterates chunks without verifying their checksums, and rendering a record reaches add_string → _load_strings. So a single crafted file passed to evtx_dump badfile.evtx hangs the process.
Proof of concept
Builds one 64 KB chunk whose first string bucket points at a two-node cycle (A↔B). The vulnerable call never returns, so run it under a timeout:
import struct
from Evtx.Evtx import ChunkHeader
buf = bytearray(0x10000)
buf[0:8] = b"ElfChnk\x00"
a, b = 0x200, 0x210
struct.pack_into("<I", buf, 0x80, a) # string hash bucket[0] -> A
struct.pack_into("<I", buf, a, b) # A.next_offset -> B
struct.pack_into("<H", buf, a + 6, 0) # A.string_length = 0
struct.pack_into("<I", buf, b, a) # B.next_offset -> A (A <-> B)
struct.pack_into("<H", buf, b + 6, 0) # B.string_length = 0
ChunkHeader(bytes(buf), 0).strings() # never returns
Measured on master (c000744): a non-cyclic bucket returns in ~0.002 s; the cyclic one does not return (killed after 8 s). Reproduced on Python 3.14 (the loop is version-independent).
Suggested fix
Track the offsets already visited while walking a bucket and stop when one repeats. I'll open a PR with the fix plus a regression test that fails on current master and passes with the change.
Environment
- python-evtx master (
c000744), version 0.8.1 - Python 3.14 (reproduces on 3.9–3.14; not version-specific)
- 主要语言
- Python
- 星标
- 783
- 派生
- 169
- 平均合并
- 2 天 21 小时
- 30 天内合并 PR
- 1
环境准备
这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
williballenthin/python-evtx 的其他 Issue
-
难度 5/5 一周以上 新手友好度 25/100
-
难度 4/5 3-5 天 新手友好度 35/100
-
难度 2/5 1-3 小时 新手友好度 42/100
williballenthin/python-evtx#94 · 2 条评论 ·
-
bug
难度 4/5 3-5 天 新手友好度 35/100
williballenthin/python-evtx#85 · 2 条评论 ·
-
难度 2/5 1-3 小时 新手友好度 42/100
查看 williballenthin/python-evtx 的全部 Issue
相似的 Issue
-
good first issue infrastructure
难度 2/5 1-3 小时 新手友好度 76/100
kuznia-rdzeni/coreblocks#1135 ·
-
good first issue server
难度 2/5 1-3 小时 新手友好度 85/100
维护者通常 1 天内回复
-
documentation priority: high
难度 2/5 1-3 小时 新手友好度 78/100
a-lex-s/mealie-telegram-bot#54 ·
维护者通常 1 天内回复
-
type/test
难度 2/5 1-3 小时 新手友好度 76/100
SaltedTan/virgil-language-server#218 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 83/100
btclib-org/btclib-wallet#541 ·
维护者通常 1 天内回复