Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 825a8436 authored by Jack Pham's avatar Jack Pham
Browse files

usb: gadget: u_ether: Fix incorrect S/G free in tx_complete



When tx_complete() is called back with a non-successful request,
as can happen with cable disconnection, the scatter-gather case
incorrectly executes the non-scatter-gather handling which treats
the req->context member as an SKB (when it is actually a pointer
to an skb_buff_head) and wrongly calls dev_kfree_skb_any() on it.

Since the completion happens in interrupt context, the effect of
this bad free is not immediately seen until the net_tx_action
thread later runs, actually executes kfree_skb() and crashes.

Fix this by properly handling the completed scatter-gather request
in both success and error cases so that skb_queue_purge() is called.

Change-Id: I2a31f3ca45fa0f0eee325edc4cadd8cb8096f9df
Signed-off-by: default avatarJack Pham <jackp@codeaurora.org>
parent a4ff7275
Loading
Loading
Loading
Loading
+21 −20
Original line number Diff line number Diff line
@@ -641,6 +641,7 @@ static void tx_complete(struct usb_ep *ep, struct usb_request *req)
	struct net_device *net;
	struct usb_request *new_req;
	struct usb_ep *in;
	int n = 1;
	int length;
	int retval;

@@ -666,35 +667,35 @@ static void tx_complete(struct usb_ep *ep, struct usb_request *req)
	case -ESHUTDOWN:		/* disconnect etc */
		break;
	case 0:
		if (!req->zero)
			dev->net->stats.tx_bytes += req->length-1;
		else
			dev->net->stats.tx_bytes += req->length;
	}

	if (req->num_sgs) {
		struct sg_ctx *sg_ctx = req->context;
			int n = skb_queue_len(&sg_ctx->skbs);

			dev->net->stats.tx_bytes += req->length;
			dev->net->stats.tx_packets += n;
		n = skb_queue_len(&sg_ctx->skbs);
		dev->tx_aggr_cnt[n-1]++;

			skb_queue_purge(&sg_ctx->skbs);
		/* sg_ctx is only accessible here, can use lock-free version */
		__skb_queue_purge(&sg_ctx->skbs);
	}

	dev->net->stats.tx_packets += n;

	spin_lock(&dev->req_lock);
	list_add_tail(&req->list, &dev->tx_reqs);
			spin_unlock(&dev->req_lock);

	if (req->num_sgs) {
		if (!req->status)
			queue_work(uether_tx_wq, &dev->tx_work);

		spin_unlock(&dev->req_lock);
		return;
	}

		if (!req->zero)
			dev->net->stats.tx_bytes += req->length-1;
		else
			dev->net->stats.tx_bytes += req->length;
	}
	dev->net->stats.tx_packets++;

	spin_lock(&dev->req_lock);
	list_add_tail(&req->list, &dev->tx_reqs);

	if (dev->port_usb->multi_pkt_xfer && !req->context) {
		dev->no_tx_req_used--;
		req->length = 0;