OpenConsole 模糊测试指南:用 Fuzzing 构建配置与 OneFuzz 对 Windows 控制台宿主做 LibFuzzer 模糊测试
OpenConsole 模糊测试指南用 Fuzzing 构建配置与 OneFuzz 对 Windows 控制台宿主做 LibFuzzer 模糊测试【免费下载链接】terminalThe new Windows Terminal and the original Windows console host, all in the same place!项目地址: https://gitcode.com/GitHub_Trending/term/terminal本篇基于 doc/fuzzing.md 展开讲解 OpenConsoleWindows Terminal 与 Windows 控制台宿主 conhost 的统一仓库如何接入 LibFuzzer 模糊测试体系如何在本地以Fuzzing配置构建出可运行的模糊测试目标fuzz target、LLVMFuzzerTestOneInput入口函数如何驱动 conhost 的核心缓冲区写入路径以及如何用 OneFuzz 把模糊测试任务跑在 CI 上并配置缺陷告警。读完本文你能独立完成一次本地 fuzzer 构建与运行并理解 Fuzzing 构建配置中 ASAN、覆盖插桩与 vcpkg triplet 的协作关系。模糊测试在 OpenConsole 中的定位OpenConsole 是 conhostWindows 控制台宿主与 Windows Terminal 的合并代码库其核心输入路径——控制序列解析、缓冲区写入——长期暴露给任意用户与外部程序产生的字节流是典型的模糊测试目标。仓库为此提供了一条完整的工具链本地模糊测试解决方案内置Fuzzing构建配置产物是一个自带 LibFuzzer 驱动的可执行程序fuzzer 可执行文件对给定的测试用例corpus 文件反复变异、注入CI 模糊测试通过微软的 OneFuzz 服务在持续集成中长时间运行 fuzzer并把新发现的 bug 通过通知系统MS Teams、Azure DevOps 工作项推送给开发者。本地与 CI 共用同一套构建产物因此本地配置好Fuzzing构建是理解整条链路的前提。Fuzzing 构建配置ASAN、覆盖插桩与静态链接仓库在所有 C 项目上通过 src/common.build.pre.props 声明了Fuzzing配置Fuzzing|Win32、Fuzzing|x64、Fuzzing|ARM64三种平台组合该配置下的关键编译/链接行为如下见 src/common.build.pre.props 第 248–264 行附近项目设置作用编译选项/fsanitizeaddress /fsanitize-coverageinline-bool-flag /fsanitize-coverageedge /fsanitize-coveragetrace-cmp /fsanitize-coveragetrace-div启用地址消毒剂ASAN与覆盖插桩边覆盖、比较追踪、除法追踪让 fuzzer 知道哪些分支被执行过、哪些比较值值得探索CRT 链接RuntimeLibrary MultiThreaded静态 CRTLibFuzzer 要求静态运行时避免运行时库与 ASAN 注入冲突预处理器宏FUZZING_BUILD源码据此切换入口函数main()还是LLVMFuzzerInitialize链接依赖libsancov.lib、clang_rt.asan_dynamic-arch.lib挂接 ASAN 动态库架构名通过OCClangArchitectureName映射x64 →x86_64x86 →i386由于全静态链接vcpkg 侧也需要配合。Fuzzing 配置会额外传入 overlay tripletsrc/common.build.pre.props--overlay-triplets$(SolutionDir)\dep\vcpkg-overlay-triplets\fuzzing对应 triplet 文件 dep/vcpkg-overlay-triplets/fuzzing/x64-windows-static.cmake 在官方x64-windows-statictriplet 基础上做了两点强化# Same as the official x64-windows-static triplet set(VCPKG_TARGET_ARCHITECTURE x64) set(VCPKG_CRT_LINKAGE static) set(VCPKG_LIBRARY_LINKAGE static) # ...but with explicit platform toolset, so that future toolsets # arent automatically picked up (it defaults to the latest one). set(VCPKG_PLATFORM_TOOLSET v145) set(VCPKG_CXX_FLAGS /fsanitizeaddress) set(VCPKG_C_FLAGS /fsanitizeaddress)即第三方依赖第三方 C/C 库也以 ASAN 编译并静态链接保证依赖内部触发的内存错误同样能被捕获同时显式锁定平台工具集版本避免将来新版工具集自动生效导致的构建漂移。Fuzzing 配置还把 vcpkg 安装目录独立为obj\$(Platform)\vcpkg-fuzzing与普通构建的vcpkg目录隔离避免两套依赖互相污染。从源码结构看个别子项目在 Fuzzing 下还会改变产物形态例如 src/host/proxy/Host.Proxy.vcxproj 将ConfigurationType从 DLL 改为StaticLibrary——注释说明其原因是该代理 DLL 在 Fuzzing 构建中并非模糊测试目标强行产出可用 PE 会失败改成静态库既能参与链接又绕开该问题。本地设置 fuzzerOpenConsole 可以以Fuzzing配置构建。要接入一个 fuzzer核心是提供LLVMFuzzerTestOneInput函数——它充当 LibFuzzer 与被测代码之间的挂接点fuzzer 从这里附着并注入测试用例签名固定为extern C int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);构建与运行在Fuzzing配置下构建 OpenConsole 解决方案会输出一个直接运行 fuzzer 的可执行程序对给定的测试用例文件执行注入。以仓库中的 conhost fuzzer 为例期望的产物位于bin\x64\Fuzzing\OpenConsoleFuzzer.exe该可执行文件由 src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj 定义TargetName即OpenConsoleFuzzer。注意它在 Fuzzing 配置下才把 LibFuzzer 运行时加入链接行ItemDefinitionGroup Condition$(Configuration)Fuzzing !-- 理论上我们可能希望在未启用 Fuzzing 时用普通 main() 构建 因此只在 Fuzzing 配置下把 fuzzer 加进链接行 -- Link AdditionalDependencieswinmm.lib;imm32.lib;clang_rt.fuzzer_MT-$(OCClangArchitectureName).lib;%(AdditionalDependencies)/AdditionalDependencies /Link /ItemDefinitionGroupclang_rt.fuzzer_MT-arch.lib是 LibFuzzer 的主机运行时MT 多线程静态 CRT与上面MultiThreaded设定一致它提供 fuzzer 主循环读取种子语料seed corpus→ 变异 → 调用LLVMFuzzerTestOneInput→ 依据覆盖反馈决定是否保留新用例。conhost fuzzer 的实现剖析fuzzmain.cpp 是这个 fuzzer 的全部逻辑值得逐段理解NullDeviceComm 设备桩。conhost 正常启动时会与 ConDrv 设备驱动通信。fuzz 环境里没有驱动fuzzmain.cpp 定义了一个空实现的IDeviceComm第 14–56 行ReadIo/ReadInput中直接挂起当前线程让 IO 线程安静退出——注释解释了原因fuzzer 不需要设备 IO 线程。StartNullConsole第 58–98 行。在连接之前先把globals.pDeviceComm替换为NullDeviceComm注释直言 Leak this——fuzzer 进程生命周期内无需释放再以INVALID_HANDLE_VALUE调用ConsoleCreateIoThreadLegacy注释指出该空句柄本会在ConDrvDeviceComm中被检出而这里已被提前替换掉。随后在锁内分配根进程句柄、伪造一份CONSOLE_API_CONNECTINFO80x25 缓冲区与窗口、标题 Fuzzing Harness并调用ConsoleAllocateConsole完成控制台分配再初始化命令历史。入口切换。RunConhost()是导出的宿主启动函数而 fuzzer 入口通过FUZZING_BUILD宏切换第 117–125 行#ifdef FUZZING_BUILD extern C __declspec(dllexport) int LLVMFuzzerInitialize(int* /*argc*/, char*** /*argv*/) #else int main(int /*argc*/, char** /*argv*/) #endif { RETURN_IF_FAILED(RunConhost()); return 0; }Fuzzing 构建下它成为 LibFuzzer 的初始化钩子每个测试用例批次前执行一次普通构建下则是main因此同一份代码两种用途。注释还提到一个有意思的取舍传入 stdin/stdout 句柄本可以像 conpty 一样驱动它、顺带测试 VT 渲染器但目前选择像 conhost 一样驱动。 4.核心注入点第 127–137 行extern C __declspec(dllexport) int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { auto gci Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation(); const auto u16String{ til::u8u16(std::string_view{ reinterpret_castconst char*(data), size }) }; til::CoordType scrollY{}; gci.LockConsole(); auto u wil::scope_exit([]() { gci.UnlockConsole(); }); WriteCharsLegacy(gci.GetActiveOutputBuffer(), u16String, scrollY); return 0; }LibFuzzer 每次变异出的字节流data先经til::u8u16做 UTF-8 → UTF-16 转换然后在持有控制台锁的情况下调用WriteCharsLegacy写入活动输出缓冲区。也就是说模糊测试真正压测的是 conhost 的字符/控制序列写入路径任何由恶意或畸形输入触发的越界写、非法状态、崩溃都会在 ASAN 保护下以 sanitizer 报告的形式暴露。仓库中的其他模糊测试形态VT 解析器 fuzzer定向模糊测试src/terminal/parser/ft_fuzzer/VTCommandFuzzer.cpp 是一台基于令牌token生成的定向 fuzzer。它按 VT100 规格构造ESC0x1B、CSIESC [及 C1 单字节变体 0x9B、OSCESC ]序列第 13–23 行并以概率表g_tokenGenerators随机组合 SGR、CUX、私有序列、设备属性查询、光标寻址、硬/软复位、VT52 序列等令牌配合少量无效令牌与文本噪声。相比纯随机字节这种语法感知的生成方式能更快到达解析器的深层状态是模糊测试输入生成器设计的一个典型范例。小型函数级 fuzz 示例src/til/ut_til/string.cpp 中保留了一段#if 0包裹的LLVMFuzzerTestOneInput第 81–119 行用于以 clang 的strtoull为参照对parse_u64做等价性模糊验证注释记录了当时的运行方式clang -fsanitizeaddress,undefined,fuzzer -stdc17 file.cpp16 个并行任务跑 20 分钟。它展示了不依赖完整解决方案也能给单个函数挂 fuzzer 的轻量路径验证结果最终沉淀为该文件中正式的单元测试如parse_u64_overflow。使用 OneFuzz 在 CI 上运行 fuzzerOneFuzz 允许把 fuzzer 跑在 CI 中并在发现新 bug 时得到告警。以下流程继承自 doc/fuzzing.md。安装 OneFuzz CLI从 OneFuzz 项目的 releases 页下载最新版 OneFuzz CLIonefuzz命令行工具并安装到 PATH。配置 OneFuzz在本地运行 OneFuzz 前需要配置 endpoint、client ID 与 client secret。Windows 团队有一份预设配置可参考文档指向 osgwiki 上的 OneFuzz 配置教程页。配置命令onefuzz config --endpoint $(endpoint) --client_id $(client_id) --authority $(authority) --tenant_domain $(tenant_domain)注意项目的流水线pipeline已经配置了这些变量因此在 Azure DevOps 上运行时无需关心这一步。在 OneFuzz 上运行任务配置完成后用如下命令创建一个 libfuzzer 任务onefuzz template libfuzzer basic project name build pool --target_exe exe_path参数说明参数含义project项目名name测试任务名称build构建标识即 commit SHA1pool运行该任务的 VM 池exe_path构建项目输出的 fuzzer 可执行文件路径如bin\x64\Fuzzing\OpenConsoleFuzzer.exe命令执行后还会以 JSON 格式输出更多任务信息例如 job ID可用于后续查询与追踪。启用通知注意项目流水线已内置该功能此处仅是快速搭建指南便于自行配置与调整。OneFuzz 支持同时启用多种通知系统包括 MS Teams 与 Azure DevOpsOneFuzz 的 getting-started、notifications 文档分别给出了 Teams 与 Azure DevOps 的配置方法。本项目的流水线配置为在发现缺陷时自动创建 Azure DevOps 工作项使每个 crash 都有可跟踪、可指派、可复现的最小用例OneFuzz 会自动归档触发崩溃的输入。适用前提与限制本地构建需要 Windows 环境、Visual StudioFuzzing 配置使用 Clang 编译器选项与clang_rt运行时仓库在 doc/building.md 中描述了总体构建前提Fuzzing配置与普通 Debug/Release 构建依赖目录隔离vcpkg-fuzzing首次构建会额外拉取并按 triplet 重编第三方依赖耗时明显更长conhost fuzzer 的LLVMFuzzerTestOneInput面向以 conhost 方式驱动的写入路径注释中明确提到若改用 conpty 方式驱动传入 stdin/stdout还能覆盖 VT 渲染器路径这属于文档中标注的后续扩展方向而非当前默认行为在 Azure DevOps 中直接运行时endpoint/client 等变量由流水线提供本地独立运行 OneFuzz 则必须自行完成onefuzz config步骤。小结OpenConsole 的模糊测试体系由两层构成本地层是解决方案级Fuzzing配置ASAN 覆盖插桩 静态 CRT LibFuzzer 运行时产物如 Host.FuzzWrapper.vcxproj 输出的OpenConsoleFuzzer.exe通过 fuzzmain.cpp 中的LLVMFuzzerTestOneInput把变异字节流注入 conhost 的WriteCharsLegacy写入路径CI 层是 OneFuzz用onefuzz template libfuzzer basic把同一可执行文件挂到 VM 池上长期运行并通过通知/工作项机制闭环缺陷。新增自己的 fuzzer 时只需遵循同样的模式提供LLVMFuzzerTestOneInput入口让Fuzzing配置完成 sanitizer 与 fuzzer 运行时的装配。【免费下载链接】terminalThe new Windows Terminal and the original Windows console host, all in the same place!项目地址: https://gitcode.com/GitHub_Trending/term/terminal创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考