From b9f90c30e1de6c33a6461aa03383694d73be08fb Mon Sep 17 00:00:00 2001 From: Markus Fritsche Date: Thu, 23 Apr 2026 11:58:50 +0200 Subject: [PATCH] patches: add tx-sdio-dma-oob (KFENCE OOB in SDIO TX path) Diagnosed via KFENCE splat on ohm (6.19.10-danctnix1, PineTab2): sdio_tx_work passes block-size-aligned length to dma_map_sg while the underlying skb linear head is only tx_buffer->len long. Fix: driver-owned DMA bounce page, memcpy + zero-pad per TX buffer. Both Mobian paths and drivers/staging/bes2600/ paths variants, checkpatch-clean. --- ...DIO-TX-buffers-to-avoid-DMA-OOB-read.patch | 123 ++++++++++++++++++ ...DIO-TX-buffers-to-avoid-DMA-OOB-read.patch | 123 ++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 patches/tx-sdio-dma-oob-danctnix/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch create mode 100644 patches/tx-sdio-dma-oob/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch diff --git a/patches/tx-sdio-dma-oob-danctnix/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch b/patches/tx-sdio-dma-oob-danctnix/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch new file mode 100644 index 000000000..0db0eed92 --- /dev/null +++ b/patches/tx-sdio-dma-oob-danctnix/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch @@ -0,0 +1,123 @@ +From 4ec7d25817af09654fb9439e472890f69281840c Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 11:58:31 +0200 +Subject: [PATCH] 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 +--- + drivers/staging/bes2600/bes2600_sdio.c | 39 ++++++++++++++++++++++++++++++++++++++- + 1 file changed, 38 insertions(+), 1 deletion(-) + +diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c +index b595365..7bc922c 100644 +--- a/drivers/staging/bes2600/bes2600_sdio.c ++++ b/drivers/staging/bes2600/bes2600_sdio.c +@@ -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); + } +-- +2.53.0 + diff --git a/patches/tx-sdio-dma-oob/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch b/patches/tx-sdio-dma-oob/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch new file mode 100644 index 000000000..62867bbd0 --- /dev/null +++ b/patches/tx-sdio-dma-oob/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch @@ -0,0 +1,123 @@ +From 4ec7d25817af09654fb9439e472890f69281840c Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 11:58:31 +0200 +Subject: [PATCH] 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 +--- + bes2600/bes2600_sdio.c | 39 ++++++++++++++++++++++++++++++++++++++- + 1 file changed, 38 insertions(+), 1 deletion(-) + +diff --git a/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c +index b595365..7bc922c 100644 +--- a/bes2600/bes2600_sdio.c ++++ b/bes2600/bes2600_sdio.c +@@ -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); + } +-- +2.53.0 +