# Presence check without type validation

Lookup proves an object exists, then it is cast or field-accessed without a type tag check

_Area: Memory corruption and native code. Seen in 4 disclosed reports; 2 confirmed bounties: $500 and $1,000; latest 2023-09-12. Updated 2026-10-23._

## How it works
Code confirms an object exists (non-NULL, found by ID, returned by a lookup) and then treats it as a specific type. The broken assumption is "found means correct type." When a different type reaches the same path, field offsets, sizes, or vtable layout differ, so reads and writes land outside the real object. The invariant that fails: presence is not type. A lookup that only proves "an object is here" says nothing about which object it is. Two sibling failures share the same shape: check-ordering (the type check exists but runs after the dereference) and unchecked handoff (an argument is passed into an API that later assumes a specific type).

## Where it shows up
- Context pointers stored across calls (e.g. FILE_OBJECT->FsContext2) and reused by handlers assuming one object type [E1](https://bugbountylibrary.com/r/p0%3ACVE-2023-36802.md)
- Lookup-by-ID services (FindObject, registries) that fetch an object and cast without a type tag [E1](https://bugbountylibrary.com/r/p0%3ACVE-2023-36802.md)
- Deserialization callbacks that mutate a zval's type mid-operation, so a later macro reinterprets the same slot as a different type [E2](https://bugbountylibrary.com/r/h1%3A198734.md)
- Accessor helpers that dereference a value before checking its type tag [E3](https://bugbountylibrary.com/r/h1%3A190133.md)
- C-extension argument parsing that passes unchecked objects into APIs assuming a real exception type [E4](https://bugbountylibrary.com/r/h1%3A182169.md)

## How to review
For every pointer crossing a call boundary, ask: is the type proven, not just present? The invariant: before any field access, virtual call, or cast, the object's type tag (or size/class) must be validated against what this path expects, and the tag itself must be initialized before comparison. Trace each stored context from writer to reader; if writer and reader disagree on type, that is the bug. Grep for casts immediately after lookup/fetch calls (FindObject, registry lookups) and confirm a tag check precedes each. Grep for FsContext2-style stored contexts and confirm every handler that reads them validates the tag. For deserialization, confirm no user callback can retype a value between the check and its use. For accessor helpers, confirm the type check is ordered before the dereference.

## Variants seen
- Cross-IOCTL context type confusion with size mismatch (0x78 vs 0x1d8) [E1](https://bugbountylibrary.com/r/p0%3ACVE-2023-36802.md)
- Deserialization __wakeup retypes a zval to integer, later read as object handle [E2](https://bugbountylibrary.com/r/h1%3A198734.md)
- Type check ordered after pointer dereference in accessor helper [E3](https://bugbountylibrary.com/r/h1%3A190133.md)
- Unchecked objects passed to PyErr_Restore, later fetched as real exceptions [E4](https://bugbountylibrary.com/r/h1%3A182169.md)

## Impact and severity
Out-of-bounds writes enabled kernel read/write and privilege escalation [E1](https://bugbountylibrary.com/r/p0%3ACVE-2023-36802.md); arbitrary property overwrite chained to RCE [E2](https://bugbountylibrary.com/r/h1%3A198734.md); memory disclosure or segfault [E3](https://bugbountylibrary.com/r/h1%3A190133.md); potential pointer control and code execution [E4](https://bugbountylibrary.com/r/h1%3A182169.md). Severity rises when the confused object is reachable across trust boundaries or grants kernel/sandbox-level access.

## Evidence
- E1: https://bugbountylibrary.com/r/p0%3ACVE-2023-36802.md
- E2: https://bugbountylibrary.com/r/h1%3A198734.md
- E3: https://bugbountylibrary.com/r/h1%3A190133.md
- E4: https://bugbountylibrary.com/r/h1%3A182169.md
