Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > linux.kernel > #1603838 > unrolled thread

[PATCH v2 0/4] cdc-acm: reassemble fragmented notifications

Started byTobias Herzog <t-herzog@gmx.de>
First post2017-03-18 20:00 +0100
Last post2017-03-24 23:00 +0100
Articles 8 — 3 participants

Back to article view | Back to linux.kernel

This discussion starts older than the indexed window; earlier articles aren't shown. The article labeled Started by below is the oldest one visible, not the original post.


Contents

  [PATCH v2 0/4] cdc-acm: reassemble fragmented notifications Tobias Herzog <t-herzog@gmx.de> - 2017-03-18 20:00 +0100
    [PATCH v2 4/4] cdc-acm: remove unused element of struct acm Tobias Herzog <t-herzog@gmx.de> - 2017-03-18 20:00 +0100
    [PATCH v2 2/4] cdc-acm: fix possible invalid access when processing notification Tobias Herzog <t-herzog@gmx.de> - 2017-03-18 20:00 +0100
      Re: [PATCH v2 2/4] cdc-acm: fix possible invalid access when  processing notification Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> - 2017-03-19 11:00 +0100
      Re: [PATCH v2 2/4] cdc-acm: fix possible invalid access when  processing notification Oliver Neukum <oneukum@suse.com> - 2017-03-20 16:10 +0100
    [PATCH v2 1/4] cdc-acm: reassemble fragmented notifications Tobias Herzog <t-herzog@gmx.de> - 2017-03-18 20:10 +0100
      Re: [PATCH v2 1/4] cdc-acm: reassemble fragmented notifications Oliver Neukum <oneukum@suse.com> - 2017-03-20 16:10 +0100
        Re: [PATCH v2 1/4] cdc-acm: reassemble fragmented notifications Tobias Herzog <t-herzog@gmx.de> - 2017-03-24 23:00 +0100

#1603838 — [PATCH v2 0/4] cdc-acm: reassemble fragmented notifications

FromTobias Herzog <t-herzog@gmx.de>
Date2017-03-18 20:00 +0100
Subject[PATCH v2 0/4] cdc-acm: reassemble fragmented notifications
Message-ID<tmrK1-27E-3@gated-at.bofh.it>
USB devices may have very limitited endpoint packet sizes, so that
notifications can not be transferred within one single usb packet.
This patchset adds the ability to reassemble notifications that are
transmitted fragmented.

v2:
 * reuse an allocated buffer for further notifications
 * fixed issues with endianess
 * check buffer allocation (kmalloc)
 * don't use hard coded size of notification-header
 * fixed typo + code structure (unneeded goto)


Tobias Herzog (4):
  cdc-acm: reassemble fragmented notifications
  cdc-acm: fix possible invalid access when processing notification
  cdc-acm: log message for serial state notification
  cdc-acm: remove unused element of struct acm

 drivers/usb/class/cdc-acm.c | 119 ++++++++++++++++++++++++++++++++------------
 drivers/usb/class/cdc-acm.h |   4 +-
 2 files changed, 90 insertions(+), 33 deletions(-)

-- 
2.1.4

[toc] | [next] | [standalone]


#1603839 — [PATCH v2 4/4] cdc-acm: remove unused element of struct acm

