/*
 * DF-0965 - twofish_set_key missing key-length validation.
 * FUNCTION-LEVEL HARNESS (not a kernel exploit).
 *
 * In-kernel callers (twofish_cbc_setkey, twofish_xts_setkey in
 * sys/crypto/cryptoapi/cryptoapi.c) validate the key length against
 * {128,192,256}/{256,512} white-lists BEFORE calling twofish_set_key,
 * and cryptoapi_cipher_find() requires cipher->probe() to accept the size.
 * So the bug is NOT reachable through the kernel syscall surface.
 *
 * This harness compiles the real twofish.c source and demonstrates the
 * OOB stack write the function performs when key_len_bits >= 320 (k_len>=5):
 *   - loop at twofish.c:435-440 writes me_key[i]/mo_key[i] (size-4 stack
 *     arrays) past their bounds, corrupting other stack locals including
 *     the `l_key`/`s_key` pointers, then the function SIGBUS/SIGSEGVs
 *     when dereferencing them (l_key[i] = a+b;),
 *   - writes s_key[k_len-i-1] past s_key[4] (into mk_tab) in the ctx,
 *   - reads ((const u_int32_t *)in_key)[i+i(+1)] past the caller's key,
 *   - gen_mk_tab's switch (twofish.c:335) silently falls through for
 *     k_len not in {2,3,4}, leaving mk_tab[] uninitialized garbage.
 *
 * Build:  cc -I. -I/usr/src/sys -Wall -O2 -o trigger trigger.c
 * Run:    ./trigger
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

#include "twofish_src.c"   /* copy of sys/crypto/twofish/twofish.c */

static int try_call(int key_len_bits)
{
    pid_t pid = fork();
    if (pid == 0) {
        /* child: silence stdout, just trigger the bug */
        size_t ctx_sz = sizeof(twofish_ctx);
        unsigned char *blob = calloc(1, ctx_sz + 256);
        if (!blob) _exit(50);
        twofish_ctx *ctx = (twofish_ctx *)blob;
        size_t keybytes = key_len_bits / 8;
        uint8_t *key = calloc(1, keybytes + 256);
        if (!key) _exit(51);
        memset(key, 0x55, keybytes);
        /* The next call corrupts the function's stack via the size-4
         * me_key[4]/mo_key[4] arrays when k_len >= 5. */
        twofish_set_key(ctx, key, key_len_bits);
        /* If we got here without crashing (small k_len), report k_len. */
        FILE *f = fopen("/tmp/df965_klen", "w");
        if (f) { fprintf(f, "%u\n", ctx->k_len); fclose(f); }
        _exit(0);
    }
    int status = 0;
    waitpid(pid, &status, 0);
    return status;
}

int main(void)
{
    printf("sizeof(twofish_ctx) = %zu (l_key[40]=160B, s_key[4]=16B, "
           "mk_tab[4*256]=4096B, k_len)\n", sizeof(twofish_ctx));

    /* 1. Sanity: supported key_len_bits=256 (k_len=4) - should NOT crash */
    int st = try_call(256);
    printf("[control] key_len_bits=256 (k_len=4): status=0x%x ", st);
    if (WIFEXITED(st) && WEXITSTATUS(st) == 0) {
        unsigned k = 0;
        FILE *f = fopen("/tmp/df965_klen", "r");
        if (f) { fscanf(f, "%u", &k); fclose(f); }
        printf("exited 0, k_len=%u (function OK)\n", k);
    } else if (WIFSIGNALED(st)) {
        printf("CRASHED sig=%d (UNEXPECTED for valid keylen)\n",
            WTERMSIG(st));
    } else {
        printf("unknown status\n");
    }
    unlink("/tmp/df965_klen");

    /* 2. Trigger: key_len_bits=640 (k_len=10) - SHOULD crash/OOB */
    st = try_call(640);
    printf("[trigger] key_len_bits=640 (k_len=10): status=0x%x ", st);
    if (WIFSIGNALED(st)) {
        printf("CRASHED sig=%d (%s)\n", WTERMSIG(st),
            WTERMSIG(st) == SIGBUS ? "SIGBUS" :
            WTERMSIG(st) == SIGSEGV ? "SIGSEGV" : "other");
        printf("BUG CONFIRMED at function level: twofish_set_key overflowed\n"
               "  the size-4 stack arrays me_key[4]/mo_key[4] (twofish.c:427)\n"
               "  during the loop at twofish.c:435 with i=0..9, corrupting\n"
               "  adjacent stack locals (including l_key/s_key pointers at\n"
               "  twofish.c:428). The subsequent l_key[i]=a+b write at line\n"
               "  447 dereferenced a corrupted pointer and faulted.\n");
        return 0;
    } else if (WIFEXITED(st)) {
        unsigned k = 0;
        FILE *f = fopen("/tmp/df965_klen", "r");
        if (f) { fscanf(f, "%u", &k); fclose(f); }
        if (k > 4) {
            printf("exited 0, k_len=%u (function accepted oversized key)\n", k);
            printf("BUG CONFIRMED: ctx->k_len=%u > supported max(4). The\n"
                "  function silently took the bad size, ran the OOB loop at\n"
                "  twofish.c:435 (i=0..%u), wrote past me_key[4]/mo_key[4]\n"
                "  (stack) and s_key[4] (struct, into mk_tab), then gen_mk_tab\n"
                "  at twofish.c:335 fell through (k_len not in {2,3,4}) leaving\n"
                "  mk_tab as the OOB-written garbage.\n", k, k-1);
            return 0;
        }
        printf("exited %d, k_len=%u (no bug observed)\n",
            WEXITSTATUS(st), k);
    } else {
        printf("unknown status\n");
    }
    return 1;
}
