DragonFlyBSD Kernel Audit
DF-0641 / overflow_harness.c
← back to finding ↓ download raw
/*
 * 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;
}