bes2600: bounce SDIO TX buffers to avoid DMA OOB read
The SDIO TX path rounds the DMA transfer length up to the host's current block size and hands that length to dma_map_sg() via sg_set_buf(&sg[scatters], tx_buffer->buf, align) in sdio_tx_work(). tx_buffer->buf typically aliases into an skb linear head whose allocated size matches tx_buffer->len, not the block-aligned align. The DMA engine (swiotlb / dw_mci IDMAC) therefore reads up to one block past the end of the skb. On a PineTab2 with KFENCE enabled this fires as: BUG: KFENCE: out-of-bounds read in __pi_memcpy_generic Out-of-bounds read at ... (704B right of kfence-#...): __pi_memcpy_generic swiotlb_tbl_map_single swiotlb_map dma_direct_map_sg __dma_map_sg_attrs dma_map_sg_attrs dw_mci_pre_dma_transfer __dw_mci_start_request ... bes_sdio_memcpy_to_io_helper+0x18c/0x288 [bes2600] sdio_tx_work+0x2b4/0x4a0 [bes2600] allocated by ... pskb_expand_head / validate_xmit_skb / tcp_* In addition to being undefined behavior, the padding bytes (which come from whatever memory follows the skb) are transmitted to the peer, leaking kernel memory on the air. Allocate a driver-owned DMA-page bounce buffer sized to MAX_SDIO_TRANSFER_LEN and use it as the scatter-gather backing for sdio_tx_work. Each TX buffer is copied into its bounce slot and the tail (align - tx_buffer->len bytes) is zeroed. This mirrors the existing bounce pattern already used by bes2600_sdio_memcpy_toio() via single_gathered_buffer; a separate allocation is used for the TX path because single_gathered_buffer is only serialised via sdio_claim_host and sdio_tx_work accumulates scatter entries before claiming the bus. Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
This commit is contained in:
+38
-1
@@ -94,6 +94,7 @@ struct sbus_priv {
|
||||
struct work_struct tx_work;
|
||||
struct scatterlist tx_sg[BES_SDIO_TX_MULTIPLE_NUM + 1];
|
||||
struct scatterlist tx_sg_nosignal[BES_SDIO_TX_MULTIPLE_NUM_NOSIGNAL + 1];
|
||||
u8 *tx_bounce;
|
||||
u32 tx_data_cnt;
|
||||
u32 tx_xfer_cnt;
|
||||
u32 tx_proc_cnt;
|
||||
@@ -1135,7 +1136,26 @@ static void sdio_tx_work(struct work_struct *work)
|
||||
}
|
||||
}
|
||||
|
||||
sg_set_buf(&sg[scatters], tx_buffer->buf, align);
|
||||
/*
|
||||
* The transfer length is rounded up to the SDIO block
|
||||
* size, but tx_buffer->buf is only tx_buffer->len bytes
|
||||
* long (it usually aliases into an skb linear head).
|
||||
* Copy into a driver-owned bounce buffer and zero-pad
|
||||
* to the aligned size; otherwise DMA reads past the
|
||||
* skb and leaks adjacent kernel memory on the wire --
|
||||
* observed as KFENCE OOB reads from
|
||||
* bes_sdio_memcpy_to_io_helper via dma_map_sg.
|
||||
*/
|
||||
if (WARN_ON_ONCE(total_len + align > MAX_SDIO_TRANSFER_LEN))
|
||||
goto flush_previous;
|
||||
memcpy(self->tx_bounce + total_len,
|
||||
tx_buffer->buf, tx_buffer->len);
|
||||
if (align > tx_buffer->len)
|
||||
memset(self->tx_bounce + total_len +
|
||||
tx_buffer->len, 0,
|
||||
align - tx_buffer->len);
|
||||
sg_set_buf(&sg[scatters],
|
||||
self->tx_bounce + total_len, align);
|
||||
total_len += align;
|
||||
++scatters;
|
||||
/*del_node:*/
|
||||
@@ -1853,6 +1873,17 @@ static int bes2600_sdio_probe(struct sdio_func *func,
|
||||
if (!self->single_gathered_buffer)
|
||||
return -ENOMEM;
|
||||
#endif
|
||||
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
|
||||
self->tx_bounce = (u8 *)__get_dma_pages(GFP_KERNEL,
|
||||
get_order(MAX_SDIO_TRANSFER_LEN));
|
||||
if (!self->tx_bounce) {
|
||||
#ifndef SDIO_HOST_ADMA_SUPPORT
|
||||
free_pages((unsigned long)self->single_gathered_buffer,
|
||||
get_order(MAX_SDIO_TRANSFER_LEN));
|
||||
#endif
|
||||
return -ENOMEM;
|
||||
}
|
||||
#endif
|
||||
#ifdef BES_SDIO_RXTX_TOGGLE
|
||||
self->fw_started = false;
|
||||
#endif
|
||||
@@ -1981,6 +2012,12 @@ static void bes2600_sdio_remove(struct sdio_func *func)
|
||||
if (self->single_gathered_buffer) {
|
||||
free_pages((unsigned long)self->single_gathered_buffer, get_order(MAX_SDIO_TRANSFER_LEN));
|
||||
}
|
||||
#endif
|
||||
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
|
||||
if (self->tx_bounce) {
|
||||
free_pages((unsigned long)self->tx_bounce,
|
||||
get_order(MAX_SDIO_TRANSFER_LEN));
|
||||
}
|
||||
#endif
|
||||
kfree(self);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user