# Root Cause Analysis — CVE-2026-42912

## Executive Summary

| | |
|---|---|
| **CVE** | CVE-2026-42912 |
| **Binary** | tapisrv.dll (Windows Telephony Service) |
| **Component** | Telephony Application Programming Interface (TAPI) RPC server |
| **Vulnerability class** | CWE-362: Concurrent Execution Using Shared Resource with Improper Synchronization |
| **Secondary CWE** | CWE-787 (Out-of-Bounds Write — Race 1), CWE-416 (Use-After-Free — Race 2) |
| **Impact** | Elevation of Privilege |
| **Attack vector** | Local — any user who can open a TAPI line device |
| **Privileges required** | Low (standard user) |
| **User interaction** | None |
| **Pre-patch version** | 10.0.26100.8521 |
| **Post-patch version** | 10.0.26100.8655 |
| **KB** | KB5094126 (June 2026 Patch Tuesday) |
| **Patch Tuesday** | 2026-06 |
| **CVSS 4.0 (estimated)** | 7.8 — `CVSS:4.0/AV:L/AC:H/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N` |
| **CFR flags** | `Feature_500158777` (Race 1), `Feature_784066872` (Race 2) |

CVE-2026-42912 covers **two distinct race conditions** in the Windows Telephony Service (`TapiSrv`) RPC server, both patched in the same KB. The first allows an out-of-bounds array write via an unchecked sub-mask index; the second allows a use-after-free in the conference call participant teardown path. Both are exploitable by a standard local user through the documented TAPI Win32 API.

---

## Attack Surface

The Windows Telephony Service (`TapiSrv`) runs as `svchost.exe -k netsvcs` under the `NT AUTHORITY\NETWORK SERVICE` account. It exposes an RPC interface (`tapsrv` named pipe) that any authenticated user can call through the `tapi32.dll` client library. The service manages telephony line devices, call objects, and conference calls on behalf of TAPI applications.

Key objects relevant to this CVE:

- **tCall** — represents a single telephony call, identified by a 32-bit handle (`hCall`) and tagged with `0x4c4c4143` ("CALL") or `0x4c414349` ("ICAL" for incoming)
- **tLineApp** — represents a TAPI application's line session, tagged `0x5050414c` ("LAPP")
- **Event mask array** — a 31-slot (`0x1f`) integer array per object, controlling which TAPI events the application receives; indexed by a sub-mask index derived from a 64-bit selector
- **Conference participant list** — a linked structure of slot arrays attached to the conference host call, tracking all participant call objects

---

## Race 1 — Sub-mask Index Out-of-Bounds (CWE-787)

### Vulnerable Code Path

```
tapi32!lineSetEventMasksOrSubMasks (client)
  → RPC → tapisrv!SettLineAppEventMasks / SettCallClientEventMasks / ...
    → tapisrv!SetEventMasksOrSubMasks(param_1, param_2, param_3, param_4)
      → tapisrv!GetSubMaskIndex(param_2) → uVar1
      → param_4[uVar1] = param_3;    // NO BOUNDS CHECK
```

Similarly, the read path:

```
tapi32!lineGetEventMasksOrSubMasks (client)
  → RPC → tapisrv!TGetEventMasksOrSubMasks
    → GetSubMaskIndex(param_2) → uVar1
    → *param_3 = param_5[uVar1];      // NO BOUNDS CHECK
```

### Root Cause

`GetSubMaskIndex()` derives an array index from an attacker-controlled 64-bit handle/selector value (`param_2`). The function performs bit manipulation to extract an index, but the result is **not validated** against the array bound before use.

The event mask array has 31 slots (indices 0 through 30, matching the `0x1f`-iteration loop in the full-mask path). Any index > 30 (`0x1e`) results in an out-of-bounds read or write on the telephony service heap.

**Pre-patch `SetEventMasksOrSubMasks`:**

