Skip to content

A VS chunk too large for a pool header is big pool too - #181

Merged
glslang merged 1 commit into
mainfrom
fix/vs-chunks-too-large-for-a-pool-header
Sep 24, 2026
Merged

glslang merged 1 commit into
mainfrom
fix/vs-chunks-too-large-for-a-pool-header

Conversation

@glslang

@glslang glslang commented Sep 24, 2026

Copy link
Copy Markdown
Owner

#180 asked the page range descriptor which
allocations carry no _POOL_HEADER, and that is only where most of them are. nt also puts them
inside VS subsegments, where the descriptor says 0x0f and the chunk chain runs straight through
them. Measured on a live 26100.33438 kernel:

address !pool the walk (before)
0xffffac09da29f000 large page allocation, MiRr, 0xe1c0 VS, ...., 57,792 bytes

0xe1c0 is 57,792 — the walk had the block, at the right address and the right length, and had
lost only its name.

The size answers it, structurally

_POOL_HEADER.BlockSize is eight bits, in sixteen-byte units:

nt!_POOL_HEADER
   +0x002 BlockSize : Pos 0, 8 Bits

So 0xff * 16 — 4080 bytes of chunk, its own header included — is the most it can describe: 4080
of payload plus the header is exactly a page, and one byte more has nowhere to record its own
length. That is why such an allocation is in nt!PoolBigPageTable, rather than a fact measured
about one kernel — and the table says it back: every one of the 7,639 live entries on that guest
recorded NumberOfBytes of 0x1000 or more, and none fewer.

Which makes the question answerable from the chunk's size rather than from which allocator produced
it, and that is the point: the two are indistinguishable by their page range descriptors.

Matched by containment and length

A chunk past the limit is matched against the entries discovery resolved inside its region — the
table's Va is where the allocation begins and NumberOfBytes is how long it is — rather than by
arithmetic on the chunk header. Taking both as given costs nothing and assumes nothing about what
sits between the chunk header and the data
, which on that guest is 0x10 that is measured and not
yet explained. Fitting the tag to that would have been a guess; three facts now have to agree or
nothing is claimed.

Measured

After, on the same guest:

!pool the walk
0xffffac09da29f000 MiRr, 0xe1c0 MiRr, 57,792 bytes, header at the allocation

Census, against the run two hours earlier (the pool itself grew ~5%, 739,888 → 780,724 chunks
walked, so read the byte totals rather than the counts): the .... bulk 51,216,352 →
42,785,104
bytes, MiRr arriving at 386 allocations / 1.8 MB and MmRe going 3.8 → 7.1 MB.
Diagnostics fell 224 → 178.

Tests

Two new unit tests, each mutation-verified by breaking the rule it pins — the limit itself, the
containment match, and the length check. The third mutation passed against the first draft,
whose fixture entry fit inside its chunk either way, so it has a case of its own now.

What is left, and what this run ruled out

The residue is one thing and no longer mixed in with the tags: 1,047 chunks under the bogus tag
0x838bffff — the top half of a kernel pointer — all of them under the limit, so they keep a
header the walk places 0x10 early. Confirmed from the bytes rather than from !pool's heuristic:
a genuine _POOL_HEADER sits at 0xffff8b836d46f000 with BlockSize 0x21 and the tag MmLd,
matching !pool's 0x210, while the walk read a pointer 0x10 before it.

The chunk area's start is ruled out as the cause, which was the leading hypothesis:
sizeof(_HEAP_VS_SUBSEGMENT) is 0x28, the first chunk header is at +0x30 where this crate
computes it, and a real MiSe _POOL_HEADER sits at +0x40. So the chain begins correctly and
drifts later. windbg-mcp FOLLOWUPS.md items 99 and 100.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MUhLUt9rB6zd42Y25btB3h

The previous fix asked the page range descriptor which allocations carry no
`_POOL_HEADER`, and that is only where most of them are. `nt` also puts them
inside VS subsegments, where the descriptor says `0x0f` and the chunk chain runs
straight through them -- so `0xffffac09da29f000` on a live 26100.33438 kernel is
an `MiRr` allocation of `0xe1c0` bytes to `!pool` and was 57,792 untagged bytes
to the walk. The same length; only the name lost.

The size answers it, and structurally rather than by measurement.
`_POOL_HEADER.BlockSize` is **eight bits** of sixteen-byte units
(`dt nt!_POOL_HEADER`, x64 26100.33438), so `0xff * 16` -- 4080 bytes of chunk,
its own header included -- is the most it can describe: 4080 of payload plus the
header is exactly a page, and one byte more has nowhere to record its own
length. That is *why* such an allocation is in `nt!PoolBigPageTable`, and it is
what the table says back: every one of the 7,639 live entries on that guest
recorded `NumberOfBytes` of `0x1000` or more, and none fewer.

A chunk past the limit is matched against the entries discovery resolved inside
its region, **by containment and length** rather than by arithmetic on the chunk
header. The table's `Va` is where the allocation begins and `NumberOfBytes` is
how long it is; taking both as given costs nothing and assumes nothing about
what sits between the chunk header and the data -- which on that guest is 0x10
that is measured and not yet explained, and which fitting the tag to would have
been a guess.

Three rules, each mutation-verified by breaking it: the limit itself, the
containment match, and the length check -- the last of which the first draft did
not pin at all, its fixture's entry fitting either way.

windbg-mcp FOLLOWUPS.md item 99.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUhLUt9rB6zd42Y25btB3h
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Sep 24, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 91cd7060-8149-46fa-9a3e-23e484dd5249


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@glslang
glslang merged commit 9bd539d into main Sep 24, 2026
7 checks passed
@glslang
glslang deleted the fix/vs-chunks-too-large-for-a-pool-header branch September 24, 2026 10:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant