应使用 filestream 分块读取并限制缓冲区大小,优先用 jsondocument.parse 解析 json,强制指定编码并截断解码,配合 try/catch 记录前 64 字节 hex;语料文件名统一用 sha256 哈希(小写),元数据存同名 .json;json/yaml/xml 先规范序列化再哈希,二进制语料跳过文件头后用 xxhash32;启用长路径支持,路径拼接一律用 path.combine。

如何用 C# 安全读取模糊测试语料文件
直接读取未经校验的 fuzzing 语料容易触发 OutOfMemoryException 或解析崩溃,尤其当文件被恶意构造为超长字符串、嵌套超深 JSON、或含 BOM/编码混杂内容时。C# 默认的 File.ReadAllText 不设限,会把整个文件拉进内存——一个 2GB 的畸形二进制文件足以让进程 OOM。
实操建议:
- 优先用
FileStream+BufferedStream分块读取,配合byte[]缓冲区控制单次载入上限(如 64KB) - 对文本类语料(如 JSON/XML),用
JsonDocument.Parse(.NET 5+)而非JObject.Parse,前者不构建完整对象树,内存更可控 - 强制指定编码:避免依赖
File.ReadAllText(path)自动探测,改用File.ReadAllBytes(path)后手动用Encoding.UTF8.GetString(bytes, 0, Math.Min(bytes.Length, 1024*1024))截断解码 - 加 try/catch 包裹,并在 catch 块中记录原始文件名与前 64 字节 hex(
BitConverter.ToString(bytes, 0, Math.Min(64, bytes.Length)).Replace("-", "")),方便后续归因
语料文件名与元数据怎么存才不踩坑
很多团队把语料存在 flat 目录下,靠文件名隐含含义(比如 crash_http_20240512.bin),但 Windows 文件系统对长名、特殊字符支持差,Linux 下大小写又敏感,Crash_HTTP_20240512.bin 和 crash_http_20240512.bin 可能被当成两个文件——而 fuzzer 实际只认一个哈希。
实操建议:
- 所有语料文件名统一用 SHA256 哈希值(小写)作为主键,如
9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 - 元数据(类型、触发 crash 的 target 函数、覆盖率信息)单独存为同名
.json文件,如9f86d081...a08.json,内容用Utf8JsonWriter写,避免Newtonsoft.Json的反射开销 - 禁止在文件名里塞路径、时间戳、用户名——这些该进元数据字段,不是文件系统的责任
- 用
Path.GetInvalidFileNameChars()过滤用户上传的原始文件名,替换为下划线,再哈希,防止非法字符导致ArgumentException
如何高效去重并识别语料相似性
单纯比哈希只能发现完全相同的文件,但 fuzzing 中大量语料是“语义等价”的:比如 {"id":1} 和 {"id": 1 }(空格差异)、或 XML 中属性顺序调换。用字符串哈希会漏掉这类重复,导致队列膨胀、覆盖率下降。
实操建议:
- 对 JSON/YAML/XML 类型,先用
JsonDocument.Parse解析后序列化为规范格式(无空格、属性排序),再算哈希;不要直接比原始字节 - 对二进制语料(如 PNG、PDF),跳过文件头(如 PNG 的前 8 字节),对剩余部分计算
XXHash32(比 MD5 快 10 倍,且抗碰撞足够 fuzzing 场景) - 避免用
Directory.GetFiles全量扫描去重——语料库大了就卡死;改用增量式:每次新增文件时,查元数据 DB(哪怕只是ConcurrentDictionary<string string></string>)是否已有相同规范哈希 - 慎用“模糊哈希”(如 ssdeep):.NET 生态没成熟封装,自己 port C 库易出内存错误,且对小文件(
Windows 上权限和路径长度限制怎么绕
默认 .NET File.Copy 在长路径(>260 字符)或只读目录下会抛 UnauthorizedAccessException 或 PathTooLongException,而 fuzzing 语料常带嵌套子目录(如 corpus/target_a/parser_v2/lexer_edge_case/),一跑就崩。
实操建议:
- 启用长路径支持:在
app.config加<appcontextswitchoverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false"></appcontextswitchoverrides>,.NET Core 3.0+ 默认开启,但桌面应用仍需显式配 - 用
\?前缀调用 Win32 API:路径拼成@"\?" + Path.GetFullPath(path),再传给CopyFileExP/Invoke(注意lpProgressRoutine设为IntPtr.Zero避免 GC 移动委托) - 语料根目录尽量设在短路径下,例如
C:uzzcorpus,而非C:UsersNamesource eposprojectrtifactsuzzingcorpus - 检查目标目录是否只读:用
File.GetAttributes(path).HasFlag(FileAttributes.ReadOnly),是的话先File.SetAttributes(path, FileAttributes.Normal)再操作
最麻烦的其实是跨平台一致性——Windows 路径分隔符是反斜杠,但语料元数据里若硬编码 ,Linux 下 File.Exists 就永远返回 false。所以所有路径拼接必须走 Path.Combine,所有分隔符判断必须用 Path.DirectorySeparatorChar,哪怕你 100% 确定只跑 Windows。这点很容易被忽略,直到某天 CI 切到 Ubuntu runner 才暴露。










