Files
SixSixSix d2391a4e83 add iscsi-chap-base64-oob — independent rediscovery PoC
Companion artifact for https://trexnegro.github.io/posts/found-the-same-iscsi-chap-overflow-25-days-early/

Sanitised reproducer for the Linux LIO iSCSI target CHAP BASE64 OOB
write, fixed upstream as commit 85db7391310b. Found independently
2026-05-05 but never shipped; ahossu sent the canonical fix.

Layout: poc/iscsi_chap_overflow.c (initiator that walks the Login
state machine and sends malformed CHAP_R = base64(96 bytes)),
lab/setup_iscsi.sh (LIO target config with CHAP+MD5),
logs/KASAN-vulnerable.log (example trace, build identifiers redacted).

OPSEC: author strings + workspace paths + customer references all
scrubbed before publication.

MIT-licensed (see root LICENSE).
2026-06-02 02:54:51 +00:00
..

iscsi-chap-base64-oob

Reproducer for the Linux LIO iSCSI target's CHAP BASE64-branch heap out-of-bounds write, fixed upstream as commit 85db7391310b ("scsi: target: iscsi: bound BASE64 CHAP_R input to digest size") on the 7.1/scsi-fixes queue and AUTOSEL'd to stable on 2026-05-25.

Companion writeup: trexnegro.github.io/posts/found-the-same-iscsi-chap-overflow-25-days-early/

The canonical write-up of the bug itself is on ahossu's blog: https://ahossu.ro/blog/iscsi-chap-base64-overflow.

This repository entry exists because I found the same bug independently on 2026-05-05, had the same fix shape ready by 2026-05-12, but never sent it upstream. ahossu shipped first; the credit is theirs. The walkthrough in the blog post above explains what I learned about review cadence; this directory is the reproducer I built.

The bug, in one paragraph

drivers/target/iscsi/iscsi_target_auth.c::chap_server_compute_hash() decodes the attacker-controlled CHAP_R BASE64 string into a kzalloc(chap->digest_size) buffer. The decoder writes one byte per four input chars with no destination-size argument, and the post-decode length check fires after the write. A 128-char CHAP_R BASE64 decodes to 96 bytes; with MD5 (digest_size = 16) that's 80 bytes of attacker-chosen out-of-bounds write into kmalloc-16, pre-auth, one TCP connection.

Layout

poc/iscsi_chap_overflow.c     malicious initiator — sends the malformed Login sequence
lab/setup_iscsi.sh            LIO target configuration (CHAP enabled, MD5)
logs/KASAN-vulnerable.log     example KASAN trace (redacted build identifiers)

Building and running the PoC

Dependencies on the host:

  • gcc (any version)
  • A Linux box on the same network as the target, or localhost for a QEMU lab

Build:

cc -O2 -o iscsi_chap_overflow poc/iscsi_chap_overflow.c

Run against a target you own:

./iscsi_chap_overflow <target-ip> <chap-username>

The PoC walks the iSCSI Login state machine through CSG=0 SecurityNegotiation, sends AuthMethod=CHAP, then the malformed CHAP_R = base64(96 bytes). The target's kernel hits the OOB write on the unauth side of the digest comparison.

Setting up a vulnerable lab target

lab/setup_iscsi.sh configures targetcli to expose a single demo-mode LUN with CHAP authentication enabled (MD5, default digest_size = 16). Run it inside a guest:

sudo bash lab/setup_iscsi.sh

Then the malicious initiator from the host:

./iscsi_chap_overflow 192.0.2.42 testuser

Expected on the vulnerable guest: a KASAN slab-out-of-bounds report matching the structure in logs/KASAN-vulnerable.log — write of size 1 into the kmalloc-16 region from chap_base64_decode.

Expected on a patched guest (kernel containing commit 85db7391310b or its stable backport): the iSCSI Login fails cleanly with "Malformed CHAP_R: BASE64 input too long" and no KASAN report.

What the fix looks like

The patch is a single hunk in chap_server_compute_hash():

     case BASE64:
+        if (strlen(chap_r) >
+            DIV_ROUND_UP(chap->digest_size * 4, 3)) {
+            pr_err("Malformed CHAP_R: BASE64 input too long\n");
+            goto out;
+        }
         if (chap_base64_decode(client_digest, chap_r, strlen(chap_r)) !=
             chap->digest_size) {
             pr_err("Malformed CHAP_R: invalid BASE64\n");
             goto out;
         }
         break;

DIV_ROUND_UP(digest_size * 4, 3) is the maximum BASE64 input length that can decode to digest_size bytes. For MD5 (16) that's 22 chars. Anything longer is by definition malformed.

Scope and intent

Authorised security research and education only. The PoC exists to make a public, upstream-fixed kernel bug verifiable for defenders and curious readers. Do not point it at targets you do not own or have written permission to test.

License

MIT — see ../LICENSE at the repository root.