CVE-2026-48095: The 7-Zip NTFS Heap Overflow That Can Ruin Your Day – And Your Network

The CyberSec Guru

CVE-2026-48095: 7-Zip Heap Buffer Overflow Vulnerability

If you like this post, then please share it:

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Why your support matters: Zero paywalls: Keep the main content 100% free for learners worldwide, Writeup Access: Get complete in-depth writeup with scripts access within 12 hours of machine drop.

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

A critical heap buffer overflow was publicly disclosed in 7-Zip yesterday, and if you haven’t patched yet, you should probably stop reading this and go do that first.

Tracked as CVE-2026-48095 (advisory GHSL-2026-140), the bug was found by Jaroslav Lobačevski of GitHub Security Lab and carries a CVSS 3.1 score of 8.8. It affects 7-Zip 26.00 and all versions before it. The patched release is 26.01. There’s already a working proof-of-concept in the wild.

What makes this particularly unpleasant isn’t just the severity but the attack surface. You don’t need to open a .ntfs file or a disk image. You can be handed a file called invoice.pdf or Q2_report.zip, double-click it in 7-Zip, and trigger the vulnerable code path before you even realize something went wrong. More on that in a moment.

Where the Bug Lives

The vulnerable code is in NtfsHandler.cpp, inside a function called CInStream::GetCuSize(). This function is responsible for computing how much memory to allocate when processing an NTFS compression-unit data stream. It sounds obscure. It isn’t.

The relevant calculation looks like this:

BufferSize = (UInt32)1 << (BlockSizeLog + CompressionUnit)

Under normal conditions this works fine. BlockSizeLog encodes the cluster size as a power of two (so a 4096-byte cluster gives you a log of 12), and CompressionUnit is a value read from the compressed data attribute in the NTFS image. Together they determine how big the decompression buffer should be.

The problem is what happens when those values are crafted to be abnormally large.

The Shift-by-32 Problem: C++ UB Meets x86 Hardware

Here’s where it gets interesting from a low-level perspective.

If an attacker crafts a malicious NTFS disk image so that BlockSizeLog evaluates to 28 or higher, and sets CompressionUnit to 4, the shift exponent hits 32:

Exponent = BlockSizeLog + CompressionUnit = 28 + 4 = 32

In C++, left-shifting a 32-bit integer by 32 or more bits is undefined behavior. The standard doesn’t specify what should happen as compilers are free to do whatever, and different optimization levels can produce different results. But here’s the thing about running this code on actual x86 or x64 hardware: the processor doesn’t crash or throw an exception. It masks the shift count.

For 32-bit shift instructions, x86 applies a bitwise AND with 31 to the shift count before executing:

ShiftCount_effective = 32 & 31 = 0

So the CPU executes (UInt32)1 << 0, which is just 1. The allocation call receives a buffer size of 1 byte.

That’s the entire bug. The application asks for 1 byte. Then immediately tries to write 256 MB into it.

This is one of those cases where the undefined behavior doesn’t produce a crash or a NOP – it produces a value that looks valid enough to pass straight through into the next phase of processing. The compiler has no reason to warn. The allocator has no reason to refuse. And the result is a catastrophically undersized buffer sitting on the heap, waiting for the stream write to begin.

What Happens Next: The Heap Overflow

Once _inBuf is allocated at 1 byte, execution flows into ReadStream_FALSE, which is responsible for pulling compressed data from the stream into the buffer. The function attempts to write up to 256 MB of attacker-controlled content into _inBuf.

The heap layout at this point looks roughly like this:

[ _inBuf (1 byte) ] [ ~304 bytes of heap metadata/gap ] [ CInStream object ]

The CInStream object which is the parent stream object used to manage the ongoing read operation lives just 304 bytes past the start of _inBuf. The stream write operates in 64 KB blocks, so within the very first read iteration, the overflow blows past that 304-byte gap and starts overwriting CInStream‘s memory.

The critical field being overwritten is the vtable pointer.