FromTobias Herzog <t-herzog@gmx.de>
Date2017-03-18 20:00 +0100
Subject[PATCH v2 4/4] cdc-acm: remove unused element of struct acm
Message-ID<tmrK2-27E-7@gated-at.bofh.it>
In reply to#1603838
write_used was introduced with commit 884b600f63dc ("[PATCH] USB: fix acm
trouble with terminals") but never used since.

Signed-off-by: Tobias Herzog <t-herzog@gmx.de>
---
 drivers/usb/class/cdc-acm.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h
index b519138..7a2b3de 100644
--- a/drivers/usb/class/cdc-acm.h
+++ b/drivers/usb/class/cdc-acm.h
@@ -101,7 +101,6 @@ struct acm {
 	u8 *notification_buffer;			/* to reassemble fragmented notifications */
 	unsigned int nb_index;
 	unsigned int nb_size;
-	int write_used;					/* number of non-empty write buffers */
 	int transmitting;
 	spinlock_t write_lock;
 	struct mutex mutex;
-- 
2.1.4

[toc] | [prev] | [next] | [standalone]


#1603840 — [PATCH v2 2/4] cdc-acm: fix possible invalid access when processing notification

FromTobias Herzog <t-herzog@gmx.de>
Date2017-03-18 20:00 +0100
Subject[PATCH v2 2/4] cdc-acm: fix possible invalid access when processing notification
Message-ID<tmrK2-27E-9@gated-at.bofh.it>
In reply to#1603838
Notifications may only be 8 bytes so long. Accessing the 9th and
10th byte of unimplemented/unknown notifications may be insecure.
Also check the length of known notifications before accessing anything
behind the 8th byte.

Signed-off-by: Tobias Herzog <t-herzog@gmx.de>
---
 drivers/usb/class/cdc-acm.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c
index 74acca1..a7c8878 100644
--- a/drivers/usb/class/cdc-acm.c
+++ b/drivers/usb/class/cdc-acm.c
@@ -296,6 +296,12 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf)
 		break;
 
 	case USB_CDC_NOTIFY_SERIAL_STATE:
+		if (le16_to_cpu(dr->wLength) != 2) {
+			dev_dbg(&acm->control->dev,
+				"%s - malformed serial state\n", __func__);
+			break;
+		}
+
 		newctrl = get_unaligned_le16(data);
 
 		if (!acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) {
@@ -332,11 +338,10 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf)
 
 	default:
 		dev_dbg(&acm->control->dev,
-			"%s - unknown notification %d received: index %d "
-			"len %d data0 %d data1 %d\n",
+			"%s - unknown notification %d received: index %d len %d\n",
 			__func__,
 			dr->bNotificationType, dr->wIndex,
-			dr->wLength, data[0], data[1]);
+			dr->wLength);
 	}
 }
 
-- 
2.1.4

[toc] | [prev] | [next] | [standalone]


#1603928 — Re: [PATCH v2 2/4] cdc-acm: fix possible invalid access when processing notification

FromSergei Shtylyov <sergei.shtylyov@cogentembedded.com>
Date2017-03-19 11:00 +0100
SubjectRe: [PATCH v2 2/4] cdc-acm: fix possible invalid access when processing notification
Message-ID<tmFN0-3Je-1@gated-at.bofh.it>
In reply to#1603840
On 3/18/2017 9:52 PM, Tobias Herzog wrote:

> Notifications may only be 8 bytes so long. Accessing the 9th and

    s/so//.
    "So long and thanks for all the fish!" :-)

> 10th byte of unimplemented/unknown notifications may be insecure.
> Also check the length of known notifications before accessing anything
> behind the 8th byte.
>
> Signed-off-by: Tobias Herzog <t-herzog@gmx.de>
[...]

MBR, Sergei

[toc] | [prev] | [next] | [standalone]


#1604672 — Re: [PATCH v2 2/4] cdc-acm: fix possible invalid access when processing notification

FromOliver Neukum <oneukum@suse.com>
Date2017-03-20 16:10 +0100
SubjectRe: [PATCH v2 2/4] cdc-acm: fix possible invalid access when processing notification
Message-ID<tn76y-6tc-13@gated-at.bofh.it>
In reply to#1603840
Am Samstag, den 18.03.2017, 19:52 +0100 schrieb Tobias Herzog:
> Notifications may only be 8 bytes so long. Accessing the 9th and
> 10th byte of unimplemented/unknown notifications may be insecure.
> Also check the length of known notifications before accessing anything
> behind the 8th byte.
> 

This is fixing a potential security issue. Please make it first in the
series and CC it to stable@vger.kernel.org

	Regards
		Oliver

[toc] | [prev] | [next] | [standalone]


#1603843 — [PATCH v2 1/4] cdc-acm: reassemble fragmented notifications

FromTobias Herzog <t-herzog@gmx.de>
Date2017-03-18 20:10 +0100
Subject[PATCH v2 1/4] cdc-acm: reassemble fragmented notifications
Message-ID<tmrTI-2ru-13@gated-at.bofh.it>
In reply to#1603838
USB devices may have very limitited endpoint packet sizes, so that
notifications can not be transferred within one single usb packet.
Reassembling of multiple packages may be necessary.

Signed-off-by: Tobias Herzog <t-herzog@gmx.de>
---
 drivers/usb/class/cdc-acm.c | 106 ++++++++++++++++++++++++++++++++------------
 drivers/usb/class/cdc-acm.h |   3 ++
 2 files changed, 80 insertions(+), 29 deletions(-)

diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c
index e35b150..74acca1 100644
--- a/drivers/usb/class/cdc-acm.c
+++ b/drivers/usb/class/cdc-acm.c
@@ -282,39 +282,13 @@ static DEVICE_ATTR(iCountryCodeRelDate, S_IRUGO, show_country_rel_date, NULL);
  * Interrupt handlers for various ACM device responses
  */
 
