Addendum: harness_qdecode.c

Full source for the Qdecode AFL++ harness referenced in Part 3: Writing the Fuzzing Harness.

/*
 * AFL++ harness: Sophos mailscanner Qdecode (quoted-printable decoder).
 *
 * Qdecode processes =XX hex escape sequences in MIME quoted-printable text.
 * It malloc's an output buffer of (len+1) bytes and decodes in-place.
 *
 * Known bug: inputs with many consecutive '=' characters cause an integer
 * underflow in the final rep movsb copy length, resulting in a massive
 * heap buffer overflow.
 *
 * Build:  clang -m32 -g -O2 -o harness_qdecode harness_qdecode.c -ldl
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <unistd.h>

typedef char *(*qdecode_fn)(const char *, int);

static qdecode_fn decode;

static void load_lib(void) {
    if (!dlopen("./stubs.so", RTLD_LAZY | RTLD_GLOBAL)) {
        fprintf(stderr, "dlopen stubs: %s\n", dlerror()); _exit(1);
    }
    void *h = dlopen("./mailscanner.so", RTLD_LAZY);
    if (!h) { fprintf(stderr, "dlopen: %s\n", dlerror()); _exit(1); }

    decode = (qdecode_fn)dlsym(h, "Qdecode");
    if (!decode) { fprintf(stderr, "dlsym: %s\n", dlerror()); _exit(1); }

    int *log_level = (int *)dlsym(h, "log_level");
    if (log_level) *log_level = 0;
}

int main(int argc, char **argv) {
    if (argc < 2) return 1;
    static int init;
    if (!init) { load_lib(); init = 1; }

    FILE *f = fopen(argv[1], "rb");
    if (!f) return 1;
    fseek(f, 0, SEEK_END);
    long len = ftell(f);
    if (len < 0 || len > 64 * 1024) { fclose(f); return 1; }
    fseek(f, 0, SEEK_SET);
    char *buf = malloc(len + 1);
    if (!buf) { fclose(f); return 1; }
    size_t n = fread(buf, 1, len, f);
    buf[n] = '\0';
    fclose(f);

    char *decoded = decode(buf, (int)n);
    if (decoded) free(decoded);

    free(buf);
    return 0;
}