In C++, every class with virtual methods has a vtable which is a table of function pointers that the runtime uses to dispatch virtual function calls. Each object instance stores a pointer to its class’s vtable. If you overwrite that pointer, the next virtual function call goes wherever you point it.

The attacker controls the content of the NTFS disk image’s cluster data. That content is what’s being written during the overflow. So the attacker controls the bytes that overwrite the vtable pointer. They point it at a memory region they’ve prepared with their own table of function pointers. The moment 7-Zip’s code tries to call a virtual method on CInStream to fetch the next read block. during the second iteration, execution jumps to the attacker’s code.

That’s the vtable hijack. From there, on a properly set up system, you have arbitrary code execution.

Attack Flow
Attack Flow

Memory Requirements and Why 64-bit Systems with Plenty of RAM Are at Higher Risk

There’s a nuance here that’s worth understanding. The exploit also allocates a large auxiliary output buffer – _outBuf, at roughly 8 GB as part of the processing pipeline. On systems with 16 GB or more of RAM, this allocation succeeds and the exploit reaches the overflow code path reliably. On low-memory systems, the 8 GB allocation fails first, and the application crashes before reaching the vulnerable write. You get a denial of service rather than code execution, which is still bad, but not as bad.

This means the exploit’s most dangerous variant works best on exactly the kind of machine you’d least want compromised: a well-specced workstation or server.

On 32-bit builds, the 8 GB allocation will generally fail for a different reason as 32-bit processes can’t address that much virtual memory. But the bug itself still exists in 32-bit builds; reliable RCE on 32-bit targets just requires a different approach to the memory layout problem.

The Extension-Agnostic Attack Surface

This is the detail that should concern everyone, not just people who regularly open disk images.

When 7-Zip opens a file, it first tries to parse it based on the file extension. If that fails, it falls back to a sequential scan through all its archive handlers, checking each handler’s magic byte signature against the file’s raw contents.

The NTFS handler looks for the ASCII string NTFS (that’s NTFS followed by a space) starting at byte offset 3 in the file. That’s it. Five bytes at offset 3.

An attacker can take a malicious NTFS disk image, rename it proposal.pdf, and send it as an email attachment. When the victim opens it with 7-Zip, the initial handler (PDF) fails, 7-Zip walks its handler list, finds the NTFS signature at offset 3, routes the file to NtfsHandler.cpp, and the vulnerable code executes.

No warning dialogs. No unusual prompts. The user experiences something that looks like a failed or slow extraction attempt while the exploit runs in the background.

This also means file-type filtering at the perimeter, blocking .img files or .ntfs files does nothing. The file can wear any extension.

The UBSan Catch: How the Bug Was Confirmed

Lobačevski confirmed the bug using UndefinedBehaviorSanitizer (UBSan) under Clang on Linux x64. UBSan instruments binaries at compile time to catch undefined behavior at runtime and emit a diagnostic before it causes silent corruption.

In this case, UBSan caught the shift operation at line 687 of NtfsHandler.cpp, flagging it as an explicit shift-exponent-overflow UB event. Execution continued as UBSan doesn’t necessarily terminate and the subsequent invalid vtable dereference produced a SIGSEGV.

This is actually a useful validation technique for any team that wants to reproduce the bug in a controlled environment: compile 7-Zip with -fsanitize=undefined and run the PoC generator’s output through it. You’ll see the exact line where the UB fires.

The PoC itself, gen_ntfs_sparse.py, generates a 512 MB sparse NTFS disk image. It’s a Python script that carefully structures the NTFS metadata to produce the anomalous BlockSizeLog and CompressionUnit values that trigger the miscalculation. Sparse file allocation means the image doesn’t actually consume 512 MB on disk – the filesystem presents 512 MB to the parser while the actual allocated data is much smaller, which is part of what makes the crafted image feasible to distribute.

The Hidden Attack Surface: Embedded 7z.dll

One thing that doesn’t get enough attention in vulnerability disclosures like this: 7-Zip isn’t just 7-Zip.