-/* control interface reports status changes with "interrupt" transfers */
-static void acm_ctrl_irq(struct urb *urb)
+static void acm_process_notification(struct acm *acm, unsigned char *buf)
 {
-	struct acm *acm = urb->context;
-	struct usb_cdc_notification *dr = urb->transfer_buffer;
-	unsigned char *data;
 	int newctrl;
 	int difference;
-	int retval;
-	int status = urb->status;
-
-	switch (status) {
-	case 0:
-		/* success */
-		break;
-	case -ECONNRESET:
-	case -ENOENT:
-	case -ESHUTDOWN:
-		/* this urb is terminated, clean up */
-		dev_dbg(&acm->control->dev,
-			"%s - urb shutting down with status: %d\n",
-			__func__, status);
-		return;
-	default:
-		dev_dbg(&acm->control->dev,
-			"%s - nonzero urb status received: %d\n",
-			__func__, status);
-		goto exit;
-	}
+	struct usb_cdc_notification *dr = (struct usb_cdc_notification *)buf;
+	unsigned char *data = (unsigned char *)(dr + 1);
 
-	usb_mark_last_busy(acm->dev);
-
-	data = (unsigned char *)(dr + 1);
 	switch (dr->bNotificationType) {
 	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
 		dev_dbg(&acm->control->dev,
@@ -363,8 +337,77 @@ static void acm_ctrl_irq(struct urb *urb)
 			__func__,
 			dr->bNotificationType, dr->wIndex,
 			dr->wLength, data[0], data[1]);
+	}
+}
+
+/* control interface reports status changes with "interrupt" transfers */
+static void acm_ctrl_irq(struct urb *urb)
+{
+	struct acm *acm = urb->context;
+	struct usb_cdc_notification *dr = urb->transfer_buffer;
+	unsigned int current_size = urb->actual_length;
+	unsigned int expected_size, copy_size;
+	int retval;
+	int status = urb->status;
+
+	switch (status) {
+	case 0:
+		/* success */
 		break;
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		/* this urb is terminated, clean up */
+		acm->nb_index = 0;
+		dev_dbg(&acm->control->dev,
+			"%s - urb shutting down with status: %d\n",
+			__func__, status);
+		return;
+	default:
+		dev_dbg(&acm->control->dev,
+			"%s - nonzero urb status received: %d\n",
+			__func__, status);
+		goto exit;
 	}
+
+	usb_mark_last_busy(acm->dev);
+
+	if (acm->nb_index)
+		dr = (struct usb_cdc_notification *)acm->notification_buffer;
+
+	/* size = notification-header + (optional) data */
+	expected_size = sizeof(struct usb_cdc_notification) +
+					le16_to_cpu(dr->wLength);
+
+	if (current_size < expected_size) {
+		/* notification is transmitted fragmented, reassemble */
+		if (acm->nb_size < expected_size) {
+			if (acm->nb_size) {
+				kfree(acm->notification_buffer);
+				acm->nb_size = 0;
+			}
+			acm->notification_buffer =
+					kmalloc(expected_size, GFP_ATOMIC);
+			if (!acm->notification_buffer)
+				goto exit;
+			acm->nb_size = expected_size;
+		}
+
+		copy_size = min(current_size,
+				expected_size - acm->nb_index);
+
+		memcpy(&acm->notification_buffer[acm->nb_index],
+		       urb->transfer_buffer, copy_size);
+		acm->nb_index += copy_size;
+		current_size = acm->nb_index;
+	}
+
+	if (current_size >= expected_size) {
+		/* notification complete */
+		acm_process_notification(acm, (unsigned char *)dr);
+		acm->nb_index = 0;
+	}
+
 exit:
 	retval = usb_submit_urb(urb, GFP_ATOMIC);
 	if (retval && retval != -EPERM)
@@ -1488,6 +1531,9 @@ static int acm_probe(struct usb_interface *intf,
 			 epctrl->bInterval ? epctrl->bInterval : 16);
 	acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
 	acm->ctrlurb->transfer_dma = acm->ctrl_dma;
+	acm->notification_buffer = NULL;
+	acm->nb_index = 0;
+	acm->nb_size = 0;
 
 	dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor);
 
