Commit message: CVE-2017-12893/SMB/CIFS: Add a bounds check in name_len().

After we advance the pointer by the length value in the buffer, make
sure it points to something in the captured data.

This fixes a buffer over-read discovered by Forcepoint's security
researchers Otto Airamo & Antti Levomäki.

Add a test using the capture file supplied by the reporter(s).
CVE: CVE-2017-12893
CWE: CWE-125
Vulnerability description: The SMB/CIFS parser in tcpdump before 4.9.2 has a buffer over-read in smbutil.c:name_len().

Project: tcpdump
File: smbutil.c

int_unix_date(uint32_t dos_date)
{
    struct tm t;

    if (dos_date == 0)
	return(0);

    interpret_dos_date(dos_date, &t);
    t.tm_wday = 1;
    t.tm_yday = 1;
    t.tm_isdst = 0;

    return (mktime(&t));
}

Project: tcpdump
File: smbutil.c

interpret_dos_date(uint32_t date, struct tm *tp)
{
    uint32_t p0, p1, p2, p3;

    p0 = date & 0xFF;
    p1 = ((date & 0xFF00) >> 8) & 0xFF;
    p2 = ((date & 0xFF0000) >> 16) & 0xFF;
    p3 = ((date & 0xFF000000) >> 24) & 0xFF;

    tp->tm_sec = 2 * (p0 & 0x1F);
    tp->tm_min = ((p0 >> 5) & 0xFF) + ((p1 & 0x7) << 3);
    tp->tm_hour = (p1 >> 3) & 0xFF;
    tp->tm_mday = (p2 & 0x1F);
    tp->tm_mon = ((p2 >> 5) & 0xFF) + ((p3 & 0x1) << 3) - 1;
    tp->tm_year = ((p3 >> 1) & 0xFF) + 80;
}

Project: tcpdump
File: smbutil.c

interpret_long_date(const u_char *p)
{
    double d;
    time_t ret;

    /* this gives us seconds since jan 1st 1601 (approx) */
    d = (EXTRACT_LE_32BITS(p + 4) * 256.0 + p[3]) * (1.0e-7 * (1 << 24));

    /* now adjust by 369 years to make the secs since 1970 */
    d -= 369.0 * 365.25 * 24 * 60 * 60;

    /* and a fudge factor as we got it wrong by a few days */
    d += (3 * 24 * 60 * 60 + 6 * 60 * 60 + 2);

    if (d < 0)
	return(0);

    ret = (time_t)d;

    return(ret);
}

Project: tcpdump
File: smbutil.c

make_unix_date(const u_char *date_ptr)
{
    uint32_t dos_date = 0;

    dos_date = EXTRACT_LE_32BITS(date_ptr);

    return int_unix_date(dos_date);
}

Project: tcpdump
File: smbutil.c

make_unix_date2(const u_char *date_ptr)
{
    uint32_t x, x2;

    x = EXTRACT_LE_32BITS(date_ptr);
    x2 = ((x & 0xFFFF) << 16) | ((x & 0xFFFF0000) >> 16);
    return int_unix_date(x2);
}

Project: tcpdump
File: smbutil.c

name_interpret(netdissect_options *ndo,
               const u_char *in, const u_char *maxbuf, char *out)
{
    int ret;
    int len;

    if (in >= maxbuf)
	return(-1);	/* name goes past the end of the buffer */
    ND_TCHECK2(*in, 1);
    len = (*in++) / 2;

    *out=0;

    if (len > 30 || len < 1)
	return(0);

    while (len--) {
	ND_TCHECK2(*in, 2);
	if (in + 1 >= maxbuf)
	    return(-1);	/* name goes past the end of the buffer */
	if (in[0] < 'A' || in[0] > 'P' || in[1] < 'A' || in[1] > 'P') {
	    *out = 0;
	    return(0);
	}
	*out = ((in[0] - 'A') << 4) + (in[1] - 'A');
	in += 2;
	out++;
    }
    *out = 0;
    ret = out[-1];

    return(ret);

trunc:
    return(-1);
}

Project: tcpdump
File: smbutil.c

name_ptr(netdissect_options *ndo,
         const u_char *buf, int ofs, const u_char *maxbuf)
{
    const u_char *p;
    u_char c;

    p = buf + ofs;
    if (p >= maxbuf)
	return(NULL);	/* name goes past the end of the buffer */
    ND_TCHECK2(*p, 1);

    c = *p;

    /* XXX - this should use the same code that the DNS dissector does */
    if ((c & 0xC0) == 0xC0) {
	uint16_t l;

	ND_TCHECK2(*p, 2);
	if ((p + 1) >= maxbuf)
	    return(NULL);	/* name goes past the end of the buffer */
	l = EXTRACT_16BITS(p) & 0x3FFF;
	if (l == 0) {
	    /* We have a pointer that points to itself. */
	    return(NULL);
	}
	p = buf + l;
	if (p >= maxbuf)
	    return(NULL);	/* name goes past the end of the buffer */
	ND_TCHECK2(*p, 1);
    }
    return(p);

trunc:
    return(NULL);	/* name goes past the end of the buffer */
}

Project: tcpdump
File: smbutil.c

name_type_str(int name_type)
{
    const char *f = NULL;

    switch (name_type) {
    case 0:    f = "Workstation"; break;
    case 0x03: f = "Client?"; break;
    case 0x20: f = "Server"; break;
    case 0x1d: f = "Master Browser"; break;
    case 0x1b: f = "Domain Controller"; break;
    case 0x1e: f = "Browser Server"; break;
    default:   f = "Unknown"; break;
    }
    return(f);
}

Project: tcpdump
File: smbutil.c