```c
undefined8 SetEventMasksOrSubMasks(int param_1, ulonglong param_2,
                                    int param_3, int *param_4)
{
    if (param_4 == NULL) return 0x80000035;
    if (param_1 == 0) {
        // full-mask path — safe, iterates exactly 0x1f slots
        ...
    } else {
        uVar1 = GetSubMaskIndex(param_2);
        param_4[uVar1] = param_3;   // OOB write if uVar1 > 0x1e
    }
    return 0;
}
```

**Post-patch `SetEventMasksOrSubMasks`:**

```c
    } else {
        uVar1 = GetSubMaskIndex(param_2);
        uVar2 = Feature_500158777__private_IsEnabledDeviceUsageNoInline();
        if ((uVar2 != 0) && (0x1e < uVar1)) {
            return 0x80000032;   // LINEERR_OPERATIONFAILED
        }
        param_4[uVar1] = param_3;
    }
```

The patch adds a bounds check (`uVar1 > 0x1e → error`) gated behind `Feature_500158777`. The same check is inserted into the new `GetEventMasksOrSubMasks` helper, which consolidates the read-path logic from `TGetEventMasksOrSubMasks`.

### `TGetEventMasksOrSubMasks` Refactoring

The pre-patch `TGetEventMasksOrSubMasks` was 1431 instructions, duplicating the event mask read logic across all six object-type branches (client, line app, line client, call client, phone app, phone client), each with its own inline `GetSubMaskIndex()` call and unchecked array read. The patch consolidates all branches into the new `GetEventMasksOrSubMasks` helper (142 instructions), shrinking the function to 938 instructions and ensuring the bounds check runs on every exit path.

### Exploitation Window

The race window exists between the call to `GetSubMaskIndex()` and the array access. On a multi-core system, thread A computes the index while thread B concurrently modifies the handle or selector value that feeds `GetSubMaskIndex()`, causing the index to exceed the array bound. The OOB write lands on adjacent heap allocations in the telephony service process, potentially overwriting function pointers, object metadata, or lock structures.

---

## Race 2 — Conference Participant List Use-After-Free (CWE-416)

### Vulnerable Code Path

```
tapi32!lineDrop (client drops a conference participant)
  → RPC → tapisrv!DestroytCall(param_1, ...)
    → RemoveCallFromLineList(param_1)
    → param_1[0x22] = 0;   // clear conf back-pointer
    → param_1[0x23] = 0;   // UNSYNCHRONISED — no lock on conf object
```

Concurrently:

```
tapi32!lineGetEventMasksOrSubMasks (another thread)
  → tapisrv!TGetEventMasksOrSubMasks
    → traverses conf participant slot array
    → dereferences participant pointer → USE-AFTER-FREE
```

### Root Cause

When `DestroytCall` destroys a call that is a **participant** (not the host) in a conference call, the pre-patch code clears the participant's conference back-pointer fields (`param_1[0x22]` and `param_1[0x23]`) directly after `RemoveCallFromLineList`, without acquiring exclusive access to the conference host call object. A concurrent thread traversing the conference's participant slot array can dereference the participant pointer after the call object has been freed by `FreetCall`, constituting a use-after-free.

**Pre-patch `DestroytCall` (participant teardown):**

```c
RemoveCallFromLineList((longlong)param_1);
pvVar15 = *(LPVOID *)(param_1 + 0x22);   // conf call pointer
if (((longlong)pvVar15 - 1U < 0xfffffffffffffffe) &&
   (*(int **)((longlong)pvVar15 + 0x18) == param_1)) {
    // This branch: participant IS the conf host — safe path
    ...
}
// MISSING: else branch for non-host participant
// param_1[0x22] and param_1[0x23] are NEVER cleared if not host
// -> dangling reference in conf's participant slot array
```

**Post-patch `DestroytCall` (participant teardown):**

