Commit message: CVE-2015-5330: next_codepoint_handle_ext: don't short-circuit UTF16 low bytes

UTF16 contains zero bytes when it is encoding ASCII (for example), so we
can't assume the absense of the 0x80 bit means a one byte encoding. No
current callers use UTF16.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=11599

Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Pair-programmed-with: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
CVE: CVE-2015-5330
CWE: CWE-200
Vulnerability description: ldb before 1.1.24, as used in the AD LDAP server in Samba 4.x before 4.1.22, 4.2.x before 4.2.7, and 4.3.x before 4.3.3, mishandles string lengths, which allows remote attackers to obtain sensitive information from daemon heap memory by sending crafted packets and then reading (1) an error message or (2) a database value.

Project: samba
File: codepoints.c

const char *charset_name(struct smb_iconv_handle *ic, charset_t ch)
{
	switch (ch) {
	case CH_UTF16: return "UTF-16LE";
	case CH_UNIX: return ic->unix_charset;
	case CH_DOS: return ic->dos_charset;
	case CH_UTF8: return "UTF8";
	case CH_UTF16BE: return "UTF-16BE";
	case CH_UTF16MUNGED: return "UTF16_MUNGED";
	default:
	return "ASCII";
	}
}

Project: samba
File: codepoints.c

static int close_iconv_handle(struct smb_iconv_handle *data)
{
	unsigned c1, c2;
	for (c1=0;c1<NUM_CHARSETS;c1++) {
		for (c2=0;c2<NUM_CHARSETS;c2++) {
			if (data->conv_handles[c1][c2] != NULL) {
				if (data->conv_handles[c1][c2] != (smb_iconv_t)-1) {
					smb_iconv_close(data->conv_handles[c1][c2]);
				}
				data->conv_handles[c1][c2] = NULL;
			}
		}
	}

	return 0;
}

Project: samba
File: codepoints.c

_PUBLIC_ int codepoint_cmpi(codepoint_t c1, codepoint_t c2)
{
	if (c1 == c2 ||
	    toupper_m(c1) == toupper_m(c2)) {
		return 0;
	}
	return c1 - c2;
}

Project: samba
File: codepoints.c

smb_iconv_t get_conv_handle(struct smb_iconv_handle *ic,
			    charset_t from, charset_t to)
{
	const char *n1, *n2;

	if (ic->conv_handles[from][to]) {
		return ic->conv_handles[from][to];
	}

	n1 = charset_name(ic, from);
	n2 = charset_name(ic, to);

	ic->conv_handles[from][to] = smb_iconv_open_ex(ic, n2, n1,
						       ic->use_builtin_handlers);

	if (ic->conv_handles[from][to] == (smb_iconv_t)-1) {
		if ((from == CH_DOS || to == CH_DOS) &&
		    strcasecmp(charset_name(ic, CH_DOS), "ASCII") != 0) {
			DEBUG(0,("dos charset '%s' unavailable - using ASCII\n",
				 charset_name(ic, CH_DOS)));
			ic->dos_charset = "ASCII";

			n1 = charset_name(ic, from);
			n2 = charset_name(ic, to);

			ic->conv_handles[from][to] =
				smb_iconv_open_ex(ic, n2, n1, ic->use_builtin_handlers);
		}
	}

	return ic->conv_handles[from][to];
}

Project: samba
File: codepoints.c

struct smb_iconv_handle *get_iconv_handle(void)
{
	if (global_iconv_handle == NULL)
		global_iconv_handle = smb_iconv_handle_reinit(talloc_autofree_context(),
							      "ASCII", "UTF-8", true, NULL);
	return global_iconv_handle;
}

Project: samba
File: codepoints.c

struct smb_iconv_handle *get_iconv_testing_handle(TALLOC_CTX *mem_ctx, 
						  const char *dos_charset, 
						  const char *unix_charset,
						  bool use_builtin_handlers)
{
	return smb_iconv_handle_reinit(mem_ctx,
				       dos_charset, unix_charset, use_builtin_handlers, NULL);
}

Project: samba
File: codepoints.c

_PUBLIC_ bool islower_m(codepoint_t val)
{
	return (toupper_m(val) != val);
}

Project: samba
File: codepoints.c

_PUBLIC_ bool isupper_m(codepoint_t val)
{
	return (tolower_m(val) != val);
}

Project: samba
File: codepoints.c

_PUBLIC_ codepoint_t next_codepoint_ext(const char *str, size_t len,
					charset_t src_charset, size_t *size)
{
	return next_codepoint_handle_ext(get_iconv_handle(), str, len,
					 src_charset, size);
}