nt_errstr(uint32_t err)
{
    static char ret[128];
    int i;

    ret[0] = 0;

    for (i = 0; nt_errors[i].name; i++) {
	if (err == nt_errors[i].code)
	    return nt_errors[i].name;
    }

    snprintf(ret, sizeof(ret), "0x%08x", err);
    return ret;
}

Project: tcpdump
File: smbutil.c

print_asc(netdissect_options *ndo,
          const unsigned char *buf, int len)
{
    int i;
    for (i = 0; i < len; i++)
        safeputchar(ndo, buf[i]);
}

Project: tcpdump
File: smbutil.c

smb_errstr(int class, int num)
{
    static char ret[128];
    int i, j;

    ret[0] = 0;

    for (i = 0; err_classes[i].class; i++)
	if (err_classes[i].code == class) {
	    if (err_classes[i].err_msgs) {
		const err_code_struct *err = err_classes[i].err_msgs;
		for (j = 0; err[j].name; j++)
		    if (num == err[j].code) {
			snprintf(ret, sizeof(ret), "%s - %s (%s)",
			    err_classes[i].class, err[j].name, err[j].message);
			return ret;
		    }
	    }

	    snprintf(ret, sizeof(ret), "%s - %d", err_classes[i].class, num);
	    return ret;
	}

    snprintf(ret, sizeof(ret), "ERROR: Unknown error (%d,%d)", class, num);
    return(ret);
}

Project: tcpdump
File: smbutil.c

smb_print_data(netdissect_options *ndo, const unsigned char *buf, int len)
{
    int i = 0;

    if (len <= 0)
	return;
    ND_PRINT((ndo, "[%03X] ", i));
    for (i = 0; i < len; /*nothing*/) {
        ND_TCHECK(buf[i]);
	ND_PRINT((ndo, "%02X ", buf[i] & 0xff));
	i++;
	if (i%8 == 0)
	    ND_PRINT((ndo, " "));
	if (i % 16 == 0) {
	    print_asc(ndo, &buf[i - 16], 8);
	    ND_PRINT((ndo, " "));
	    print_asc(ndo, &buf[i - 8], 8);
	    ND_PRINT((ndo, "\n"));
	    if (i < len)
		ND_PRINT((ndo, "[%03X] ", i));
	}
    }
    if (i % 16) {
	int n;

	n = 16 - (i % 16);
	ND_PRINT((ndo, " "));
	if (n>8)
	    ND_PRINT((ndo, " "));
	while (n--)
	    ND_PRINT((ndo, "   "));

	n = min(8, i % 16);
	print_asc(ndo, &buf[i - (i % 16)], n);
	ND_PRINT((ndo, " "));
	n = (i % 16) - n;
	if (n > 0)
	    print_asc(ndo, &buf[i - n], n);
	ND_PRINT((ndo, "\n"));
    }
    return;

trunc:
    ND_PRINT((ndo, "\n"));
    ND_PRINT((ndo, "WARNING: Short packet. Try increasing the snap length\n"));
}

Project: tcpdump
File: smbutil.c

unistr(netdissect_options *ndo,
       const u_char *s, uint32_t *len, int use_unicode)
{
    static char buf[MAX_UNISTR_SIZE+1];
    size_t l = 0;
    uint32_t strsize;
    const u_char *sp;

    if (use_unicode) {
	/*
	 * Skip padding that puts the string on an even boundary.
	 */
	if (((s - startbuf) % 2) != 0) {
	    ND_TCHECK(s[0]);
	    s++;
	}
    }
    if (*len == 0) {
	/*
	 * Null-terminated string.
	 */
	strsize = 0;
	sp = s;
	if (!use_unicode) {
	    for (;;) {
		ND_TCHECK(sp[0]);
		*len += 1;
		if (sp[0] == 0)
		    break;
		sp++;
	    }
	    strsize = *len - 1;
	} else {
	    for (;;) {
		ND_TCHECK2(sp[0], 2);
		*len += 2;
		if (sp[0] == 0 && sp[1] == 0)
		    break;
		sp += 2;
	    }
	    strsize = *len - 2;
	}
    } else {
	/*
	 * Counted string.
	 */
	strsize = *len;
    }
    if (!use_unicode) {
    	while (strsize != 0) {
          ND_TCHECK(s[0]);
	    if (l >= MAX_UNISTR_SIZE)
		break;
	    if (ND_ISPRINT(s[0]))
		buf[l] = s[0];
	    else {
		if (s[0] == 0)
		    break;
		buf[l] = '.';
	    }
	    l++;
	    s++;
	    strsize--;
	}
    } else {
	while (strsize != 0) {
	    ND_TCHECK2(s[0], 2);
	    if (l >= MAX_UNISTR_SIZE)
		break;
	    if (s[1] == 0 && ND_ISPRINT(s[0])) {
		/* It's a printable ASCII character */
		buf[l] = s[0];
	    } else {
		/* It's a non-ASCII character or a non-printable ASCII character */
		if (s[0] == 0 && s[1] == 0)
		    break;
		buf[l] = '.';
	    }
	    l++;
	    s += 2;
	    if (strsize == 1)
		break;
	    strsize -= 2;
	}
    }
    buf[l] = 0;
    return buf;

trunc:
    return NULL;
}

Project: tcpdump
File: smbutil.c

write_bits(netdissect_options *ndo,
           unsigned int val, const char *fmt)
{
    const char *p = fmt;
    int i = 0;

    while ((p = strchr(fmt, '|'))) {
	size_t l = PTR_DIFF(p, fmt);
	if (l && (val & (1 << i)))
	    ND_PRINT((ndo, "%.*s ", (int)l, fmt));
	fmt = p + 1;
	i++;
    }
}