A lot of third-party applications bundle 7z.dll directly inside their own installation directories. Archive managers, backup tools, deployment utilities, software installers – many of them ship their own copy of the library rather than depending on a system-level installation. Those embedded copies don’t get updated when you update 7-Zip through your package manager or download the official installer. They sit in the application’s folder, at whatever version the vendor last bundled, until the vendor ships an update.

Hunting for vulnerable installations means checking not just 7z.exe and 7zG.exe in the expected locations, but scanning the entire filesystem for copies of 7z.dll and checking their version metadata. EDR tools with software inventory capabilities can help here. On Windows, something like:

Get-ChildItem -Path C:\ -Filter 7z.dll -Recurse -ErrorAction SilentlyContinue |
ForEach-Object { [System.Diagnostics.FileVersionInfo]::GetVersionInfo($_.FullName) } |
Select-Object FileName, FileVersion | Where-Object { $_.FileVersion -lt "26.1" }

That won’t catch everything because version strings aren’t always populated accurately but it’s a reasonable starting point.

Network-Level Detection

Since the vulnerability is extension-agnostic, blocking files by extension doesn’t help. What you can do at the perimeter is inspect file content for the NTFS magic byte signature.

The signature is NTFS (hex: 4E 54 46 53 20) at byte offset 3 in the file. A file with that signature masquerading as a PDF, ZIP, DOCX, or any other common format is anomalous and worth quarantining for review.

Most enterprise Secure Email Gateways and web proxies support custom YARA rules or file inspection policies. A YARA rule targeting this pattern would look something like:

rule NTFS_Magic_Offset3 {
strings:
$ntfs_sig = { 4E 54 46 53 20 }
condition:
$ntfs_sig at 3
}

Apply this to inbound email attachments and web proxy traffic, especially for files arriving with document-type extensions. It won’t catch everything. An attacker could theoretically structure the image to evade this while still triggering the bug but it raises the cost of a phishing campaign significantly.

Severity Breakdown

FieldDetail
CVECVE-2026-48095
AdvisoryGHSL-2026-140
CVSS 3.18.8 (High)
VectorAV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
WeaknessCWE-787 (Out-of-bounds Write), CWE-190 (Integer Overflow)
Affected7-Zip 26.00 and earlier
Fixed7-Zip 26.01
ResearcherJaroslav Lobačevski, GitHub Security Lab
PoCPublic (gen_ntfs_sparse.py)

What To Do Right Now

Update 7-Zip to 26.01. That’s the primary fix. The patch revises the GetCuSize() calculation to validate the shift exponent before executing it, so the undefined behavior path can’t be reached regardless of what the NTFS metadata says.

If you can’t patch immediately – maybe you’re managing endpoints in a locked-down environment with a change control process, consider temporarily removing 7-Zip’s association with file types so users can’t double-click files directly into 7-Zip. That’s not a real mitigation but it slightly raises the bar.

Hunt for embedded copies of 7z.dll across your environment. Version 26.01 of the library needs to reach every application that bundles it, not just standalone 7-Zip installations.

Add NTFS magic byte inspection at your email gateway if your tooling supports it.

Watch your EDR telemetry for unusual child processes spawned by 7zG.exe or 7z.exe. Post-exploitation activity after a vtable hijack will typically involve shellcode or a staged payload running in the 7-Zip process context, which should produce detectable behavior if your endpoint tooling is configured to monitor for it.

Lobačevski’s full advisory is available through GitHub Security Lab. The fix is in 26.01. Go patch.

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Your contribution powers free tutorials, hands-on labs, and security resources.

Why your support matters:
  • Writeup Access: Get complete writeup access within 12 hours
  • Zero paywalls: Keep the main content 100% free for learners worldwide

Perks for one-time supporters:
☕️ $5: Shoutout in Buy Me a Coffee
🛡️ $8: Fast-track Access to Live Webinars
💻 $10: Vote on future tutorial topics + exclusive AMA access

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

If you like this post, then please share it:

Discover more from The CyberSec Guru

Subscribe to get the latest posts sent to your email!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from The CyberSec Guru

Subscribe now to keep reading and get access to the full archive.

Continue reading