LnvMSRIO.sys 3.1.0.36 carries addresses and register numbers from user requests into physical-memory mappings and MSR operations. CVE-2025-8061 starts with excessive access to privileged resources, not a complicated memory-corruption primitive. A signed, loadable driver can still expose an unsafe interface.
The analysis separates historical decompiler views, Windows 11 24H2 runtime records, and offline layout models. Device reachability, IOCTL buffers, copy direction, and processor state need different evidence. No driver was loaded, MSR changed, or kernel payload executed on the current machine.
Advisory scope and sample conditions
| Item | Finding |
|---|---|
| Vulnerability | CVE-2025-8061, CWE-782 |
| Vendor product scope | Dispatcher 3.0 and 3.1 Driver |
| Version boundary | Affected versions below 3.1.0.41 |
| Explicit exclusion | Dispatcher 3.2 Driver |
| Examined sample | LnvMSRIO.sys 3.1.0.36, x64 |
| Historical terminal version | 10.0.26100.4351, Windows 11 24H2 |
The Lenovo advisory and vendor-submitted CVE record describe an access-control issue reachable by an authenticated local user. They explicitly exclude systems with Core Isolation Memory Integrity enabled. Select the update from the remediation table for the affected model rather than matching a filename alone. The vendor credits YiShun Zeng and Luis Casvella with independently reporting the issue.
The historical test disabled HVCI-related protections. That condition belongs next to the privilege-change result. The signature dialog reports a valid signature from Lenovo, but this establishes provenance and integrity, not appropriate constraints on every IOCTL.
Follow the device handle into IOCTL dispatch
flowchart LR accTitle: From initialization to resource operations accDescr: Driver initialization creates a device and symbolic link, registers the device-control dispatch, and routes accepted requests to physical-memory or MSR handlers. A["DriverEntry"] --> B["Initialization"] B --> C["Device and WinMsrDev link"] B --> D["MajorFunction 0x0e"] C --> E["Existing device handle"] E --> D D --> F["IOCTL dispatch"] F --> G["Physical-memory handlers"] F --> H["MSR handlers"]
The initialization view creates \DosDevices\WinMsrDev and assigns the device-control handler to DriverObject->MajorFunction[0x0e]. Opening an existing device and installing a driver are separate prerequisites; installation also depends on loading privileges and system policy.
The four constants decode as follows using the CTL_CODE layout. Access specifies handle access requirements, not the application's entire authorization policy.
| IOCTL | Branch | Function | Access | Method |
|---|---|---|---|---|
| 0x9c406104 | Physical read | 0x841 | 1: FILE_READ_DATA | 0: METHOD_BUFFERED |
| 0x9c40a108 | Physical-write entry | 0x842 | 2: FILE_WRITE_DATA | 0: METHOD_BUFFERED |
| 0x9c402084 | MSR read | 0x821 | 0: FILE_ANY_ACCESS | 0: METHOD_BUFFERED |
| 0x9c402088 | MSR write | 0x822 | 0: FILE_ANY_ACCESS | 0: METHOD_BUFFERED |
All four have DeviceType = 0x9c40. FILE_ANY_ACCESS on the MSR requests still presupposes an existing device handle. An audit therefore needs the actual device security descriptor, requested access, and open result. The MSR-write code is 0x9c402088, not the physical-read constant.
One SystemBuffer serves two roles
All four requests use METHOD_BUFFERED. The I/O manager supplies Irp->AssociatedIrp.SystemBuffer for both input and output and allocates the larger of the two requested lengths. This is consistent with user-mode DeviceIoControl accepting separate input and output pointers.
| Length | Meaning |
|---|---|
| InputBufferLength | Bytes available under the input contract |
| OutputBufferLength | Caller-provided output capacity |
| IoStatus.Information | Valid returned bytes reported by the driver |
A larger allocation does not make every byte valid input or initialized output. See Microsoft's buffer documentation. Some argument types and names in the historical pseudocode are inconsistent. The reconstruction below uses field offsets and data direction, not those prototypes as a compilable ABI.
| Offset | Name | Type | Size |
|---|---|---|---|
| 0x00 | physical_address | uint64_t | 8 |
| 0x08 | operation_type | uint32_t | 4 |
| 0x0c | count | uint32_t | 4 |
The physical-read path requires a 0x10-byte input. Its first eight bytes supply the physical address; the remaining fields control the mapping length and copy branch. MmMapIoSpace returns a kernel virtual mapping. Its address argument is a physical-address value, not a user pointer; supported usage and failure handling still follow the API contract.
Mapping length and copy length differ
The read view computes operation_type * count for the mapping, but the three wrappers do not all copy that product. Their displayed argument order is src, dst, count; internally they call the conventional memcpy(dst, src, length).
| operation_type | Mapping-length expression | Bytes copied by the wrapper |
|---|---|---|
| 1 | 1 × count | count |
| 2 | 2 × count | count << 1 |
| 8 | 8 × count | count << 2 |
The 8 branch therefore does not imply eight copied bytes per item. The displayed product is a 32-bit value, whereas the wrapper zero-extends the count before shifting. Boundary inputs in the offline model separate these lengths further. This is an audit lead requiring instruction-level and runtime confirmation, not a newly validated out-of-bounds finding.
flowchart LR accTitle: Mapping and copying are separate operations accDescr: MmMapIoSpace maps a physical region; a read copies from the mapping to the system buffer. A write copies payload bytes in the opposite direction for the confirmed write branches. P["Physical region"] -->|"MmMapIoSpace"| M["Kernel mapping"] M -->|"Read"| O["SystemBuffer output"] I["SystemBuffer payload +16"] -->|"Write branches"| M
The physical-write entry uses a 16-byte control header followed by the actual payload. A naturally aligned C structure containing Data[1] may include tail padding. Its sizeof is not a general formula for arbitrary payload lengths; calculate the request size explicitly and check addition and multiplication boundaries.
The write view has another significant exception: branch 8 passes the mapping as the source of memcpy_wrapper3 and the input buffer at +16 as its destination, reversing the usual write diagram. This has decompiler evidence but no fresh instruction or runtime confirmation. The diagram's write arrow therefore applies only to matching branches, not a claim that every mode is an established arbitrary write.
Physical-memory access also does not automatically provide arbitrary kernel-virtual-address access. Mappings, page types, and the physical location of the object need further evidence. No physical-address scan was used to fill those gaps.
Encode MSR fields at exact byte offsets
The read branch takes a 32-bit MSR number, executes rdmsr, and combines EDX:EAX into a 64-bit result. The write record places the four-byte number at +0 and the eight-byte value at +4: a packed 12-byte layout.
register0x00–0x03value0x04–0x0B
import struct
register = 0xc0000082
sample_value = 0x1122334455667788
request = struct.pack("<IQ", register, sample_value)
assert len(request) == 12
assert struct.unpack_from("<I", request, 0)[0] == register
assert struct.unpack_from("<Q", request, 4)[0] == sample_valuesample_value is illustrative byte data, not a register value to write. The local Win64 layout check placed the value at +8 in the default structure, making it 16 bytes long. One-byte packing or explicit serialization produced offset +4 and size 12.
Likewise, sizeof(pointer) measures the pointer, not the register-number field. A permissive handler accepting excess bytes does not validate the wrapper. Register allowlists, value constraints, input/output lengths, and exception paths require separate checks. A field name or valid structure size does not authorize a resource.
Bind address results to one kernel build
The historical module-query results are summarized below. The second needs an explanation involving token privileges, rather than a claim that 24H2 removed module-address queries.
| Record | Result or condition |
|---|---|
| Earlier-environment capture | 0xFFFFF8077D800000 |
| 24H2 capture | 0x0 |
| Microsoft's 24H2 rule | Valid ImageBase values require enabled SeDebugPrivilege |
Microsoft documents that the function can still succeed without the privilege enabled while returning an array of NULL addresses. Record the API result, array contents, and effective token together instead of printing only the first address.
[*] KiSystemCall64 at 0xFFFFF80392AB8740
[*] Kernel Base Address at 0xFFFFF80392400000The historical record combines the observed LSTAR value with the entry RVA from the same build to obtain a candidate base:
observed_entry = 0xfffff80392ab8740
entry_rva = 0x6b8740
candidate_base = observed_entry - entry_rva
assert candidate_base == 0xfffff803924000000x6b8740 belongs to that build; it is not a universal constant. The loaded kernel, disk image, entry variant, and symbols must correspond. Subtraction yielding an aligned address is a consistency check, not image validation.
Entry and return state must be paired
flowchart LR accTitle: LSTAR selects an entry, not a complete context accDescr: A syscall on one logical processor selects an entry through LSTAR. Software must still establish the kernel stack and GS context and preserve the user return state. A["SYSCALL on one logical CPU"] --> B["LSTAR target"] B --> C["Entry code"] C --> D["Stack, GS and return-state handling"]
LSTAR is a model-specific register (MSR), numbered 0xc0000082. Changing one logical processor's entry state does not synchronize every CPU automatically. Migration and scheduling affect which state the next system call uses.
SYSCALL does not save RSP, and SYSRET does not restore it; software manages those stack transitions. The obligations represented by the historical entry and return stack diagrams are more informative than a list of build-specific gadget addresses.
| State | Entry side | Return side |
|---|---|---|
| GS base | Establish the current context and swapgs count | Pair transitions with the return mode |
| RSP | Preserve its original relationship and establish a valid stack | Restore the required user stack without guessing alignment changes |
| RIP and flags | Separate the entry target from return state | Satisfy the return instruction's architectural conditions |
| LSTAR and CR4 | Save actual original values and the associated logical CPU | Restore the matching state, not a universal constant |
Check iretq frame consumption for the actual 64-bit execution mode rather than importing assumptions from another mode. Raising thread priority affects scheduling probability; it does not prove that interrupts or CPU migration are excluded. See the Intel architecture manuals for instruction and state definitions.
| Bits | Field | Value |
|---|---|---|
| 20 | SMEP | 1 |
| 21 | SMAP | 1 |
This diagram shows changed bits, not an actual CR4 value. XORing the recorded constants 0x350ef8 and 0x050ef8 yields 0x300000, covering both SMEP bit 20 and SMAP bit 21, not SMEP alone. Another common annotation error concerns OR 0x40000: it sets RFLAGS.AC bit 18. AND 0xff clears several higher flag bits, not just IF.
Results, limits, and remediation checks
Microsoft Windows [version 10.0.26100.4351]
$ whoami
autorité nt\systèmeThe localized identity output supports SYSTEM execution in that run. Paths, the original user name, and deployment details are omitted. It does not establish reproducibility with default protections, nor correct token reference counting, thread migration handling, and every exit path.
The checks actually run locally covered four IOCTL round trips, 24 mapping/copy-length cases, six variable-payload sizes, four flag inputs, MSR packing, and RVA arithmetic. They are data models, not a new driver exploit.
- 1
Identify the product and version
Apply the Lenovo update for the model and record the file actually loaded, not just the installer version.
- 2
Keep system protections enabled
Verify the runtime state of Memory Integrity and related controls. Do not treat results obtained with protections disabled as default behavior.
- 3
Constrain each capability
Check the device ACL, IOCTL access rights, caller identity, physical-resource range, and permitted MSR numbers and values.
- 4
Check rejection and output
Test unapproved callers, invalid lengths, count boundaries, and mapping failures. Record status, valid returned bytes, and actual side effects separately.
The durable fix belongs at the resource boundary. A valid signature and a valid message shape do not authorize arbitrary physical addresses or processor registers. Trace each controlled field to its final side effect, then establish who is allowed to request it.