# mskssrv.sys Patch Diff — CVE-2023-36802

| | |
|---|---|
| Binary | mskssrv.sys (Microsoft Kernel Streaming Server) |
| Pre-patch version | 10.0.22621.1848 |
| Post-patch version | 10.0.22621.2283 |
| KB | KB5030219 (Windows 11 22H2) |
| CVE | CVE-2023-36802 — Type Confusion (CWE-843), Elevation of Privilege, CVSS 7.8 |
| Exploited | **Yes — exploited in the wild** |
| Diff tool | ghidriff (Ghidra VersionTrackingDiff engine) |
| Functions changed | 2 with code changes (out of 360 total, 99.4% avg similarity) |
| Functions added | 0 |
| Independent analysis | [Google Project Zero — 0-day RCA](https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2023/CVE-2023-36802.html) by Benoît Sevens; [IBM X-Force](https://securityintelligence.com/x-force/critically-close-to-zero-day-exploiting-microsoft-kernel-streaming-service/) by Valentina Palmiotti |

## Summary

The fix closes a **type confusion** vulnerability in the Microsoft Kernel
Streaming Server that allows an unprivileged local attacker to escalate
privileges to SYSTEM. The driver's `FSRendezvousServer::FindObject()` did
not validate the type of the object retrieved from `FsContext2`, allowing
a `FsContextReg` object (0x78 bytes, type 1) to be used where a
`FsStreamReg` object (0x1D8 bytes, type 2) was expected. Since the stream
object is larger, operations on it read and write **out of bounds** beyond
the smaller context object — into adjacent pool memory controlled by the
attacker via heap spray.

The fix renames `FindObject` → `FindStreamObject` and adds a type field
check: `*(int *)(param_1 + 0x30) == 2`. Only stream objects (type 2) pass
the check; context objects (type 1) are rejected.

**Confidence:** high. The ghidriff output shows exactly the changes
described in both the Project Zero RCA and Valentina Palmiotti's X-Force
write-up — the type check addition and function rename are one-to-one
with the documented fix.

**Credit.** MSRC credits Valentina Palmiotti (IBM X-Force), Quan Jin &
ze0r (DBAPPSecurity WeBin Lab), Guanghui Xia (Hebei HuaCe), Microsoft
Threat Intelligence, and Microsoft Security Response Center.

## Product background

`mskssrv.sys` is a kernel driver that is part of Windows' Multimedia
Framework service (Frame Server). It virtualizes the camera device and
allows sharing between multiple applications. The driver creates a PnP
device reachable via `KsOpenDefaultDevice(KSNAME_Server, ...)` and handles
IOCTLs including `IOCTL_FRAMESERVER_INIT_CONTEXT` (allocates a
`FsContextReg` at `FsContext2`) and `IOCTL_FRAMESERVER_PUBLISH_RX`
(operates on what it expects to be a `FsStreamReg` at `FsContext2`).

The driver stores per-file-handle metadata in
`Irp->CurrentStackLocation->FileObject->FsContext2`, which can point to
either a context registration object or a stream registration object.

## Functions changed

### FSRendezvousServer::FindObject → FindStreamObject (the type check)

| | |
|---|---|
| Change type | code, name, fullname, length, sig, address |
| Similarity | 0.76 (b_ratio) |
| Length | 179 → 99 bytes |
| Fix pattern | Type field check added; function renamed |

Before (pre-patch — vulnerable):

```c
bool FSRendezvousServer::FindObject(FSRendezvousServer *this, FSRegObject *param_1)
{
    if (param_1 != NULL) {
        if (*(int *)(param_1 + 0x30) == 1) {
            // Type 1 = FsContextReg → search context list at this+0x80
            // ... iterate list, return true if found ...
        }
        else {
            // ANY other type → search stream list at this+0x50
            // ... iterate list, return true if found ...
        }
    }
    return false;
}
```

The function searches **both** lists based on the type field. Critically,
the `else` branch accepts **any** non-1 type value — there is no check
that the object is actually type 2 (stream). Because both context and
stream objects are stored in `FsContext2`, an attacker can:

1. Call `IOCTL_FRAMESERVER_INIT_CONTEXT` to allocate a `FsContextReg`
   (0x78 bytes, type 1) and store it in `FsContext2`
2. Call `IOCTL_FRAMESERVER_PUBLISH_RX` on the same handle — this reads
   `FsContext2`, calls `FindObject` (which finds it in the context list
   and returns TRUE), then passes it to `FSStreamReg::PublishRx()` which
   treats the 0x78-byte object as a 0x1D8-byte stream object — reading
   and writing **out of bounds**

After (post-patch — fixed):

```c
bool FSRendezvousServer::FindStreamObject(FSRendezvousServer *this, FSRegObject *param_1)
{
    if ((param_1 != NULL) && (*(int *)(param_1 + 0x30) == 2)) {
        // ONLY type 2 (FsStreamReg) → search stream list at this+0x50
        // ... iterate list, return true if found ...
    }
    return false;
}
```

The function now:
- Is renamed to `FindStreamObject` (clarifying intent)
- Checks that the type field is **exactly 2** (stream) before searching
- Removes the context-list search entirely — context objects (type 1)
  always return FALSE
- The context-list search was moved into `FSRendezvousServer::Close`
  (see below), where it's needed for cleanup

### FSRendezvousServer::Close (inlined context search)

| | |
|---|---|
| Change type | code, length, called |
| Similarity | 0.90 (b_ratio) |

The `Close` function was updated to handle both object types:

- Pre-patch: called `FindObject()` (which searched both lists)
- Post-patch: first checks if type == 1 and searches the context list
  inline; then calls `FindStreamObject()` for stream objects. This allows
  `Close` to properly clean up both object types while `FindStreamObject`
  is restricted to streams only.

## Exploitation

This vulnerability was **exploited in the wild** and independently
discovered by multiple researchers. Two distinct exploitation strategies
are documented:

### X-Force exploit (Valentina Palmiotti)

Uses the **constant write-where** primitive from
`FSFrameMdl::UnmapPages()` (writes value `2` at a controlled offset) in
conjunction with the **I/O Ring** technique:

1. Spray non-paged pool with 0x80-byte `NpFr` buffers and `FsContextReg`
   objects to control memory adjacent to the vulnerable object
2. Use `FSStreamReg::GetStats` (via `IOCTL_FRAMESERVER_CONSUME_TX`) as a
   **pool info leak** to locate the correctly groomed object
3. Trigger the OOB write via `IOCTL_FRAMESERVER_PUBLISH_RX`
4. Leverage the write primitive with I/O Ring for arbitrary kernel R/W
5. Token swap for privilege escalation

### In-the-wild exploit (Project Zero RCA)

Two flows depending on `mskssrv.sys` version:

**CLFS-less flow** (newer versions): Uses `ObfDereferenceObject()` call
present in newer `FSStreamReg::PublishRx` to **decrement `PreviousMode`**
of `KTHREAD` to 0, enabling `NtReadVirtualMemory`/`NtWriteVirtualMemory`
on kernel addresses. Keeps the function locked in a while loop via a
self-referencing linked list entry in usermode to avoid a crash from
`KeSetEvent` on a corrupted `ProcessBilled` field.

**CLFS flow** (older versions): Corrupts a CLFS log file object to
redirect a vtable call through `PoFxProcessorNotification` →
`RtlClearBit`, clearing the `PreviousMode` bit. Bypasses CFG by using
only allowed function addresses as gadgets.

---

<sub>Source: ghidriff diff of mskssrv-2023-08.sys (10.0.22621.1848,
pre-patch) vs mskssrv-2023-09.sys (10.0.22621.2283, post-patch) —
[download pre](/data/patch_diffs/binaries/mskssrv-2023-08.sys) /
[download post](/data/patch_diffs/binaries/mskssrv-2023-09.sys).
References: [Google Project Zero RCA](https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2023/CVE-2023-36802.html),
[IBM X-Force](https://securityintelligence.com/x-force/critically-close-to-zero-day-exploiting-microsoft-kernel-streaming-service/).</sub>