Project: samba
File: codepoints.c

_PUBLIC_ codepoint_t next_codepoint_handle(struct smb_iconv_handle *ic,
					   const char *str, size_t *size)
{
	/*
	 * We assume that no multi-byte character can take more than 5 bytes
	 * thus avoiding walking all the way down a long string. This is OK as
	 * Unicode codepoints only go up to (U+10ffff), which can always be
	 * encoded in 4 bytes or less.
	 */
	return next_codepoint_handle_ext(ic, str, strnlen(str, 5), CH_UNIX,
					 size);
}

Project: samba
File: codepoints.c

_PUBLIC_ ssize_t push_codepoint(char *str, codepoint_t c)
{
	return push_codepoint_handle(get_iconv_handle(), str, c);
}

Project: samba
File: codepoints.c

_PUBLIC_ ssize_t push_codepoint_handle(struct smb_iconv_handle *ic,
				char *str, codepoint_t c)
{
	smb_iconv_t descriptor;
	uint8_t buf[4];
	size_t ilen, olen;
	const char *inbuf;

	if (c < 128) {
		*str = c;
		return 1;
	}

	descriptor = get_conv_handle(ic,
				     CH_UTF16, CH_UNIX);
	if (descriptor == (smb_iconv_t)-1) {
		return -1;
	}

	if (c < 0x10000) {
		ilen = 2;
		olen = 5;
		inbuf = (char *)buf;
		SSVAL(buf, 0, c);
		smb_iconv(descriptor, &inbuf, &ilen, &str, &olen);
		if (ilen != 0) {
			return -1;
		}
		return 5 - olen;
	}

	c -= 0x10000;

	buf[0] = (c>>10) & 0xFF;
	buf[1] = (c>>18) | 0xd8;
	buf[2] = c & 0xFF;
	buf[3] = ((c>>8) & 0x3) | 0xdc;

	ilen = 4;
	olen = 5;
	inbuf = (char *)buf;

	smb_iconv(descriptor, &inbuf, &ilen, &str, &olen);
	if (ilen != 0) {
		return -1;
	}
	return 5 - olen;
}

Project: samba
File: codepoints.c

_PUBLIC_ struct smb_iconv_handle *smb_iconv_handle_reinit(TALLOC_CTX *mem_ctx,
								    const char *dos_charset,
								    const char *unix_charset,
								    bool use_builtin_handlers,
								    struct smb_iconv_handle *old_ic)
{
	struct smb_iconv_handle *ret;

	if (old_ic != NULL) {
		ret = old_ic;
		close_iconv_handle(ret);
		talloc_free(ret->child_ctx);
		ZERO_STRUCTP(ret);
	} else {
		ret = talloc_zero(mem_ctx, struct smb_iconv_handle);
	}
	if (ret == NULL) {
		return NULL;
	}

	/* we use a child context to allow us to free all ptrs without
	   freeing the structure itself */
	ret->child_ctx = talloc_new(ret);
	if (ret->child_ctx == NULL) {
		return NULL;
	}

	talloc_set_destructor(ret, close_iconv_handle);

	if (strcasecmp(dos_charset, "UTF8") == 0 || strcasecmp(dos_charset, "UTF-8") == 0) {
		DEBUG(0,("ERROR: invalid DOS charset: 'dos charset' must not be UTF8, using (default value) CP850 instead\n"));
		dos_charset = "CP850";
	}

	ret->dos_charset = talloc_strdup(ret->child_ctx, dos_charset);
	ret->unix_charset = talloc_strdup(ret->child_ctx, unix_charset);
	ret->use_builtin_handlers = use_builtin_handlers;

	return ret;
}

Project: samba
File: codepoints.c

void smb_init_locale(void)
{
	/* This is a useful global hook where we can ensure that the
	 * locale is set from the environment.  This is needed so that
	 * we can use LOCALE as a codepage */
#ifdef HAVE_SETLOCALE
	setlocale(LC_ALL, "");
#endif
}

Project: samba
File: codepoints.c

_PUBLIC_ codepoint_t tolower_m(codepoint_t val)
{
	if (val < 128) {
		return tolower(val);
	}
	if (val >= ARRAY_SIZE(lowcase_table)) {
		return val;
	}
	return lowcase_table[val];
}

Project: samba
File: codepoints.c

_PUBLIC_ codepoint_t toupper_m(codepoint_t val)
{
	if (val < 128) {
		return toupper(val);
	}
	if (val >= ARRAY_SIZE(upcase_table)) {
		return val;
	}
	return upcase_table[val];
}