DF-0641 / overflow_harness.c
/* * DF-0641 arithmetic harness (primitive characterization). * * This userspace program reproduces the EXACT size math of the buggy * cleartext-password path in smb_smb_ssnsetup() (sys/netproto/smb/smb_smb.c) * WITHOUT needing the network/SMB stack: * * pbuf = vc_pass; // 128-byte multi-byte UTF-8 pw * iconv_convstr(vc_toserver, pbuf, pbuf); // UTF-8 -> single-byte: SHORTENS * plen = strlen(pbuf) + 1; // length of SHORTENED pbuf (:266) * uniplen = plen * 2; // <-- BUG: sized from SHORTENED (:268) * ntencpass = kmalloc(uniplen, ...); // undersized (:269) * smb_strtouni(ntencpass, smb_vc_getpass(vcp)); // writes from ORIGINAL (:270) * * smb_strtouni() (smb_subr.c:185) has NO length param: it writes * (strlen(src)+1)*2 bytes. It is fed smb_vc_getpass(vcp) -- the ORIGINAL, * un-shortened password -- so it writes (strlen(vc_pass)+1)*2 bytes into a * buffer allocated for the SHORTENED length. Overflow = difference * 2. * * Build: cc -O2 -o overflow_harness overflow_harness.c * Run: ./overflow_harness */ #include <stdio.h> #include <string.h> #include <stdlib.h> /* Faithful copy of smb_strtouni() write semantics (smb_subr.c:185-191): * writes 2 bytes per source byte until NUL, then a final 0x0000. */ static unsigned long smb_strtouni_writes(const char *src) { unsigned long bytes = 0; while (*src) { bytes += 2; src++; } bytes += 2; /* terminating 0x0000 */ return bytes; } /* Simulate iconv UTF-8 -> ISO8859-1/CP1252 shortening: every valid 2-byte * UTF-8 sequence (0xC2/0xC3 + 0x80..0xBF, i.e. U+0080..U+00FF) collapses to * a single byte. ASCII (1 byte) stays 1 byte. */ static unsigned long shorten(const unsigned char *pw, unsigned long n) { unsigned long out = 0, i = 0; while (i < n) { if (i + 1 < n && (pw[i] == 0xC2 || pw[i] == 0xC3) && pw[i+1] >= 0x80 && pw[i+1] <= 0xBF) { out += 1; i += 2; /* 2-byte UTF-8 -> 1 byte */ } else { out += 1; i += 1; /* 1 byte stays 1 byte */ } } return out; /* strlen of the shortened buffer (no NUL) */ } int main(void) { unsigned char vc_pass[129]; unsigned long i, pw_len, short_len, plen, uniplen, strtouni_bytes, overflow; char *pbuf_short, *ntencpass; /* Build the same 128-byte UTF-8 password as trigger.c (64 x 2-byte). */ for (i = 0; i < 64; i++) { vc_pass[i*2] = 0xC3; vc_pass[i*2+1] = 0x80 + (i % 0x40); } vc_pass[128] = '\0'; pw_len = strlen((char *)vc_pass); /* 128 */ /* iconv_convstr(toserver) shortens pbuf in place. */ short_len = shorten(vc_pass, pw_len); /* 64 */ /* Buggy ssnsetup math: */ plen = short_len + 1; /* strlen(pbuf)+1 = 65 */ uniplen = plen * 2; /* 130 -- UNDERSIZED */ pbuf_short = malloc(plen); (void)pbuf_short; ntencpass = malloc(uniplen); /* smb_strtouni writes from the ORIGINAL vc_pass: */ strtouni_bytes = smb_strtouni_writes((char *)vc_pass); /* (128+1)*2 = 258 */ overflow = strtouni_bytes - uniplen; /* 258 - 130 = 128 */ printf("vc_pass (original password) : %lu bytes\n", pw_len); printf("pbuf after UTF-8->ISO8859-1 iconv : %lu bytes (SHORTENED)\n", short_len); printf("plen = strlen(pbuf)+1 : %lu\n", plen); printf("uniplen = plen*2 (kmalloc size) : %lu\n", uniplen); printf("smb_strtouni writes (from vc_pass) : %lu bytes\n", strtouni_bytes); printf("==> HEAP OVERFLOW : %lu bytes\n", overflow); printf("overflow content: attacker-controlled (Unicode expansion of pw)\n"); if (overflow > 0) printf("RESULT: ntencpass (slab bucket for %lu) overflows by %lu bytes " "=> kernel heap corruption (C:H/I:H/A:H)\n", uniplen, overflow); else printf("RESULT: no overflow\n"); free(pbuf_short); free(ntencpass); return (overflow > 0) ? 0 : 1; } |