[PATCH] wifi: libertas_tf: Fix slab-out-of-bounds write in if_usb_send_fw_pkt()
Yang Zi <[email protected]>
| Newsgroups | org.kernel.vger.linux-wireless,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
if_usb_send_fw_pkt() copies a firmware block into the driver's ep_out_buf using the length taken directly from the firmware header (fwdata->hdr.datalength) without any validation. That same untrusted value is also used as the USB transfer length and to advance cardp->totalbytes. A crafted or truncated firmware image can set datalength to a huge value, which makes the memcpy() into fwdata->data write far past the end of the 1574-byte ep_out_buf (KASAN reports a ~3.5 GiB slab-out-of-bounds write) and, once totalbytes has run away, makes the subsequent header memcpy() read past the end of the firmware image. check_fwfile_format() only checks the cumulative length and never bounds an individual block, and FW_MAX_DATA_BLK_SIZE (600) is never used. Validate the block length in if_usb_send_fw_pkt(): reject any block whose datalength exceeds FW_MAX_DATA_BLK_SIZE or the number of bytes remaining in the firmware image, and return -EINVAL before the memcpy(). Also enforce the per-block upper bound in check_fwfile_format() so that invalid images are rejected up front. Signed-off-by: Yang Zi <[email protected]> --- diff --git a/drivers/net/wireless/marvell/libertas_tf/if_usb.c b/drivers/net/wireless/marvell/libertas_tf/if_usb.c index b85c6d783bf7..f82860b77da2 100644 --- a/drivers/net/wireless/marvell/libertas_tf/if_usb.c +++ b/drivers/net/wireless/marvell/libertas_tf/if_usb.c @@ -268,6 +268,7 @@ static int if_usb_send_fw_pkt(struct if_usb_card *cardp) { struct fwdata *fwdata = cardp->ep_out_buf; u8 *firmware = (u8 *) cardp->fw->data; + u32 datalength; lbtf_deb_enter(LBTF_DEB_FW); @@ -291,17 +292,24 @@ static int if_usb_send_fw_pkt(struct if_usb_card *cardp) cardp->fwlastblksent = cardp->totalbytes; cardp->totalbytes += sizeof(struct fwheader); - memcpy(fwdata->data, &firmware[cardp->totalbytes], - le32_to_cpu(fwdata->hdr.datalength)); + datalength = le32_to_cpu(fwdata->hdr.datalength); + if (datalength > FW_MAX_DATA_BLK_SIZE || + cardp->totalbytes > cardp->fw->size || + datalength > cardp->fw->size - cardp->totalbytes) { + lbtf_deb_usb2(&cardp->udev->dev, + "invalid firmware block length %u\n", datalength); + return -EINVAL; + } - lbtf_deb_usb2(&cardp->udev->dev, "Data length = %d\n", - le32_to_cpu(fwdata->hdr.datalength)); + memcpy(fwdata->data, &firmware[cardp->totalbytes], datalength); + + lbtf_deb_usb2(&cardp->udev->dev, "Data length = %u\n", datalength); fwdata->seqnum = cpu_to_le32(++cardp->fwseqnum); - cardp->totalbytes += le32_to_cpu(fwdata->hdr.datalength); + cardp->totalbytes += datalength; usb_tx_block(cardp, cardp->ep_out_buf, sizeof(struct fwdata) + - le32_to_cpu(fwdata->hdr.datalength), 0); + datalength, 0); if (fwdata->hdr.dnldcmd == cpu_to_le32(FW_HAS_DATA_TO_RECV)) { lbtf_deb_usb2(&cardp->udev->dev, "There are data to follow\n"); @@ -775,6 +783,10 @@ static int check_fwfile_format(const u8 *data, u32 totlen) blksize = le32_to_cpu(fwh->datalength); switch (bincmd) { case FW_HAS_DATA_TO_RECV: + if (blksize > FW_MAX_DATA_BLK_SIZE) { + exit = 1; + break; + } offset = sizeof(struct fwheader) + blksize; data += offset; len += offset; @@ -782,6 +794,10 @@ static int check_fwfile_format(const u8 *data, u32 totlen) exit = 1; break; case FW_HAS_LAST_BLOCK: + if (blksize > FW_MAX_DATA_BLK_SIZE) { + exit = 1; + break; + } exit = 1; ret = 0; break;