@@ -1580,6 +1626,8 @@ static void acm_disconnect(struct usb_interface *intf)
 	usb_free_coherent(acm->dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
 	acm_read_buffers_free(acm);
 
+	kfree(acm->notification_buffer);
+
 	if (!acm->combined_interfaces)
 		usb_driver_release_interface(&acm_driver, intf == acm->control ?
 					acm->data : acm->control);
diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h
index c980f11..b519138 100644
--- a/drivers/usb/class/cdc-acm.h
+++ b/drivers/usb/class/cdc-acm.h
@@ -98,6 +98,9 @@ struct acm {
 	struct acm_wb *putbuffer;			/* for acm_tty_put_char() */
 	int rx_buflimit;
 	spinlock_t read_lock;
+	u8 *notification_buffer;			/* to reassemble fragmented notifications */
+	unsigned int nb_index;
+	unsigned int nb_size;
 	int write_used;					/* number of non-empty write buffers */
 	int transmitting;
 	spinlock_t write_lock;
-- 
2.1.4

[toc] | [prev] | [next] | [standalone]


#1604668 — Re: [PATCH v2 1/4] cdc-acm: reassemble fragmented notifications

FromOliver Neukum <oneukum@suse.com>
Date2017-03-20 16:10 +0100
SubjectRe: [PATCH v2 1/4] cdc-acm: reassemble fragmented notifications
Message-ID<tn76x-6tc-1@gated-at.bofh.it>
In reply to#1603843
Am Samstag, den 18.03.2017, 19:52 +0100 schrieb Tobias Herzog:
> USB devices may have very limitited endpoint packet sizes, so that
> notifications can not be transferred within one single usb packet.
> Reassembling of multiple packages may be necessary.

Hi,

almost perfect.
A few new issue. Comments inline.

> 
> +	struct usb_cdc_notification *dr = (struct usb_cdc_notification *)buf;
> +	unsigned char *data = (unsigned char *)(dr + 1);

This is border line incorrect. It depends on the compiler not
adding padding. Please make it

buf + sizeof(struct usb_cdc_notification)

> -	usb_mark_last_busy(acm->dev);
> -
> -	data = (unsigned char *)(dr + 1);
>  	switch (dr->bNotificationType) {
>  	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
>  		dev_dbg(&acm->control->dev,
> @@ -363,8 +337,77 @@ static void acm_ctrl_irq(struct urb *urb)
>  			__func__,
>  			dr->bNotificationType, dr->wIndex,
>  			dr->wLength, data[0], data[1]);
> +	}
> +}
> +
> +/* control interface reports status changes with "interrupt" transfers */
> +static void acm_ctrl_irq(struct urb *urb)
> +{
> +	struct acm *acm = urb->context;
> +	struct usb_cdc_notification *dr = urb->transfer_buffer;
> +	unsigned int current_size = urb->actual_length;
> +	unsigned int expected_size, copy_size;
> +	int retval;
> +	int status = urb->status;
> +
> +	switch (status) {
> +	case 0:
> +		/* success */
>  		break;
> +	case -ECONNRESET:
> +	case -ENOENT:
> +	case -ESHUTDOWN:
> +		/* this urb is terminated, clean up */
> +		acm->nb_index = 0;
> +		dev_dbg(&acm->control->dev,
> +			"%s - urb shutting down with status: %d\n",
> +			__func__, status);
> +		return;
> +	default:
> +		dev_dbg(&acm->control->dev,
> +			"%s - nonzero urb status received: %d\n",
> +			__func__, status);
> +		goto exit;
>  	}
> +
> +	usb_mark_last_busy(acm->dev);
> +
> +	if (acm->nb_index)
> +		dr = (struct usb_cdc_notification *)acm->notification_buffer;
> +
> +	/* size = notification-header + (optional) data */
> +	expected_size = sizeof(struct usb_cdc_notification) +
> +					le16_to_cpu(dr->wLength);
> +
> +	if (current_size < expected_size) {
> +		/* notification is transmitted fragmented, reassemble */
> +		if (acm->nb_size < expected_size) {
> +			if (acm->nb_size) {
> +				kfree(acm->notification_buffer);
> +				acm->nb_size = 0;
> +			}
> +			acm->notification_buffer =
> +					kmalloc(expected_size, GFP_ATOMIC);

Given how kmalloc works you'd better round this up to a power of two.

> +			if (!acm->notification_buffer)
> +				goto exit;

This is most subtle. Please add a comment that this prevents a double
free if we get a disconnect()

> +			acm->nb_size = expected_size;
> +		}

	Regards
		Oliver

[toc] | [prev] | [next] | [standalone]


#1609004 — Re: [PATCH v2 1/4] cdc-acm: reassemble fragmented notifications

FromTobias Herzog <t-herzog@gmx.de>
Date2017-03-24 23:00 +0100
SubjectRe: [PATCH v2 1/4] cdc-acm: reassemble fragmented notifications
Message-ID<toFpw-7JB-9@gated-at.bofh.it>
In reply to#1604668
Hi Oliver,

thank you for your patience... :) I have a question to one of your
comments (see below).

Best regards,
Tobias

> Am Samstag, den 18.03.2017, 19:52 +0100 schrieb Tobias Herzog:
> > 
> > USB devices may have very limitited endpoint packet sizes, so that
> > notifications can not be transferred within one single usb packet.
> > Reassembling of multiple packages may be necessary.
> Hi,
> 
> almost perfect.
> A few new issue. Comments inline.
> 
> > 
> > 
> > +	struct usb_cdc_notification *dr = (struct
> > usb_cdc_notification *)buf;
> > +	unsigned char *data = (unsigned char *)(dr + 1);
> This is border line incorrect. It depends on the compiler not
> adding padding. Please make it
> 
> buf + sizeof(struct usb_cdc_notification)
> 
> > 
> > -	usb_mark_last_busy(acm->dev);
> > -
> > -	data = (unsigned char *)(dr + 1);
> >  	switch (dr->bNotificationType) {
> >  	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
> >  		dev_dbg(&acm->control->dev,
> > @@ -363,8 +337,77 @@ static void acm_ctrl_irq(struct urb *urb)
> >  			__func__,
> >  			dr->bNotificationType, dr->wIndex,
> >  			dr->wLength, data[0], data[1]);
> > +	}
> > +}
> > +
> > +/* control interface reports status changes with "interrupt"
> > transfers */
> > +static void acm_ctrl_irq(struct urb *urb)
> > +{
> > +	struct acm *acm = urb->context;
> > +	struct usb_cdc_notification *dr = urb->transfer_buffer;
> > +	unsigned int current_size = urb->actual_length;
> > +	unsigned int expected_size, copy_size;
> > +	int retval;
> > +	int status = urb->status;
> > +
> > +	switch (status) {
> > +	case 0:
> > +		/* success */
> >  		break;
> > +	case -ECONNRESET:
> > +	case -ENOENT:
> > +	case -ESHUTDOWN:
> > +		/* this urb is terminated, clean up */
> > +		acm->nb_index = 0;
> > +		dev_dbg(&acm->control->dev,
> > +			"%s - urb shutting down with status:
> > %d\n",
> > +			__func__, status);
> > +		return;
> > +	default:
> > +		dev_dbg(&acm->control->dev,
> > +			"%s - nonzero urb status received: %d\n",
> > +			__func__, status);
> > +		goto exit;
> >  	}
> > +
> > +	usb_mark_last_busy(acm->dev);
> > +
> > +	if (acm->nb_index)
> > +		dr = (struct usb_cdc_notification *)acm-
> > >notification_buffer;
> > +
> > +	/* size = notification-header + (optional) data */
> > +	expected_size = sizeof(struct usb_cdc_notification) +
> > +					le16_to_cpu(dr->wLength);
> > +
> > +	if (current_size < expected_size) {
> > +		/* notification is transmitted fragmented,
> > reassemble */
> > +		if (acm->nb_size < expected_size) {
> > +			if (acm->nb_size) {
> > +				kfree(acm->notification_buffer);
> > +				acm->nb_size = 0;
> > +			}
> > +			acm->notification_buffer =
> > +					kmalloc(expected_size,
> > GFP_ATOMIC);
> Given how kmalloc works you'd better round this up to a power of two.
> 
> > 
> > +			if (!acm->notification_buffer)
> > +				goto exit;
> This is most subtle. Please add a comment that this prevents a double
> free if we get a disconnect()
I'm unsure if I got this right: Are you talking about the fact, that
the use of 'kmalloc' will make 'acm->notification_buffer' valid again
(i.e. NULL or pointing to a correctly allocated block), after it was
"invalidated" by 'kfree' (in case the previously allocated buffer was
too small)?
The 'goto exit'-thing for me is just not to use the buffer if
allocation fails. Or am I missing anything here?
> 
> > 
> > +			acm->nb_size = expected_size;
> > +		}
> 	Regards
> 		Oliver
> 

[toc] | [prev] | [standalone]


Back to top | Article view | linux.kernel


csiph-web