CVE-2013-1773 (C, published 2013-02-28T19:55Z)
Description: value: Buffer overflow in the VFAT filesystem implementation in the Linux kernel before 3.3 allows local users to gain privileges or cause a denial of service (system crash) via a VFAT write operation on a filesystem with the utf8 mount option, which is not properly handled during UTF-8 to UTF-16 conversion.
Severity: MEDIUM (CVSS v3 base score: )
Weakness: CWE-119 \u2014 Improper Restriction of Operations within the Bounds of a Memory Buffer
The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
Repository: https://github.com/torvalds/linux
Files changed:
drivers/hv/hv_kvp.c
fs/fat/namei_vfat.c
fs/nls/nls_base.c
include/linux/nls.h
Commit message: NLS: improve UTF8 -> UTF16 string conversion routine

The utf8s_to_utf16s conversion routine needs to be improved.  Unlike
its utf16s_to_utf8s sibling, it doesn't accept arguments specifying
the maximum length of the output buffer or the endianness of its
16-bit output.

This patch (as1501) adds the two missing arguments, and adjusts the
only two places in the kernel where the function is called.  A
follow-on patch will add a third caller that does utilize the new
capabilities.

The two conversion routines are still annoyingly inconsistent in the
way they handle invalid byte combinations.  But that's a subject for a
different patch.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
CC: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>


Vulnerable code:
	keylen = utf8s_to_utf16s(key_name, strlen(key_name),
				(wchar_t *)kvp_data->data.key);
	valuelen = utf8s_to_utf16s(value, strlen(value),
				(wchar_t *)kvp_data->data.value);
		*outlen = utf8s_to_utf16s(name, len, (wchar_t *)outname);
int utf8s_to_utf16s(const u8 *s, int len, wchar_t *pwcs)
	while (*s && len > 0) {
				*op++ = (wchar_t) (SURROGATE_PAIR |
						((u >> 10) & SURROGATE_BITS));
				*op++ = (wchar_t) (SURROGATE_PAIR |
						(u & SURROGATE_BITS));
				*op++ = (wchar_t) u;
			s += size;
			len -= size;
			*op++ = *s++;
/* nls.c */
extern int utf8s_to_utf16s(const u8 *s, int len, wchar_t *pwcs);

Fixed code:
	keylen = utf8s_to_utf16s(key_name, strlen(key_name), UTF16_HOST_ENDIAN,
				(wchar_t *) kvp_data->data.key,
				HV_KVP_EXCHANGE_MAX_KEY_SIZE / 2);
	valuelen = utf8s_to_utf16s(value, strlen(value), UTF16_HOST_ENDIAN,
				(wchar_t *) kvp_data->data.value,
				HV_KVP_EXCHANGE_MAX_VALUE_SIZE / 2);
		*outlen = utf8s_to_utf16s(name, len, UTF16_HOST_ENDIAN,
				(wchar_t *) outname, FAT_LFN_LEN + 2);
static inline void put_utf16(wchar_t *s, unsigned c, enum utf16_endian endian)
{
	switch (endian) {
	default:
		*s = (wchar_t) c;
		break;
	case UTF16_LITTLE_ENDIAN:
		*s = __cpu_to_le16(c);
		break;
	case UTF16_BIG_ENDIAN:
		*s = __cpu_to_be16(c);
		break;
	}
}

int utf8s_to_utf16s(const u8 *s, int len, enum utf16_endian endian,
		wchar_t *pwcs, int maxlen)
	while (len > 0 && maxlen > 0 && *s) {
			s += size;
			len -= size;
				if (maxlen < 2)
					break;
				put_utf16(op++, SURROGATE_PAIR |
						((u >> 10) & SURROGATE_BITS),
						endian);
				put_utf16(op++, SURROGATE_PAIR |
						(u & SURROGATE_BITS),
						endian);
				maxlen -= 2;
				put_utf16(op++, u, endian);
				maxlen--;
			put_utf16(op++, *s++, endian);
			maxlen--;
/* nls_base.c */
extern int utf8s_to_utf16s(const u8 *s, int len,
		enum utf16_endian endian, wchar_t *pwcs, int maxlen);