news 2026/9/6 22:02:22

OpenConsole 模糊测试指南:用 Fuzzing 构建配置与 OneFuzz 对 Windows 控制台宿主做 LibFuzzer 模糊测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 展开,讲解 OpenConsole(Windows Terminal 与 Windows 控制台宿主 conhost 的统一仓库)如何接入 LibFuzzer 模糊测试体系:如何在本地以Fuzzing配置构建出可运行的模糊测试目标(fuzz target)、LLVMFuzzerTestOneInput入口函数如何驱动 conhost 的核心缓冲区写入路径,以及如何用 OneFuzz 把模糊测试任务跑在 CI 上并配置缺陷告警。读完本文,你能独立完成一次本地 fuzzer 构建与运行,并理解 Fuzzing 构建配置中 ASAN、覆盖插桩与 vcpkg triplet 的协作关系。

模糊测试在 OpenConsole 中的定位

OpenConsole 是 conhost(Windows 控制台宿主)与 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|Win32Fuzzing|x64Fuzzing|ARM64三种平台组合),该配置下的关键编译/链接行为如下(见 src/common.build.pre.props 第 248–264 行附近):

项目设置作用
编译选项/fsanitize=address /fsanitize-coverage=inline-bool-flag /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div启用地址消毒剂(ASAN)与覆盖插桩(边覆盖、比较追踪、除法追踪),让 fuzzer 知道哪些分支被执行过、哪些比较值值得探索
CRT 链接RuntimeLibrary = MultiThreaded(静态 CRT)LibFuzzer 要求静态运行时,避免运行时库与 ASAN 注入冲突
预处理器宏FUZZING_BUILD源码据此切换入口函数(main()还是LLVMFuzzerInitialize
链接依赖libsancov.libclang_rt.asan_dynamic-<arch>.lib挂接 ASAN 动态库;架构名通过OCClangArchitectureName映射(x64 →x86_64,x86 →i386

由于全静态链接,vcpkg 侧也需要配合。Fuzzing 配置会额外传入 overlay triplet(src/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 # aren't automatically picked up (it defaults to the latest one). set(VCPKG_PLATFORM_TOOLSET v145) set(VCPKG_CXX_FLAGS /fsanitize=address) set(VCPKG_C_FLAGS /fsanitize=address)

即第三方依赖(第三方 C/C++ 库)也以 ASAN 编译并静态链接,保证依赖内部触发的内存错误同样能被捕获;同时显式锁定平台工具集版本,避免将来新版工具集自动生效导致的构建漂移。Fuzzing 配置还把 vcpkg 安装目录独立为obj\$(Platform)\vcpkg-fuzzing,与普通构建的vcpkg目录隔离,避免两套依赖互相污染。

从源码结构看,个别子项目在 Fuzzing 下还会改变产物形态:例如 src/host/proxy/Host.Proxy.vcxproj 将ConfigurationType从 DLL 改为StaticLibrary——注释说明其原因是该代理 DLL 在 Fuzzing 构建中并非模糊测试目标,强行产出可用 PE 会失败,改成静态库既能参与链接又绕开该问题。

本地设置 fuzzer

OpenConsole 可以以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 定义(TargetNameOpenConsoleFuzzer)。注意它在 Fuzzing 配置下才把 LibFuzzer 运行时加入链接行:

<ItemDefinitionGroup Condition="'$(Configuration)'=='Fuzzing'"> <!-- 理论上我们可能希望在未启用 Fuzzing 时用普通 main() 构建, 因此只在 Fuzzing 配置下把 fuzzer 加进链接行 --> <Link> <AdditionalDependencies>winmm.lib;imm32.lib;clang_rt.fuzzer_MT-$(OCClangArchitectureName).lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup>

clang_rt.fuzzer_MT-<arch>.lib是 LibFuzzer 的主机运行时(MT = 多线程静态 CRT,与上面MultiThreaded设定一致),它提供 fuzzer 主循环:读取种子语料(seed corpus)→ 变异 → 调用LLVMFuzzerTestOneInput→ 依据覆盖反馈决定是否保留新用例。

conhost fuzzer 的实现剖析

fuzzmain.cpp 是这个 fuzzer 的全部逻辑,值得逐段理解:

  1. NullDeviceComm 设备桩。conhost 正常启动时会与 ConDrv 设备驱动通信。fuzz 环境里没有驱动,fuzzmain.cpp 定义了一个空实现的IDeviceComm(第 14–56 行):ReadIo/ReadInput中直接挂起当前线程,让 IO 线程安静退出——注释解释了原因:"fuzzer 不需要设备 IO 线程"。
  2. StartNullConsole(第 58–98 行)。在"连接"之前先把globals.pDeviceComm替换为NullDeviceComm(注释直言 "Leak this"——fuzzer 进程生命周期内无需释放),再以INVALID_HANDLE_VALUE调用ConsoleCreateIoThreadLegacy(注释指出该空句柄本会在ConDrvDeviceComm中被检出,而这里已被提前替换掉)。随后在锁内分配根进程句柄、伪造一份CONSOLE_API_CONNECTINFO(80x25 缓冲区与窗口、标题 "Fuzzing Harness")并调用ConsoleAllocateConsole完成控制台分配,再初始化命令历史。
  3. 入口切换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_cast<const 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 规格构造ESC(0x1B)、CSI(ESC [,及 C1 单字节变体 0x9B)、OSC(ESC ])序列(第 13–23 行),并以概率表(g_tokenGenerators)随机组合 SGR、CUX、私有序列、设备属性查询、光标寻址、硬/软复位、VT52 序列等令牌,配合少量无效令牌与文本噪声。相比纯随机字节,这种"语法感知"的生成方式能更快到达解析器的深层状态,是模糊测试输入生成器设计的一个典型范例。
  • 小型函数级 fuzz 示例:src/til/ut_til/string.cpp 中保留了一段#if 0包裹的LLVMFuzzerTestOneInput(第 81–119 行),用于以 clang 的strtoull为参照对parse_u64做等价性模糊验证,注释记录了当时的运行方式:clang++ -fsanitize=address,undefined,fuzzer -std=c++17 file.cpp,16 个并行任务跑 20 分钟。它展示了不依赖完整解决方案也能给单个函数挂 fuzzer 的轻量路径;验证结果最终沉淀为该文件中正式的单元测试(如parse_u64_overflow)。

使用 OneFuzz 在 CI 上运行 fuzzer

OneFuzz 允许把 fuzzer 跑在 CI 中,并在发现新 bug 时得到告警。以下流程继承自 doc/fuzzing.md。

安装 OneFuzz CLI

从 OneFuzz 项目的 releases 页下载最新版 OneFuzz CLI(onefuzz命令行工具)并安装到 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 SHA1)
pool运行该任务的 VM 池
exe_path构建项目输出的 fuzzer 可执行文件路径(如bin\x64\Fuzzing\OpenConsoleFuzzer.exe

命令执行后还会以 JSON 格式输出更多任务信息(例如 job ID),可用于后续查询与追踪。

启用通知

注意:项目流水线已内置该功能,此处仅是快速搭建指南,便于自行配置与调整。

OneFuzz 支持同时启用多种通知系统,包括 MS Teams 与 Azure DevOps(OneFuzz 的 getting-started、notifications 文档分别给出了 Teams 与 Azure DevOps 的配置方法)。本项目的流水线配置为在发现缺陷时自动创建 Azure DevOps 工作项,使每个 crash 都有可跟踪、可指派、可复现的最小用例(OneFuzz 会自动归档触发崩溃的输入)。

适用前提与限制

  • 本地构建需要 Windows 环境、Visual Studio(Fuzzing 配置使用 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 22:00:29

智慧公安大数据平台与资源中心建设方案实战拆解

简介&#xff1a;这份智慧公安大数据平台与资源中心建设方案PPT&#xff0c;面向智慧城市、公安信息化与大数据平台设计人员&#xff0c;提供一份可落地的顶层规划参考。方案以“聚、管、通、用、安”为主线&#xff0c;围绕数据采集、存储、计算、治理、共享与应用展开&#x…

作者头像 李华
网站建设 2026/9/6 21:57:24

docx打不开?从格式原理到批量转换,彻底解决Word 2003兼容问题

简介&#xff1a;Word文档格式的对话文本围绕高级英语口语交际场景展开&#xff0c;共一个文件&#xff0c;压缩包整体约三十三KB。内容来自高级班课程&#xff0c;节选多个实用对话&#xff0c;覆盖邀约、建议、人物评价、社交聚会、文化差异等情境。适合中高级英语学习者用于…

作者头像 李华
网站建设 2026/9/6 21:54:24

IOPaint Windows 安装 10 分钟搞定:AI 修图工具避坑实操

IOPaint Windows 安装 10 分钟搞定&#xff1a;AI 修图工具避坑实操 【免费下载链接】IOPaint Image inpainting tool powered by SOTA AI Model. Remove any unwanted object, defect, people from your pictures or erase and replace(powered by stable diffusion) any thin…

作者头像 李华
网站建设 2026/9/6 21:54:15

高级班文本拆解:从知识管理到内容创作的方法论落地

简介&#xff1a;这份《I_show高级班文本》是一份英语口语高级课程对话练习素材&#xff0c;面向具备一定英语基础、希望提升真实交际能力的学习者。文档以多个场景化对话片段为主体&#xff0c;涵盖日常邀约、人物描述、文化差异、共同兴趣、情感变化、观点争论、社交活动与自…

作者头像 李华