FuzzOS
发布日期: 2020年12月6日
概要
我们将着手开发一个专门为模糊测试设计的操作系统!这将成为十二月份大部分时间的一个系列直播,内容涵盖创建一个以模糊测试为核心关注点的新操作系统。这意味着内存管理器、确定性和可扩展性将是这个操作系统中最重要的部分,我们将投入大量精力使它们变得超快!
何时开始
直播将于12月10日星期四(UTC时间)开始,大概在18:00左右,但直播时间会在相对随机的日期、相对随机的时间进行。我无法承诺固定的时间! 直播可能会每周进行4-5天(可能是周一到周五),每次可能持续8-12小时。我们到时候看情况,谁知道呢,这取决于我们玩得有多开心!
观看地点
你可以在我Twitch频道观看直播,如果你不幸错过了直播,也可以在我YouTube频道找到录像!当然,别忘了点赞、评论和订阅。
具体内容
那么……说实话,我也不完全确定会发生什么。但是,我可以预测一些我们将要做的事情。首先,重要的是要注意,这些直播不是培训材料。没有准备好的脚本、材料或流程。如果我们最终构建了完全不同的东西,那也没关系,我们会顺其自然。没有必须完成这个项目的要求,也没有承诺项目会以特定方式完成。所以……就这些。
我们将致力于制作一个操作系统,专门针对x86-64架构(开始时是Intel风味的处理器,但AMD在非管理程序模式下应该也能工作)。这个操作系统将为模糊测试而设计,这意味着我们将专注于使虚拟内存管理极其快速。这是大多数高性能模糊测试的支柱,我们需要能够映射、取消映射和恢复页面,因为模糊测试用例会修改它们。
为了让你保持期待,我将首先从我们必须做的无聊事情开始。
操作系统
我们必须制作一个能够启动的操作系统。我们将制作一个UEFI内核,并且可能会尝试在ARM64上运行它,因为我们的代码大部分将是平台无关的。但是,谁知道呢。这将是一个相当通用的内核,我主要会在裸机上开发它,但当然,我们会确保它能在KVM/Xen/Hyper-V上运行,以便在云环境中使用。
ACPI
我们需要编写ACPI表解析器,以便找到系统中内存和CPU的NUMA位置。这对于获得一个能随核心数量扩展的高性能内存管理器至关重要。
多进程
当然,内核将支持多核心,否则对计算来说就没什么用了。
10Gb网络 + TCP协议栈
由于我从不使用磁盘,我将遵循我的标准模式,只使用网络作为通用目的。为此,我们需要10Gb网络驱动程序和TCP协议栈,以便与网络的其余部分通信。这里没什么太疯狂的,我们可能会从“巧克力牛奶”(Chocolate Milk,推测是一个项目或库)借用一些代码。
有趣的内容
好了,那些东西很无聊,让我们来谈谈有趣的部分!
奇特的内存模型
由于我们将对内存本身进行“快照”,我们需要确保指针之类的东西不会成为问题。对此最快、最简单、最好的解决方案,就是确保内存总是加载到相同的地址。这对单个核心来说没问题,但对多个核心来说很困难,因为它们需要在相同位置映射相同数据的副本。
解决方案是什么?当然,我们将让系统上的每个核心运行自己的地址空间。这意味着核心之间没有共享内存(除了一些非常、非常小的例外)。这不仅带来了极高的内存访问性能(因为缓存只处于独占或共享状态),而且意味着共享(可变)内存将不存在!这意味着我们将通过消息传递来完成所有核心同步,这在最佳情况下比共享内存模型的延迟更高,但优势是扩展性更好。只要我们的消息可以序列化到TCP流中,这意味着我们可以在网络上扩展而不费吹灰之力。
这有一些很棒的特性,因为我们不再需要任何锁来添加和删除页表条目,也不需要执行任何TLB刷新操作,这些操作可能会花费数万个时钟周期。
我在Sushi Roll中使用了这个模型,我非常怀念它。它具有极好的性能特性,并且迫使你在核心之间共享信息时进行更多思考。
可扩展性
就像我写的大多数东西一样,线性扩展是必需的,并且跨网络扩展是隐含的,因为任何实际的模糊测试应用都需要它。
快速且差异化的内存快照
到目前为止,这些东西都不是超级有趣。很长时间以来,我已经有许多操作系统为了模糊测试而把这些事情做得很好。然而,我从未将这些内存管理技术变成一个真正的数据结构,而是根据需要手动使用它们。我计划将这个操作系统的核心,结合Rust过程宏和虚拟内存管理技巧,构建成一个树形结构的、可检查点的数据结构。
这将允许在不同状态的结构之间快速切换,因为它们已被快照。这将通过利用页表中的脏位,并创建一个在内存池中分配的分配器来实现,该内存池将在快照时保存和恢复。这个内存将在内部被视为不透明的二进制大对象(blob),因此它可以保存你想要的任何信息——设备状态、客户机内存状态、寄存器状态,与模糊测试完全无关的东西也没关系。为了处理嵌套结构(更具体地说,是要被跟踪的结构中的指针),我们将使用一个Rust过程宏来禁止在跟踪结构中出现未跟踪的指针。
实际上,我们将大量利用硬件的内存管理单元(MMU)来差异快照、传送和恢复内存块。对于模糊测试来说,这是保存客户机内存状态和寄存器状态的必要手段。通过不透明地处理这个问题,我们可以专注于把MMU方面做得非常好,而不再担心在重置时需要恢复所有这些变量的特殊处理。
Linux模拟器
好了,上面说的所有内容都算是为开发高性能模糊器铺路。就我而言,我主要是想用它来重写向量化模拟,但为了让它对其他人更有趣,我们将实现一个能够运行QEMU的Linux模拟器。
这意味着我们将能够(可能只是静态地)编译QEMU。然后,我们可以获取这个二进制文件,将其加载到我们的操作系统中,并在我们的操作系统中运行QEMU。这意味着我们可以控制QEMU发起的系统调用的响应。如果我们确定性地做这件事(我们会的),那么QEMU将是确定性的。这也就意味着,QEMU内部的客户机也将是确定性的。明白了吗?这是我过去使用过的技术,效果非常好。在处理系统调用方面,我们绝对会超越Linux,扩展性更好,并且在内存管理方面将远超Linux。
KVM模拟器 + 管理程序
所以,我不知道这会有多难,但通过大约5分钟浏览互联网,似乎我可以在我的操作系统中轻松编写一个模拟KVM ioctl的管理程序。这意味着QEMU会以为KVM存在,并使用它!
这将使我们能够完全控制QEMU的确定性、系统调用、性能和重置速度……而无需实际修改QEMU的代码。
以上就是计划
这就是计划。一个操作系统 + 快速MMU代码 + 管理程序 + Linux模拟器,让我们能够确定性地运行QEMU能运行的任何东西,这实际上就是一切。我们将在单核上实现每秒数百万次虚拟机重置的性能,随核心数量线性扩展,包括跨网络扩展,以实现世界上有史以来最快的通用模糊测试:D
常见问题
一些人在网上问了问题,我将把它们贴在这里:
Hackernews 问题1
问: 嗯。所以我最初的反应是,“你到底为什么需要整个操作系统来做这个”,但内存快照和改进的虚拟内存性能可能确实是一个很好的理由。Linux确实有CRIU,也许可以用于此目的,但我可以理解一个理性的人更愿意从头开始做。另一方面,如果你需要qemu来运行应用程序(我对此真的很不清楚;我无法判断计划是在这个操作系统上原生运行东西,还是只是提供足够的系统来运行qemu,然后在qemu上的linux上运行应用程序),那么我很惊讶,直接让qemu做你想做的事并不更容易(同样,我很确定qemu已经有自己的内存快照功能可以构建)。
当然,写操作系统本身也是一种回报:)
答: 哦,没想到这个会上HN,因为它本意更像是一个公告而非详细描述。
但是是的,过去我已经为模糊测试做过大约7、8个操作系统,这是一个巨大的性能(和代码整洁度)提升。这个操作系统将像我2-3年前为我的向量化模拟工作写的那个操作系统一样。
回答你的QEMU问题,目标是用MUSL(只是为了让它静态链接,这样我就不需要动态加载器)有效地构建QEMU,并修改MUSL,将所有系统调用变成call指令。这意味着“系统调用”只是调用另一个区域,这将由我的Rust Linux模拟器处理。我将实现最少的系统调用(以及这些系统调用的枚举变体)来让QEMU工作,仅此而已。目标不是运行Linux应用程序,而是运行一个QEMU+MUSL组合,如果这意味着更低的模拟负担(例如,如果可能的话,去掉QEMU中的线程,这样我们就可以避免fork()),可能会稍作修改。
这样做的主要目的不是性能,而是确定性,但性能是附带的好处。一个正常的系统调用指令涉及切换到内核的上下文切换,根据CPU缓解配置可能涉及cr3交换,返回时也一样。这很容易就是几百个周期。一个处理系统调用的call指令大约在1-4个周期内。
虽然对于系统调用来说这不是什么大问题,但当涉及到KVM管理程序调用时就更明显了。切换到管理程序的成本非常高,而在这种情况下,内核、管理程序和QEMU(例如设备模拟)都将在相同的权限级别运行,并且每次设备交互时不会有奇怪的QEMU -> OS -> KVM -> 其他客户操作系统设备 -> KVM -> OS -> QEMU转换。
但话说回来,这主要是为了确定性。通过确定性地模拟Linux(例如,不通过时间或其他系统调用返回来提供熵),我们可以确保QEMU没有外部熵源,因此,每次都会做同样的事情。即使它使用随机种子的哈希表,种子也将来自系统调用,因此每次都会相同。这种确定性意味着客户机每次都会做同样的事情,精确到指令。中断发生在相同的指令上,上下文切换也是如此。这意味着任何错误,无论多么复杂,每次都会重现。
所有这些系统调用模拟 + 确定性我以前也做过,在我为微软写的一个叫tkofuzz的工具中。那使用了Linux模拟 + Bochs,并且是用用户空间写的。这已被证明非常成功,也是微软大多数研究人员现在使用的工具。话虽如此,Bochs比原生执行慢大约100倍,既然人们已经很好地掌握了快照模糊测试(有一个陡峭的学习曲线),是时候实现一个性能更高的版本了。使用QEMU,我们可以通过JIT获得比Bochs至少2-5倍的改进,同时仍然在“模拟”,但如果我们能让KVM模拟工作并使用管理程序,将能获得更多价值。话虽如此,我确实计划支持一种“模式”,在这种模式下,不接触设备的客户机(或者更具体地说,在设备I/O发生后拍摄的快照)将能够完全脱离QEMU运行。我们真的只是用QEMU进行设备模拟+中断控制,因此,如果你对一个函数的快照进行解析,该函数只在一个线程中解析所有内容,没有进程间通信或设备访问(这种情况很少见,当你从磁盘“读取”时,你很可能只是碰到了操作系统RAM缓存,而不是设备),我们就可以去掉QEMU的所有“臃肿”部分,而是在一个非常非常薄的管理程序中运行。
在模糊测试中,快速映射和取消映射内存的方法至关重要,因为大多数模糊测试用例只持续几百微秒。这意味着几百微秒后,我想将所有内存恢复到“处理用户输入之前”的状态,然后继续。这在每个传统的操作系统中都非常慢,而且真的没有办法绕过它。当然,可以制作驱动程序或使用CRIU,但这些仍然不是这里确切需要的解决方案。我宁愿只制作一个操作系统,它能轻松地在KVM/Hyper-V/Xen中运行,从而可以在虚拟机中获得跨平台支持,而不是为我计划使用它的每个操作系统编写驱动程序。
保持可爱,~gamozo
社交
我最近在我的Twitch上更规律地直播了!我已经开发了用于模糊测试的管理程序、变异器、模拟器,并且在直播中做了很多有趣的模糊测试工作。过来看看吧! 在Twitter上关注我 @gamozolabs,如果你想在有新博客发布时收到通知的话。我经常会在数据到来和我学习的过程中发布数据及图表!