Compare commits

..

1 Commits

Author SHA1 Message Date
test0r 4ec7d25817 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>
2026-04-23 11:58:31 +02:00
4 changed files with 42 additions and 28 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ KERN_DIR = /lib/modules/$(KERNELRELEASE)/build
# feature option # feature option
BES2600 ?= m BES2600 ?= m
CONFIG_BES2600_TESTMODE ?= y CONFIG_BES2600_TESTMODE ?= n
CONFIG_BES2600_ENABLE_DEVEL_LOGS ?= n CONFIG_BES2600_ENABLE_DEVEL_LOGS ?= n
+38 -1
View File
@@ -94,6 +94,7 @@ struct sbus_priv {
struct work_struct tx_work; struct work_struct tx_work;
struct scatterlist tx_sg[BES_SDIO_TX_MULTIPLE_NUM + 1]; struct scatterlist tx_sg[BES_SDIO_TX_MULTIPLE_NUM + 1];
struct scatterlist tx_sg_nosignal[BES_SDIO_TX_MULTIPLE_NUM_NOSIGNAL + 1]; struct scatterlist tx_sg_nosignal[BES_SDIO_TX_MULTIPLE_NUM_NOSIGNAL + 1];
u8 *tx_bounce;
u32 tx_data_cnt; u32 tx_data_cnt;
u32 tx_xfer_cnt; u32 tx_xfer_cnt;
u32 tx_proc_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; total_len += align;
++scatters; ++scatters;
/*del_node:*/ /*del_node:*/
@@ -1853,6 +1873,17 @@ static int bes2600_sdio_probe(struct sdio_func *func,
if (!self->single_gathered_buffer) if (!self->single_gathered_buffer)
return -ENOMEM; return -ENOMEM;
#endif #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 #ifdef BES_SDIO_RXTX_TOGGLE
self->fw_started = false; self->fw_started = false;
#endif #endif
@@ -1981,6 +2012,12 @@ static void bes2600_sdio_remove(struct sdio_func *func)
if (self->single_gathered_buffer) { if (self->single_gathered_buffer) {
free_pages((unsigned long)self->single_gathered_buffer, get_order(MAX_SDIO_TRANSFER_LEN)); 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 #endif
kfree(self); kfree(self);
} }
-23
View File
@@ -8,26 +8,3 @@ extern struct device *global_dev;
#define bes_info(fmt, ...) dev_info(global_dev, fmt, ##__VA_ARGS__) #define bes_info(fmt, ...) dev_info(global_dev, fmt, ##__VA_ARGS__)
#define bes_warn(fmt, ...) dev_warn(global_dev, fmt, ##__VA_ARGS__) #define bes_warn(fmt, ...) dev_warn(global_dev, fmt, ##__VA_ARGS__)
#define bes_err(fmt, ...) dev_err(global_dev, fmt, ##__VA_ARGS__) #define bes_err(fmt, ...) dev_err(global_dev, fmt, ##__VA_ARGS__)
/*
* Legacy debug-subsystem-tagged log macros. The per-subsystem filtering
* was never implemented in-tree; these shims let code paths gated by
* CONFIG_BES2600_TESTMODE / CONFIG_BES2600_ITP / BES2600_DETECTION_LOGIC
* build when their conditions are enabled. The first argument is
* currently unused; pick one of the BES2600_DBG_* constants below for
* documentation.
*/
#define BES2600_DBG_SBUS 0
#define BES2600_DBG_DOWNLOAD 0
#define BES2600_DBG_ITP 0
#define BES2600_DBG_TEST_MODE 0
#define bes2600_info(_dbg, fmt, ...) bes_info(fmt, ##__VA_ARGS__)
#define bes2600_err(_dbg, fmt, ...) bes_err(fmt, ##__VA_ARGS__)
#define bes2600_warn(_dbg, fmt, ...) bes_warn(fmt, ##__VA_ARGS__)
#define bes2600_dbg(_dbg, fmt, ...) bes_devel(fmt, ##__VA_ARGS__)
#define bes2600_err_with_cond(_cond, _dbg, fmt, ...) \
do { \
if (_cond) \
bes_err(fmt, ##__VA_ARGS__); \
} while (0)
+3 -3
View File
@@ -3633,7 +3633,7 @@ static int bes2600_set_power_save(struct ieee80211_hw *hw,
* *
* Returns: 0 on success or non zero value on failure * Returns: 0 on success or non zero value on failure
*/ */
static int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data) int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
{ {
struct bes_msg_start_stop_tsm *start_stop_tsm = struct bes_msg_start_stop_tsm *start_stop_tsm =
(struct bes_msg_start_stop_tsm *) data; (struct bes_msg_start_stop_tsm *) data;
@@ -3663,7 +3663,7 @@ static int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
* *
* Returns: TSM parameters collected * Returns: TSM parameters collected
*/ */
static int bes2600_get_tsm_params(struct ieee80211_hw *hw) int bes2600_get_tsm_params(struct ieee80211_hw *hw)
{ {
struct bes2600_common *hw_priv = hw->priv; struct bes2600_common *hw_priv = hw->priv;
struct bes_tsm_stats tsm_stats; struct bes_tsm_stats tsm_stats;
@@ -3703,7 +3703,7 @@ static int bes2600_get_tsm_params(struct ieee80211_hw *hw)
* *
* Returns: Returns the last measured roam delay * Returns: Returns the last measured roam delay
*/ */
static int bes2600_get_roam_delay(struct ieee80211_hw *hw) int bes2600_get_roam_delay(struct ieee80211_hw *hw)
{ {
struct bes2600_common *hw_priv = hw->priv; struct bes2600_common *hw_priv = hw->priv;
u16 roam_delay = hw_priv->tsm_info.roam_delay / 1000; u16 roam_delay = hw_priv->tsm_info.roam_delay / 1000;