Re: Linux host controller string descriptors are odd-length

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

 



On 22 Aug 2009, George Spelvin wrote:

> > 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...

Please feel free to submit a patch.

Alan Stern

--
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