```c
RemoveCallFromLineList((longlong)param_1);
p_Var23 = *(LPCRITICAL_SECTION *)(param_1 + 0x22);
if (...) {
    if (p_Var23->LockSemaphore == param_1) {
        // Host path — unchanged
        ...
    } else {
        // NEW: Non-host participant path
        uVar7 = Feature_784066872__private_IsEnabledDeviceUsageNoInline();
        if (uVar7 != 0) {
            // 1. Read conf call pointer from participant
            piVar10 = p_Var23->LockSemaphore;
            
            // 2. Pin conf object via ReferenceObject
            p_Var11 = ReferenceObject(piVar9, uVar7, 0x4c4c4143 /*CALL*/);
            
            if (p_Var11 != NULL) {
                // 3. Acquire exclusive access to conf call
                uVar8 = WaitForExclusivetCallAccess((int *)p_Var11, 0x4c4c4143);
                if ((int)uVar8 != 0) {
                    // 4. Verify participant still belongs to this conf
                    if ((p_Var11[3].OwningThread == p_Var23) &&
                       (p_Var23->LockSemaphore == p_Var11)) {
                        // 5. Clear back-pointers
                        param_1[0x22] = 0;
                        param_1[0x23] = 0;
                        // 6. Scan and compact participant slot array
                        // (removes param_1's entry under lock)
                        ...
                    }
                    // 7. Release lock
                    LeaveCriticalSection(p_Var12);
                }
                // 8. Drop reference
                DereferenceObject(p_Var12, uVar7, 1);
            }
        }
    }
}
```

The patch adds ~300 instructions to the non-host participant path, implementing proper synchronisation: `ReferenceObject` → `WaitForExclusivetCallAccess` → scan/compact participant slot array → `LeaveCriticalSection` → `DereferenceObject`. The back-pointer fields are only cleared under the lock, and the participant entry is physically removed from the slot array, eliminating the dangling reference.

---

## Controlled Feature Rollout

Both fixes are gated behind Windows Invariant Library (WIL) Controlled Feature Rollout flags:

| Flag | Feature ID | Guards |
|---|---|---|
| `Feature_500158777` | Sub-mask bounds check in `SetEventMasksOrSubMasks` and `GetEventMasksOrSubMasks` | Race 1 |
| `Feature_784066872` | Exclusive-access participant removal in `DestroytCall` | Race 2 |

Both use `IsEnabledDeviceUsageNoInline`, which queries the Windows Feature Store and falls back to `wil_details_IsEnabledFallback` on cache miss. The fixes are **inert until Microsoft enables the flags** for each device cohort.

To force-enable for testing:

```
reg add "HKLM\SYSTEM\CurrentControlSet\Control\FeatureManagement\Overrides\8\500158777" /v EnabledState /t REG_DWORD /d 2 /f
reg add "HKLM\SYSTEM\CurrentControlSet\Control\FeatureManagement\Overrides\8\784066872" /v EnabledState /t REG_DWORD /d 2 /f
```

---

## Diff Statistics

| Metric | Value |
|---|---|
| Functions changed (code) | 3 (`SetEventMasksOrSubMasks`, `TGetEventMasksOrSubMasks`, `DestroytCall`) |
| Functions added | 3 (`GetEventMasksOrSubMasks`, 2× feature flag accessors) |
| Total functions | 1,020 |
| Match rate | 99.85% |
| Binary size delta | +16 bytes |
| Instruction delta | +35 |

---

## Detection Rules

### YARA — Binary Feature Flag Detection

