Re: Linux host controller string descriptors are odd-length

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



> Yes, this is a bug.  In ascii2utf, this line:
>
>	if (utfmax > 0) {
>
> should be replaced with this:
>
>	if (*s && utfmax > 0) {

Ah, I see.  Now it makes sense what the function is trying to do.
How about the following simplification instead:
(Released to the public domain, copyright abandoned, have fun...)

/*
 * helper routine for returning string descriptors in UTF-16LE
 * input can actually be ISO-8859-1; ASCII is its 7-bit subset.
 * @s: Null-terminated ASCII (actually ISO-8859-1) string
 * @utf: Buffer for UTF-16LE output
 * @utfmax: Length (in bytes; may be odd) of utf buffer.
 *
 * The return value is the number of bytes filled in: 2*strlen(s) or
 * utfmax, whichever is less.
 */
static unsigned ascii2utf(char const *s, u8 *utf, unsigned utfmax)
{
	u8 * const utf0 = utf;

	while (*s && utfmax--) {
		*utf++ = *s++;
		if (!utfmax--)
			break;
		*utf++ = 0;
	}
	return (unsigned)(utf - utf0);
}

You could also re-jigger rh_string() to just pass the cd->self.bus_name
and hcd->product_desc strings to ascii2utf() by reference rather than
strlcpy()ing them.

I also was worried that rh_string would leak data from the uninitialized
stack buffer if id > 3.  It turns out that that can't happen, but it sure
got my heart rate going for a bit...

static unsigned
rh_string(int id, struct usb_hcd const *hcd, u8 *data, unsigned len)
{
        char buf[100];
	char const *s;
	unsigned slen;

        // language ids
	switch (id) {
	case 0:
		/* Array of LANGID codes (0x0409 is MSFT-speak for "en-us") */
		static char const langids[4] = {4, 3, 0x09, 0x04};
		if (len > 4)
			len = 4;
                memcpy(data, langids, len);
                return len;
	case 1:
		/* Serial number */
		s = hcd->self.bus_name;
		break;
	case 2:
		/* Product name */
		s = hcd->product_desc;
		break;
	case 3:
		/* Manufacturer */
		snprintf (buf, sizeof buf, "%s %s %s", init_utsname()->sysname,
			init_utsname()->release, hcd->driver->description);
		s = buf;
		break;
	default:
		/* Can't happen; caller guarantees it */
		return 0;
	}

	slen = strlen(s);
	if (slen > 126)		/* Should never happen, but make sure */
		slen = 126;

        switch (len) {          /* All cases fall through */
        default:
                len = 2 + ascii2utf(s, data + 2, len - 2);
        case 2:
                data [1] = 3;   /* Descriptor type == string */
        case 1:
                data [0] = 2 * (slen + 1);	/* Total descriptor length */
        case 0:
                ;               /* Compiler wants a statement here */
        }
        return len;
}



You could also refactor the code to fill in the first 2 bytes inside
ascii2utf().  That cleans things up a lot:

/*
 * helper routine for returning string descriptors in UTF-16LE
 * input can actually be ISO-8859-1; ASCII is its 7-bit subset.
 * @s: Null-terminated ASCII (actually ISO-8859-1) string
 * @utf: Buffer for USB string descritpro (header + UTF-16LE)
 * @utfmax: Length (in bytes; may be odd) of utf buffer.
 *
 * The return value is the number of bytes filled in: 2 + 2*strlen(s) or
 * utfmax, whichever is less.
 */
static unsigned
ascii2desc(char const *s, u8 *utf, unsigned utfmax)
{
	u8 * const utf0 = utf;
	unsigned slen = strlen(s);

	if (slen > 126)
		slen = 126;	/* Limit so 2 + 2*slen <= 255 */

	if (!utfmax--)
		goto end;
	*utf++ = 2 + 2*slen;	/* Descriptor total length */

	if (!utfmax--)
		goto end;
	*utf++ = 3;		/* Descriptor type 3 (string) */

	if (utfmax > 2*slen)
		utfmax = 2*slen;

	while (utfmax--) {
		*utf++ = *s++;
		if (!utfmax--)
			break;
		*utf++ = 0;
	}
end:
	return (unsigned)(utf - utf0);
}

static unsigned
rh_string(int id, struct usb_hcd const *hcd, u8 *data, unsigned len)
{
        char buf[100];
	char const *s;

        // language ids
	switch (id) {
	case 0:
		/* Array of LANGID codes (0x0409 is MSFT-speak for "en-us") */
		static char const langids[4] = {4, 3, 0x09, 0x04};
		if (len > 4)
			len = 4;
                memcpy(data, langids, len);
                return len;
	case 1:
		/* Serial number */
		s = hcd->self.bus_name;
		break;
	case 2:
		/* Product name */
		s = hcd->product_desc;
		break;
	case 3:
		/* Manufacturer */
		snprintf (buf, sizeof buf, "%s %s %s", init_utsname()->sysname,
			init_utsname()->release, hcd->driver->description);
		s = buf;
		break;
	default:
		/* Can't happen; caller guarantees it */
		return 0;
	}

	return ascii2desc(s, data, len);
}


That sure looks a lot cleaner to me...
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux