Skip to content

WASAPI Driver for ASIO 0.9.8

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 20 Oct 12:25
· 15 commits to main since this release

0.9.8 (changes from 0.9.7) - "Hardening"; no new user features

Requested by an external review (Windows audio / MIDI engineering): the driver
must withstand untrusted, buggy or malicious data from host applications and
from its dependencies without crashing or compromising the host process, be
covered by fuzz and unit tests that gate every release, and be observable
with TraceLogging. Valid calls and the audio engine behave as in 0.9.7.
Every finding is listed in CODE-REVIEW.txt ("0.9.8 hardening review",
H1-H32); the design is ARCHITECTURE.txt section 58.
Summary of all changes:

  • Exception barrier (H1): every function the host or Windows calls (all
    IASIO and IUnknown methods, the class factory and the four DLL exports,
    every window procedure, hook and enumeration callback, the device and
    volume notification callbacks, every thread procedure, the destructor and
    the panel's message loop) catches any C++ exception and returns that
    boundary's error (ASE_HWMalfunction / ASE_NoMemory, ASIOFalse, E_FAIL /
    E_OUTOFMEMORY, DefWindowProc, ...). Contained exceptions are logged (first
    16) and traced. The real-time path allocates nothing; the audio thread is
    guarded at its thread procedure only (no cost).
  • Host argument validation (H3, H4, H13-H17): new src/asiocheck.h (pure,
    tested offline) with the call-order rules (Created / Initialized /
    Prepared / Running) and every argument rule: null pointers, channel
    indices, isInput values, numChannels <= 0 or above the channel count,
    duplicate channels, buffer sizes outside 32..4096, missing callbacks,
    NaN / infinite / negative / non-standard sample rates, clock sources,
    future() selectors with a null or wrong argument. Host structures are read
    once and copied (the callbacks too), so changing them concurrently cannot
    bypass a check. createBuffers checks everything before it disposes the
    previous buffers. Return codes that changed: start() before init and
    queries before init -> ASE_NotPresent; setSampleRate / controlPanel from
    inside a callback -> ASE_InvalidMode; QueryInterface with a null pointer
    -> E_POINTER. disposeBuffers (in any state) and setClockSource(0) keep
    answering ASE_OK as in 0.9.7. The test tool's private engine-information
    selector now needs a header (a 0.9.7 test tool shows no engine line with
    this driver).
  • Use after free (H2, H19): the device and volume notification objects
    detach under a lock, so the driver can be destroyed while Windows delivers
    a notification; a release on any thread marks the object as dying.
  • Untrusted device data (H5-H7, H10): device mix formats, periods,
    latencies and buffer sizes are range-checked (no write past a frame, no
    undefined float-to-integer conversion, no size overflow on the 32-bit
    build); at most 256 capture packets are read per wake-up.
  • No unbounded waits (H8, H9): start() waits at most 10 s for an exiting
    audio thread (then handles it as stuck, like stop()); the audio thread
    ends with a reset request after 10 s without a render event while the
    device position stays frozen (device stalled without a notification; a
    device whose position still moves, e.g. right after a resume from Modern
    Standby, keeps the stream) and on a failed wait. Every remaining
    wait is listed with its bound in ARCHITECTURE.txt 58.4.
  • config.ini and other parsers (H11, H12, H23-H25): the value parser,
    normalization, UTF-16 decoding, profile lists, host profiles and OutputMap
    moved to pure headers (src/parse.h, src/cfgtypes.h) and are fuzzed;
    fixed an integer overflow (DirectMonitorInput / Output = -2147483648) and
    undefined swscanf overflow in OutputMap; temporary file names include the
    thread id; the profile-API lists are walked within the reported length.
  • Data races removed (H18): getSamplePosition's fallback values, the
    callback counter and the init flag are atomics.
  • TraceLogging (new): the DLL is the ETW provider "WASAPIDriverForASIO"
    {52b9f111-957d-5973-b76d-97bdf8f813df} (name-hash GUID, never a Microsoft
    name or GUID). Events: driver load / unload / create / destroy, init,
    every IASIO call outcome (non-real-time threads), device resolution,
    stream opening (mode, rates, period, buffer, format, conversion), resets
    and reasons, device notifications, errors with HRESULT, contained
    exceptions, a 10 s statistics summary (counters only, from the host
    thread), crash protection, panel and update-check results. Never audio
    data, paths, user or computer names. Disabled tracing costs one atomic
    read per event. Encoder: src/tlg.h (Microsoft's TraceLoggingDynamic.h
    does not compile with mingw-w64, see ARCHITECTURE.txt 58.7).
  • Tracing kit: trace/WasapiDriver.wprp (own EventCollector and buffer budget
    for the driver at level 5; Microsoft-Windows-Audio at level 4 in a
    separate collector; memory and file profiles), "Start trace.bat" (color
    B1) and "Stop trace.bat" (color E1), and TRACING.txt (how to record,
    what is and is not recorded, how to attach it to a GitHub issue). The
    Setup installs them in "Tracing" with Start menu shortcuts; the release
    folder has them in Tools. PRIVACY.txt describes tracing.
  • Tests: tests/run_all.sh runs all 18 offline tests; new fuzz_test (180000
    deterministic iterations over every parser of untrusted data),
    asiocheck_test (validation rules, 200000 random host calls, read-once
    property) and trace_test (SHA-1, name-hash GUID, exact event bytes), all
    with AddressSanitizer + UndefinedBehaviorSanitizer + libstdc++
    assertions. WasapiDriverTest.exe: new T11 "Bad host calls" (invalid
    arguments and wrong order against the real driver, no audio; runs right
    after T1 so every later test shows the driver survived).
  • CI: build.yml runs tests/run_all.sh; release.yml has a "tests" job that
    every other job needs, so nothing is built, signed or published unless all
    tests passed.
  • Dead code removed (H32): unused interface strings S_ABOUT and
    S_STATUS_TITLE (5 languages), WasapiBridge::recommendedBuffer, the
    never-read member monWas, a duplicated null check in getClockSources,
    KnownRate and the swscanf OutputMap loop (replaced); duplicated helpers
    now exist once in parse.h.
  • Portable install.bat / uninstall.bat colors 4A / 8B.
  • Independent review of 0.9.8 (CODE-REVIEW.txt R1-R7, all fixed):
    getErrorMessage copies only the message and its terminator again;
    disposeBuffers and setClockSource(0) are lenient as in 0.9.7; the stall
    detection checks that the device position is frozen; install.bat,
    uninstall.bat, "Start trace.bat" and "Stop trace.bat" check administrator
    rights with fltmc, relaunch themselves elevated only once (marker
    argument) and the trace helpers treat any non-zero wpr result (HRESULTs
    are negative) as a failure; "Stop trace.bat" saves to the signed-in user's
    Desktop (decided before elevation) and prints the full path; the update
    worker's trace event is inside its exception barrier; comment fixes.