```yara
rule CVE_2026_42912_TapiSrv_Patch_Feature500158777
{
    meta:
        description = "Detects tapisrv.dll patched for CVE-2026-42912 Race 1 (Feature_500158777 sub-mask bounds check)"
        author      = "OnlyFm252 / STAR Labs SG"
        date        = "2026-07-22"
        cve         = "CVE-2026-42912"
        reference   = "https://onlyfm252.starlabs.sg"
        hash_pre    = "be01d3bd28d2e12d81690790d3a68fbab6814a97b650a1534a9e73ad5ed5a01e"
        hash_post   = "c00c8f51b6ba0bbd76927ac6eefb399a62e5e20fbbe41bdb45ef5901a8a1f169"

    strings:
        $feat500 = "Feature_500158777" ascii wide
        $func_get = "GetEventMasksOrSubMasks" ascii
        $func_set = "SetEventMasksOrSubMasks" ascii
        $pe       = { 4D 5A }

    condition:
        $pe at 0 and $feat500 and ($func_get or $func_set)
}

rule CVE_2026_42912_TapiSrv_Patch_Feature784066872
{
    meta:
        description = "Detects tapisrv.dll patched for CVE-2026-42912 Race 2 (Feature_784066872 conf participant locking)"
        author      = "OnlyFm252 / STAR Labs SG"
        date        = "2026-07-22"
        cve         = "CVE-2026-42912"
        reference   = "https://onlyfm252.starlabs.sg"
        hash_pre    = "be01d3bd28d2e12d81690790d3a68fbab6814a97b650a1534a9e73ad5ed5a01e"
        hash_post   = "c00c8f51b6ba0bbd76927ac6eefb399a62e5e20fbbe41bdb45ef5901a8a1f169"

    strings:
        $feat784 = "Feature_784066872" ascii wide
        $func_destroy = "DestroytCall" ascii
        $call_tag = { 43 41 4C 4C }  // "CALL" (0x4c4c4143 LE)
        $pe       = { 4D 5A }

    condition:
        $pe at 0 and $feat784 and $func_destroy and $call_tag
}
```

### YARA — Vulnerable Binary Detection

```yara
rule CVE_2026_42912_TapiSrv_Vulnerable
{
    meta:
        description = "Detects pre-patch tapisrv.dll vulnerable to CVE-2026-42912"
        author      = "OnlyFm252 / STAR Labs SG"
        date        = "2026-07-22"
        cve         = "CVE-2026-42912"

    strings:
        $func_set   = "SetEventMasksOrSubMasks" ascii
        $func_tget  = "TGetEventMasksOrSubMasks" ascii
        $func_gsi   = "GetSubMaskIndex" ascii
        $no_feat500 = "Feature_500158777" ascii wide
        $no_feat784 = "Feature_784066872" ascii wide
        $pe         = { 4D 5A }

    condition:
        $pe at 0 and $func_set and $func_tget and $func_gsi
        and not $no_feat500 and not $no_feat784
}
```

### Sigma — TAPI Service Exploitation Indicators

```yaml
title: CVE-2026-42912 — Suspicious TAPI RPC Activity (Race Condition Trigger)
id: c3f8a912-7d4e-4b2a-9f1c-6e8d5a3b7c01
status: experimental
description: |
    Detects rapid TAPI line open/close or conference call setup/teardown
    patterns consistent with CVE-2026-42912 race condition exploitation.
    The vulnerability requires concurrent threads racing SetEventMasksOrSubMasks
    or DestroytCall.
author: OnlyFm252 / STAR Labs SG
date: 2026/07/22
references:
    - https://onlyfm252.starlabs.sg
logsource:
    category: process_creation
    product: windows
detection:
    selection_parent:
        ParentImage|endswith:
            - '\svchost.exe'
    selection_child:
        Image|endswith:
            - '\tapisrv.dll'
        CommandLine|contains:
            - 'TapiSrv'
    selection_crash:
        EventType: 'Error'
        Source: 'Application Error'
        Data|contains:
            - 'tapisrv.dll'
            - 'svchost.exe'
    condition: selection_crash or (selection_parent and selection_child)
    # Note: TAPI exploitation is subtle — the primary indicator is a crash
    # in svchost hosting TapiSrv, or abnormal TAPI line open/close frequency
    # visible in ETW tracing (Microsoft-Windows-TAPI3 provider).
level: medium
tags:
    - attack.privilege_escalation
    - attack.t1068
    - cve.2026.42912
falsepositives:
    - Legitimate telephony applications with high call volume
    - VoIP software creating and tearing down many conference calls

---
title: CVE-2026-42912 — Rapid TAPI Line Handle Manipulation
id: c3f8a912-7d4e-4b2a-9f1c-6e8d5a3b7c02
status: experimental
description: |
    Detects processes making an abnormally high number of TAPI line
    open/negotiation calls in a short window, consistent with the sub-mask
    index OOB race (Race 1).
author: OnlyFm252 / STAR Labs SG
date: 2026/07/22
logsource:
    category: pipe_connected
    product: windows
detection:
    selection:
        PipeName: '\tapsrv'
    filter_legitimate:
        Image|endswith:
            - '\explorer.exe'
            - '\dialer.exe'
            - '\Teams.exe'
            - '\lync.exe'
    condition: selection and not filter_legitimate
    # Combined with: frequency threshold of >50 connections in 10 seconds
    # from the same process (requires SIEM correlation).
level: low
tags:
    - attack.privilege_escalation
    - attack.t1068
    - cve.2026.42912
falsepositives:
    - Legitimate CTI/TAPI middleware (e.g., Cisco JTAPI, Genesys)
```

