From 59bd12d37072b7c14658b3940d8584f7c1545431 Mon Sep 17 00:00:00 2001 From: Lior David Date: Mon, 16 Oct 2017 20:39:31 +0300 Subject: [PATCH 1/4] wil6210: fix length check in __wmi_send The current length check: sizeof(cmd) + len > r->entry_size will allow very large values of len (> U16_MAX - sizeof(cmd)) and can cause a buffer overflow. Fix the check to cover this case. In addition, ensure the mailbox entry_size is not too small, since this can also bypass the above check. Change-Id: Iecb4f53ef05da0e015bc954b57b0e40debb7c8b7 Signed-off-by: Lior David --- drivers/net/wireless/ath/wil6210/interrupt.c | 22 +++++++++++++++++++- drivers/net/wireless/ath/wil6210/wmi.c | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/wil6210/interrupt.c b/drivers/net/wireless/ath/wil6210/interrupt.c index 59def4f3fcf3..5cf341702dc1 100644 --- a/drivers/net/wireless/ath/wil6210/interrupt.c +++ b/drivers/net/wireless/ath/wil6210/interrupt.c @@ -358,6 +358,25 @@ static void wil_cache_mbox_regs(struct wil6210_priv *wil) wil_mbox_ring_le2cpus(&wil->mbox_ctl.tx); } +static bool wil_validate_mbox_regs(struct wil6210_priv *wil) +{ + size_t min_size = sizeof(struct wil6210_mbox_hdr) + + sizeof(struct wmi_cmd_hdr); + + if (wil->mbox_ctl.rx.entry_size < min_size) { + wil_err(wil, "rx mbox entry too small (%d)\n", + wil->mbox_ctl.rx.entry_size); + return false; + } + if (wil->mbox_ctl.tx.entry_size < min_size) { + wil_err(wil, "tx mbox entry too small (%d)\n", + wil->mbox_ctl.tx.entry_size); + return false; + } + + return true; +} + static irqreturn_t wil6210_irq_misc(int irq, void *cookie) { struct wil6210_priv *wil = cookie; @@ -393,7 +412,8 @@ static irqreturn_t wil6210_irq_misc(int irq, void *cookie) if (isr & ISR_MISC_FW_READY) { wil_dbg_irq(wil, "IRQ: FW ready\n"); wil_cache_mbox_regs(wil); - set_bit(wil_status_mbox_ready, wil->status); + if (wil_validate_mbox_regs(wil)) + set_bit(wil_status_mbox_ready, wil->status); /** * Actual FW ready indicated by the * WMI_FW_READY_EVENTID diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c index e421fdad81e2..10bfd1681280 100644 --- a/drivers/net/wireless/ath/wil6210/wmi.c +++ b/drivers/net/wireless/ath/wil6210/wmi.c @@ -223,7 +223,7 @@ static int __wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len) uint retry; int rc = 0; - if (sizeof(cmd) + len > r->entry_size) { + if (len > r->entry_size - sizeof(cmd)) { wil_err(wil, "WMI size too large: %d bytes, max is %d\n", (int)(sizeof(cmd) + len), r->entry_size); return -ERANGE; From 5c9c0841e591001071a138644f6698de9d077210 Mon Sep 17 00:00:00 2001 From: Lior David Date: Tue, 17 Oct 2017 11:13:36 +0300 Subject: [PATCH 2/4] wil6210: add block size checks during FW load When loading FW from file add block size checks to ensure a corrupted FW file will not cause the driver to write outside the device memory. Change-Id: I6d7342cd33b2c47b701bcca4ee3cd84febbc56a2 Signed-off-by: Lior David --- drivers/net/wireless/ath/wil6210/fw_inc.c | 58 ++++++++++++++-------- drivers/net/wireless/ath/wil6210/wil6210.h | 1 + drivers/net/wireless/ath/wil6210/wmi.c | 11 +++- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/drivers/net/wireless/ath/wil6210/fw_inc.c b/drivers/net/wireless/ath/wil6210/fw_inc.c index 7a33792913a3..77d1902947e3 100644 --- a/drivers/net/wireless/ath/wil6210/fw_inc.c +++ b/drivers/net/wireless/ath/wil6210/fw_inc.c @@ -26,14 +26,17 @@ prefix_type, rowsize, \ groupsize, buf, len, ascii) -#define FW_ADDR_CHECK(ioaddr, val, msg) do { \ - ioaddr = wmi_buffer(wil, val); \ - if (!ioaddr) { \ - wil_err_fw(wil, "bad " msg ": 0x%08x\n", \ - le32_to_cpu(val)); \ - return -EINVAL; \ - } \ - } while (0) +static bool wil_fw_addr_check(struct wil6210_priv *wil, + void __iomem **ioaddr, __le32 val, + u32 size, const char *msg) +{ + *ioaddr = wmi_buffer_block(wil, val, size); + if (!(*ioaddr)) { + wil_err_fw(wil, "bad %s: 0x%08x\n", msg, le32_to_cpu(val)); + return false; + } + return true; +} /** * wil_fw_verify - verify firmware file validity @@ -160,7 +163,8 @@ static int fw_handle_data(struct wil6210_priv *wil, const void *data, return -EINVAL; } - FW_ADDR_CHECK(dst, d->addr, "address"); + if (!wil_fw_addr_check(wil, &dst, d->addr, s, "address")) + return -EINVAL; wil_dbg_fw(wil, "write [0x%08x] <== %zu bytes\n", le32_to_cpu(d->addr), s); wil_memcpy_toio_32(dst, d->data, s); @@ -192,7 +196,8 @@ static int fw_handle_fill(struct wil6210_priv *wil, const void *data, return -EINVAL; } - FW_ADDR_CHECK(dst, d->addr, "address"); + if (!wil_fw_addr_check(wil, &dst, d->addr, s, "address")) + return -EINVAL; v = le32_to_cpu(d->value); wil_dbg_fw(wil, "fill [0x%08x] <== 0x%08x, %zu bytes\n", @@ -248,7 +253,8 @@ static int fw_handle_direct_write(struct wil6210_priv *wil, const void *data, u32 v = le32_to_cpu(block[i].value); u32 x, y; - FW_ADDR_CHECK(dst, block[i].addr, "address"); + if (!wil_fw_addr_check(wil, &dst, block[i].addr, 0, "address")) + return -EINVAL; x = readl(dst); y = (x & m) | (v & ~m); @@ -314,10 +320,15 @@ static int fw_handle_gateway_data(struct wil6210_priv *wil, const void *data, wil_dbg_fw(wil, "gw write record [%3d] blocks, cmd 0x%08x\n", n, gw_cmd); - FW_ADDR_CHECK(gwa_addr, d->gateway_addr_addr, "gateway_addr_addr"); - FW_ADDR_CHECK(gwa_val, d->gateway_value_addr, "gateway_value_addr"); - FW_ADDR_CHECK(gwa_cmd, d->gateway_cmd_addr, "gateway_cmd_addr"); - FW_ADDR_CHECK(gwa_ctl, d->gateway_ctrl_address, "gateway_ctrl_address"); + if (!wil_fw_addr_check(wil, &gwa_addr, d->gateway_addr_addr, 0, + "gateway_addr_addr") || + !wil_fw_addr_check(wil, &gwa_val, d->gateway_value_addr, 0, + "gateway_value_addr") || + !wil_fw_addr_check(wil, &gwa_cmd, d->gateway_cmd_addr, 0, + "gateway_cmd_addr") || + !wil_fw_addr_check(wil, &gwa_ctl, d->gateway_ctrl_address, 0, + "gateway_ctrl_address")) + return -EINVAL; wil_dbg_fw(wil, "gw addresses: addr 0x%08x val 0x%08x" " cmd 0x%08x ctl 0x%08x\n", @@ -373,12 +384,19 @@ static int fw_handle_gateway_data4(struct wil6210_priv *wil, const void *data, wil_dbg_fw(wil, "gw4 write record [%3d] blocks, cmd 0x%08x\n", n, gw_cmd); - FW_ADDR_CHECK(gwa_addr, d->gateway_addr_addr, "gateway_addr_addr"); + if (!wil_fw_addr_check(wil, &gwa_addr, d->gateway_addr_addr, 0, + "gateway_addr_addr")) + return -EINVAL; for (k = 0; k < ARRAY_SIZE(block->value); k++) - FW_ADDR_CHECK(gwa_val[k], d->gateway_value_addr[k], - "gateway_value_addr"); - FW_ADDR_CHECK(gwa_cmd, d->gateway_cmd_addr, "gateway_cmd_addr"); - FW_ADDR_CHECK(gwa_ctl, d->gateway_ctrl_address, "gateway_ctrl_address"); + if (!wil_fw_addr_check(wil, &gwa_val[k], + d->gateway_value_addr[k], + 0, "gateway_value_addr")) + return -EINVAL; + if (!wil_fw_addr_check(wil, &gwa_cmd, d->gateway_cmd_addr, 0, + "gateway_cmd_addr") || + !wil_fw_addr_check(wil, &gwa_ctl, d->gateway_ctrl_address, 0, + "gateway_ctrl_address")) + return -EINVAL; wil_dbg_fw(wil, "gw4 addresses: addr 0x%08x cmd 0x%08x ctl 0x%08x\n", le32_to_cpu(d->gateway_addr_addr), diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h index ef10abc07da6..a89bd12d3005 100644 --- a/drivers/net/wireless/ath/wil6210/wil6210.h +++ b/drivers/net/wireless/ath/wil6210/wil6210.h @@ -878,6 +878,7 @@ void wil_mbox_ring_le2cpus(struct wil6210_mbox_ring *r); int wil_find_cid(struct wil6210_priv *wil, const u8 *mac); void wil_set_ethtoolops(struct net_device *ndev); +void __iomem *wmi_buffer_block(struct wil6210_priv *wil, __le32 ptr, u32 size); void __iomem *wmi_buffer(struct wil6210_priv *wil, __le32 ptr); void __iomem *wmi_addr(struct wil6210_priv *wil, u32 ptr); int wmi_read_hdr(struct wil6210_priv *wil, __le32 ptr, diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c index 10bfd1681280..6fa93854806b 100644 --- a/drivers/net/wireless/ath/wil6210/wmi.c +++ b/drivers/net/wireless/ath/wil6210/wmi.c @@ -141,13 +141,15 @@ static u32 wmi_addr_remap(u32 x) /** * Check address validity for WMI buffer; remap if needed * @ptr - internal (linker) fw/ucode address + * @size - if non zero, validate the block does not + * exceed the device memory (bar) * * Valid buffer should be DWORD aligned * * return address for accessing buffer from the host; * if buffer is not valid, return NULL. */ -void __iomem *wmi_buffer(struct wil6210_priv *wil, __le32 ptr_) +void __iomem *wmi_buffer_block(struct wil6210_priv *wil, __le32 ptr_, u32 size) { u32 off; u32 ptr = le32_to_cpu(ptr_); @@ -162,10 +164,17 @@ void __iomem *wmi_buffer(struct wil6210_priv *wil, __le32 ptr_) off = HOSTADDR(ptr); if (off > wil->bar_size - 4) return NULL; + if (size && ((off + size > wil->bar_size) || (off + size < off))) + return NULL; return wil->csr + off; } +void __iomem *wmi_buffer(struct wil6210_priv *wil, __le32 ptr_) +{ + return wmi_buffer_block(wil, ptr_, 0); +} + /** * Check address validity */ From 6e5a9b32503d37a202fccc5d24b189ae6107a256 Mon Sep 17 00:00:00 2001 From: Lior David Date: Tue, 17 Oct 2017 14:18:17 +0300 Subject: [PATCH 3/4] wil6210: missing length check in wmi_set_ie Add a length check in wmi_set_ie to detect unsigned integer overflow. Change-Id: Id1ec6a6218f3fe6e00cc3f9a8e674f8f843273f2 Signed-off-by: Lior David --- drivers/net/wireless/ath/wil6210/wmi.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c index 6fa93854806b..160eb17fc0ed 100644 --- a/drivers/net/wireless/ath/wil6210/wmi.c +++ b/drivers/net/wireless/ath/wil6210/wmi.c @@ -1420,8 +1420,14 @@ int wmi_set_ie(struct wil6210_priv *wil, u8 type, u16 ie_len, const void *ie) }; int rc; u16 len = sizeof(struct wmi_set_appie_cmd) + ie_len; - struct wmi_set_appie_cmd *cmd = kzalloc(len, GFP_KERNEL); + struct wmi_set_appie_cmd *cmd; + if (len < ie_len) { + rc = -EINVAL; + goto out; + } + + cmd = kzalloc(len, GFP_KERNEL); if (!cmd) { rc = -ENOMEM; goto out; From 3d38ac71cc4ae56d208d03b73fa39a50d73641d8 Mon Sep 17 00:00:00 2001 From: Lior David Date: Tue, 17 Oct 2017 15:44:26 +0300 Subject: [PATCH 4/4] wil6210: missing length check in wil_cfg80211_mgmt_tx Add a length check in wil_cfg80211_mgmt_tx to detect unsigned integer overflow. Change-Id: I37f988481433a2e1238831980715aef32aa89a85 Signed-off-by: Lior David --- drivers/net/wireless/ath/wil6210/cfg80211.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index 35dfa410c90c..797c157fa0ba 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -977,7 +977,7 @@ int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, u64 *cookie) { const u8 *buf = params->buf; - size_t len = params->len; + size_t len = params->len, total; struct wil6210_priv *wil = wiphy_to_wil(wiphy); int rc; bool tx_status = false; @@ -1002,7 +1002,11 @@ int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, if (len < sizeof(struct ieee80211_hdr_3addr)) return -EINVAL; - cmd = kmalloc(sizeof(*cmd) + len, GFP_KERNEL); + total = sizeof(*cmd) + len; + if (total < len) + return -EINVAL; + + cmd = kmalloc(total, GFP_KERNEL); if (!cmd) { rc = -ENOMEM; goto out; @@ -1012,7 +1016,7 @@ int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, cmd->len = cpu_to_le16(len); memcpy(cmd->payload, buf, len); - rc = wmi_call(wil, WMI_SW_TX_REQ_CMDID, cmd, sizeof(*cmd) + len, + rc = wmi_call(wil, WMI_SW_TX_REQ_CMDID, cmd, total, WMI_SW_TX_COMPLETE_EVENTID, &evt, sizeof(evt), 2000); if (rc == 0) tx_status = !evt.evt.status;