### Sysmon Configuration

```xml
<Sysmon schemaversion="4.90">
  <EventFiltering>

    <!-- EID 7: Track tapisrv.dll loads into svchost -->
    <ImageLoad onmatch="include">
      <ImageLoaded condition="end with">tapisrv.dll</ImageLoaded>
    </ImageLoad>

    <!-- EID 18: Monitor named pipe connections to tapsrv -->
    <PipeEvent onmatch="include">
      <PipeName condition="is">\tapsrv</PipeName>
    </PipeEvent>

    <!-- EID 1: Catch svchost crash-restarts hosting TapiSrv -->
    <ProcessCreate onmatch="include">
      <CommandLine condition="contains">TapiSrv</CommandLine>
    </ProcessCreate>

    <!-- EID 11: WER crash dumps for TapiSrv host -->
    <FileCreate onmatch="include">
      <TargetFilename condition="contains">svchost</TargetFilename>
      <TargetFilename condition="end with">.dmp</TargetFilename>
    </FileCreate>

    <!-- EID 5: Process termination of TapiSrv host -->
    <ProcessTerminate onmatch="include">
      <Image condition="end with">svchost.exe</Image>
    </ProcessTerminate>

  </EventFiltering>
</Sysmon>
```

---

## Timeline

| Date | Event |
|---|---|
| 2026-06-10 | Microsoft releases KB5094126 (June 2026 Patch Tuesday) |
| 2026-06-10 | CVE-2026-42912 disclosed — Elevation of Privilege in Windows Telephony Service |
| 2026-07-22 | This analysis completed (OnlyFm252 / STAR Labs SG) |

---

## References

- [Microsoft Security Update Guide — CVE-2026-42912](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-42912)
- [KB5094126 release notes](https://support.microsoft.com/help/5094126)
- [TAPI Overview (Win32 API)](https://learn.microsoft.com/en-us/windows/win32/tapi/tapi-overview)
- [lineSetEventMasksOrSubMasks function](https://learn.microsoft.com/en-us/windows/win32/api/tapi/nf-tapi-linegetstatusmessages)
- [Windows Feature Store / Controlled Feature Rollout](https://learn.microsoft.com/en-us/windows/deployment/update/feature-flighting)

---

<sub>Source: ghidriff diff of tapisrv-2026-05.dll (10.0.26100.8521, pre-patch, SHA256 be01d3bd28d2e12d81690790d3a68fbab6814a97b650a1534a9e73ad5ed5a01e) vs tapisrv-2026-06.dll (10.0.26100.8655, post-patch, SHA256 c00c8f51b6ba0bbd76927ac6eefb399a62e5e20fbbe41bdb45ef5901a8a1f169) — [download pre](/data/patch_diffs/binaries/tapisrv-2026-05.dll) / [download post](/data/patch_diffs/binaries/tapisrv-2026-06.dll).</sub>
