[PATCH v5] Bluetooth: hci_event: move wake reason storage into validated event handlers

Oleh Konko posted 1 patch 1 week ago
There is a newer version of this series
net/bluetooth/hci_event.c | 90 ++++++++++++++-------------------------
1 file changed, 31 insertions(+), 59 deletions(-)
[PATCH v5] Bluetooth: hci_event: move wake reason storage into validated event handlers
Posted by Oleh Konko 1 week ago
hci_store_wake_reason() is called from hci_event_packet() immediately
after stripping the HCI event header but before hci_event_func()
enforces the per-event minimum payload length from hci_ev_table.
This means a short HCI event frame can reach bacpy() before any bounds
check runs.

Rather than duplicating skb parsing and per-event length checks inside
hci_store_wake_reason(), move wake-address storage into the individual
event handlers after their existing event-length validation has
succeeded. Convert hci_store_wake_reason() into a small helper that only
stores an already-validated bdaddr while the caller holds hci_dev_lock().
Use the same helper after hci_event_func() with a NULL address to
preserve the existing unexpected-wake fallback semantics when no
validated event handler records a wake address.

Annotate the helper with __must_hold(&hdev->lock) and add
lockdep_assert_held(&hdev->lock) so future call paths keep the lock
contract explicit.

Call the helper from hci_conn_request_evt(), hci_conn_complete_evt(),
hci_le_adv_report_evt(), hci_le_ext_adv_report_evt(), and
hci_le_direct_adv_report_evt().

Fixes: 2f20216c1d6f ("Bluetooth: Emit controller suspend and resume events")
Cc: stable@vger.kernel.org
Signed-off-by: Oleh Konko <security@1seal.org>
---
v5:
- add __must_hold(&hdev->lock) and lockdep_assert_held(&hdev->lock)

 net/bluetooth/hci_event.c | 90 ++++++++++++++-------------------------
 1 file changed, 31 insertions(+), 59 deletions(-)

diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 286529d2e554..cb27037eeef5 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -80,6 +80,10 @@ static void *hci_le_ev_skb_pull(struct hci_dev *hdev, struct sk_buff *skb,
 	return data;
 }
 
+static void hci_store_wake_reason(struct hci_dev *hdev,
+				  const bdaddr_t *bdaddr, u8 addr_type)
+	__must_hold(&hdev->lock);
+
 static u8 hci_cc_inquiry_cancel(struct hci_dev *hdev, void *data,
 				struct sk_buff *skb)
 {
@@ -3111,6 +3115,7 @@ static void hci_conn_complete_evt(struct hci_dev *hdev, void *data,
 	bt_dev_dbg(hdev, "status 0x%2.2x", status);
 
 	hci_dev_lock(hdev);
+	hci_store_wake_reason(hdev, &ev->bdaddr, BDADDR_BREDR);
 
 	/* Check for existing connection:
 	 *
@@ -3274,6 +3279,10 @@ static void hci_conn_request_evt(struct hci_dev *hdev, void *data,
 
 	bt_dev_dbg(hdev, "bdaddr %pMR type 0x%x", &ev->bdaddr, ev->link_type);
 
+	hci_dev_lock(hdev);
+	hci_store_wake_reason(hdev, &ev->bdaddr, BDADDR_BREDR);
+	hci_dev_unlock(hdev);
+
 	/* Reject incoming connection from device with same BD ADDR against
 	 * CVE-2020-26555
 	 */
@@ -6403,6 +6412,8 @@ static void hci_le_adv_report_evt(struct hci_dev *hdev, void *data,
 					info->length + 1))
 			break;
 
+		hci_store_wake_reason(hdev, &info->bdaddr, info->bdaddr_type);
+
 		if (info->length <= max_adv_len(hdev)) {
 			rssi = info->data[info->length];
 			process_adv_report(hdev, info->type, &info->bdaddr,
@@ -6491,6 +6502,8 @@ static void hci_le_ext_adv_report_evt(struct hci_dev *hdev, void *data,
 					info->length))
 			break;
 
+		hci_store_wake_reason(hdev, &info->bdaddr, info->bdaddr_type);
+
 		evt_type = __le16_to_cpu(info->type) & LE_EXT_ADV_EVT_TYPE_MASK;
 		legacy_evt_type = ext_evt_type_to_legacy(hdev, evt_type);
 
@@ -6834,6 +6847,8 @@ static void hci_le_direct_adv_report_evt(struct hci_dev *hdev, void *data,
 	for (i = 0; i < ev->num; i++) {
 		struct hci_ev_le_direct_adv_info *info = &ev->info[i];
 
+		hci_store_wake_reason(hdev, &info->bdaddr, info->bdaddr_type);
+
 		process_adv_report(hdev, info->type, &info->bdaddr,
 				   info->bdaddr_type, &info->direct_addr,
 				   info->direct_addr_type, HCI_ADV_PHY_1M, 0,
@@ -7517,73 +7532,29 @@ static bool hci_get_cmd_complete(struct hci_dev *hdev, u16 opcode,
 	return true;
 }
 
-static void hci_store_wake_reason(struct hci_dev *hdev, u8 event,
-				  struct sk_buff *skb)
+static void hci_store_wake_reason(struct hci_dev *hdev,
+				  const bdaddr_t *bdaddr, u8 addr_type)
+	__must_hold(&hdev->lock)
 {
-	struct hci_ev_le_advertising_info *adv;
-	struct hci_ev_le_direct_adv_info *direct_adv;
-	struct hci_ev_le_ext_adv_info *ext_adv;
-	const struct hci_ev_conn_complete *conn_complete = (void *)skb->data;
-	const struct hci_ev_conn_request *conn_request = (void *)skb->data;
-
-	hci_dev_lock(hdev);
+	lockdep_assert_held(&hdev->lock);
 
 	/* If we are currently suspended and this is the first BT event seen,
 	 * save the wake reason associated with the event.
 	 */
 	if (!hdev->suspended || hdev->wake_reason)
-		goto unlock;
+		return;
+
+	if (!bdaddr) {
+		hdev->wake_reason = MGMT_WAKE_REASON_UNEXPECTED;
+		return;
+	}
 
 	/* Default to remote wake. Values for wake_reason are documented in the
 	 * Bluez mgmt api docs.
 	 */
 	hdev->wake_reason = MGMT_WAKE_REASON_REMOTE_WAKE;
-
-	/* Once configured for remote wakeup, we should only wake up for
-	 * reconnections. It's useful to see which device is waking us up so
-	 * keep track of the bdaddr of the connection event that woke us up.
-	 */
-	if (event == HCI_EV_CONN_REQUEST) {
-		bacpy(&hdev->wake_addr, &conn_request->bdaddr);
-		hdev->wake_addr_type = BDADDR_BREDR;
-	} else if (event == HCI_EV_CONN_COMPLETE) {
-		bacpy(&hdev->wake_addr, &conn_complete->bdaddr);
-		hdev->wake_addr_type = BDADDR_BREDR;
-	} else if (event == HCI_EV_LE_META) {
-		struct hci_ev_le_meta *le_ev = (void *)skb->data;
-		u8 subevent = le_ev->subevent;
-		u8 *ptr = &skb->data[sizeof(*le_ev)];
-		u8 num_reports = *ptr;
-
-		if ((subevent == HCI_EV_LE_ADVERTISING_REPORT ||
-		     subevent == HCI_EV_LE_DIRECT_ADV_REPORT ||
-		     subevent == HCI_EV_LE_EXT_ADV_REPORT) &&
-		    num_reports) {
-			adv = (void *)(ptr + 1);
-			direct_adv = (void *)(ptr + 1);
-			ext_adv = (void *)(ptr + 1);
-
-			switch (subevent) {
-			case HCI_EV_LE_ADVERTISING_REPORT:
-				bacpy(&hdev->wake_addr, &adv->bdaddr);
-				hdev->wake_addr_type = adv->bdaddr_type;
-				break;
-			case HCI_EV_LE_DIRECT_ADV_REPORT:
-				bacpy(&hdev->wake_addr, &direct_adv->bdaddr);
-				hdev->wake_addr_type = direct_adv->bdaddr_type;
-				break;
-			case HCI_EV_LE_EXT_ADV_REPORT:
-				bacpy(&hdev->wake_addr, &ext_adv->bdaddr);
-				hdev->wake_addr_type = ext_adv->bdaddr_type;
-				break;
-			}
-		}
-	} else {
-		hdev->wake_reason = MGMT_WAKE_REASON_UNEXPECTED;
-	}
-
-unlock:
-	hci_dev_unlock(hdev);
+	bacpy(&hdev->wake_addr, bdaddr);
+	hdev->wake_addr_type = addr_type;
 }
 
 #define HCI_EV_VL(_op, _func, _min_len, _max_len) \
@@ -7830,14 +7801,15 @@ void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb)
 
 	skb_pull(skb, HCI_EVENT_HDR_SIZE);
 
-	/* Store wake reason if we're suspended */
-	hci_store_wake_reason(hdev, event, skb);
-
 	bt_dev_dbg(hdev, "event 0x%2.2x", event);
 
 	hci_event_func(hdev, event, skb, &opcode, &status, &req_complete,
 		       &req_complete_skb);
 
+	hci_dev_lock(hdev);
+	hci_store_wake_reason(hdev, NULL, 0);
+	hci_dev_unlock(hdev);
+
 	if (req_complete) {
 		req_complete(hdev, status, opcode);
 	} else if (req_complete_skb) {
-- 
2.50.0

Re: [PATCH v5] Bluetooth: hci_event: move wake reason storage into validated event handlers
Posted by Luiz Augusto von Dentz 1 week ago
Hi Oleh,

On Thu, Mar 26, 2026 at 11:06 AM Oleh Konko <security@1seal.org> wrote:
>
> hci_store_wake_reason() is called from hci_event_packet() immediately
> after stripping the HCI event header but before hci_event_func()
> enforces the per-event minimum payload length from hci_ev_table.
> This means a short HCI event frame can reach bacpy() before any bounds
> check runs.
>
> Rather than duplicating skb parsing and per-event length checks inside
> hci_store_wake_reason(), move wake-address storage into the individual
> event handlers after their existing event-length validation has
> succeeded. Convert hci_store_wake_reason() into a small helper that only
> stores an already-validated bdaddr while the caller holds hci_dev_lock().
> Use the same helper after hci_event_func() with a NULL address to
> preserve the existing unexpected-wake fallback semantics when no
> validated event handler records a wake address.
>
> Annotate the helper with __must_hold(&hdev->lock) and add
> lockdep_assert_held(&hdev->lock) so future call paths keep the lock
> contract explicit.
>
> Call the helper from hci_conn_request_evt(), hci_conn_complete_evt(),
> hci_le_adv_report_evt(), hci_le_ext_adv_report_evt(), and
> hci_le_direct_adv_report_evt().
>
> Fixes: 2f20216c1d6f ("Bluetooth: Emit controller suspend and resume events")
> Cc: stable@vger.kernel.org
> Signed-off-by: Oleh Konko <security@1seal.org>
> ---
> v5:
> - add __must_hold(&hdev->lock) and lockdep_assert_held(&hdev->lock)
>
>  net/bluetooth/hci_event.c | 90 ++++++++++++++-------------------------
>  1 file changed, 31 insertions(+), 59 deletions(-)
>
> diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
> index 286529d2e554..cb27037eeef5 100644
> --- a/net/bluetooth/hci_event.c
> +++ b/net/bluetooth/hci_event.c
> @@ -80,6 +80,10 @@ static void *hci_le_ev_skb_pull(struct hci_dev *hdev, struct sk_buff *skb,
>         return data;
>  }
>
> +static void hci_store_wake_reason(struct hci_dev *hdev,
> +                                 const bdaddr_t *bdaddr, u8 addr_type)
> +       __must_hold(&hdev->lock);
> +
>  static u8 hci_cc_inquiry_cancel(struct hci_dev *hdev, void *data,
>                                 struct sk_buff *skb)
>  {
> @@ -3111,6 +3115,7 @@ static void hci_conn_complete_evt(struct hci_dev *hdev, void *data,
>         bt_dev_dbg(hdev, "status 0x%2.2x", status);
>
>         hci_dev_lock(hdev);
> +       hci_store_wake_reason(hdev, &ev->bdaddr, BDADDR_BREDR);
>
>         /* Check for existing connection:
>          *
> @@ -3274,6 +3279,10 @@ static void hci_conn_request_evt(struct hci_dev *hdev, void *data,
>
>         bt_dev_dbg(hdev, "bdaddr %pMR type 0x%x", &ev->bdaddr, ev->link_type);
>
> +       hci_dev_lock(hdev);
> +       hci_store_wake_reason(hdev, &ev->bdaddr, BDADDR_BREDR);
> +       hci_dev_unlock(hdev);
> +
>         /* Reject incoming connection from device with same BD ADDR against
>          * CVE-2020-26555
>          */
> @@ -6403,6 +6412,8 @@ static void hci_le_adv_report_evt(struct hci_dev *hdev, void *data,
>                                         info->length + 1))
>                         break;
>
> +               hci_store_wake_reason(hdev, &info->bdaddr, info->bdaddr_type);
> +
>                 if (info->length <= max_adv_len(hdev)) {
>                         rssi = info->data[info->length];
>                         process_adv_report(hdev, info->type, &info->bdaddr,
> @@ -6491,6 +6502,8 @@ static void hci_le_ext_adv_report_evt(struct hci_dev *hdev, void *data,
>                                         info->length))
>                         break;
>
> +               hci_store_wake_reason(hdev, &info->bdaddr, info->bdaddr_type);
> +
>                 evt_type = __le16_to_cpu(info->type) & LE_EXT_ADV_EVT_TYPE_MASK;
>                 legacy_evt_type = ext_evt_type_to_legacy(hdev, evt_type);
>
> @@ -6834,6 +6847,8 @@ static void hci_le_direct_adv_report_evt(struct hci_dev *hdev, void *data,
>         for (i = 0; i < ev->num; i++) {
>                 struct hci_ev_le_direct_adv_info *info = &ev->info[i];
>
> +               hci_store_wake_reason(hdev, &info->bdaddr, info->bdaddr_type);
> +
>                 process_adv_report(hdev, info->type, &info->bdaddr,
>                                    info->bdaddr_type, &info->direct_addr,
>                                    info->direct_addr_type, HCI_ADV_PHY_1M, 0,
> @@ -7517,73 +7532,29 @@ static bool hci_get_cmd_complete(struct hci_dev *hdev, u16 opcode,
>         return true;
>  }
>
> -static void hci_store_wake_reason(struct hci_dev *hdev, u8 event,
> -                                 struct sk_buff *skb)
> +static void hci_store_wake_reason(struct hci_dev *hdev,
> +                                 const bdaddr_t *bdaddr, u8 addr_type)
> +       __must_hold(&hdev->lock)
>  {
> -       struct hci_ev_le_advertising_info *adv;
> -       struct hci_ev_le_direct_adv_info *direct_adv;
> -       struct hci_ev_le_ext_adv_info *ext_adv;
> -       const struct hci_ev_conn_complete *conn_complete = (void *)skb->data;
> -       const struct hci_ev_conn_request *conn_request = (void *)skb->data;
> -
> -       hci_dev_lock(hdev);
> +       lockdep_assert_held(&hdev->lock);
>
>         /* If we are currently suspended and this is the first BT event seen,
>          * save the wake reason associated with the event.
>          */
>         if (!hdev->suspended || hdev->wake_reason)
> -               goto unlock;
> +               return;
> +
> +       if (!bdaddr) {
> +               hdev->wake_reason = MGMT_WAKE_REASON_UNEXPECTED;
> +               return;
> +       }
>
>         /* Default to remote wake. Values for wake_reason are documented in the
>          * Bluez mgmt api docs.
>          */
>         hdev->wake_reason = MGMT_WAKE_REASON_REMOTE_WAKE;
> -
> -       /* Once configured for remote wakeup, we should only wake up for
> -        * reconnections. It's useful to see which device is waking us up so
> -        * keep track of the bdaddr of the connection event that woke us up.
> -        */
> -       if (event == HCI_EV_CONN_REQUEST) {
> -               bacpy(&hdev->wake_addr, &conn_request->bdaddr);
> -               hdev->wake_addr_type = BDADDR_BREDR;
> -       } else if (event == HCI_EV_CONN_COMPLETE) {
> -               bacpy(&hdev->wake_addr, &conn_complete->bdaddr);
> -               hdev->wake_addr_type = BDADDR_BREDR;
> -       } else if (event == HCI_EV_LE_META) {
> -               struct hci_ev_le_meta *le_ev = (void *)skb->data;
> -               u8 subevent = le_ev->subevent;
> -               u8 *ptr = &skb->data[sizeof(*le_ev)];
> -               u8 num_reports = *ptr;
> -
> -               if ((subevent == HCI_EV_LE_ADVERTISING_REPORT ||
> -                    subevent == HCI_EV_LE_DIRECT_ADV_REPORT ||
> -                    subevent == HCI_EV_LE_EXT_ADV_REPORT) &&
> -                   num_reports) {
> -                       adv = (void *)(ptr + 1);
> -                       direct_adv = (void *)(ptr + 1);
> -                       ext_adv = (void *)(ptr + 1);
> -
> -                       switch (subevent) {
> -                       case HCI_EV_LE_ADVERTISING_REPORT:
> -                               bacpy(&hdev->wake_addr, &adv->bdaddr);
> -                               hdev->wake_addr_type = adv->bdaddr_type;
> -                               break;
> -                       case HCI_EV_LE_DIRECT_ADV_REPORT:
> -                               bacpy(&hdev->wake_addr, &direct_adv->bdaddr);
> -                               hdev->wake_addr_type = direct_adv->bdaddr_type;
> -                               break;
> -                       case HCI_EV_LE_EXT_ADV_REPORT:
> -                               bacpy(&hdev->wake_addr, &ext_adv->bdaddr);
> -                               hdev->wake_addr_type = ext_adv->bdaddr_type;
> -                               break;
> -                       }
> -               }
> -       } else {
> -               hdev->wake_reason = MGMT_WAKE_REASON_UNEXPECTED;
> -       }
> -
> -unlock:
> -       hci_dev_unlock(hdev);
> +       bacpy(&hdev->wake_addr, bdaddr);
> +       hdev->wake_addr_type = addr_type;
>  }
>
>  #define HCI_EV_VL(_op, _func, _min_len, _max_len) \
> @@ -7830,14 +7801,15 @@ void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb)
>
>         skb_pull(skb, HCI_EVENT_HDR_SIZE);
>
> -       /* Store wake reason if we're suspended */
> -       hci_store_wake_reason(hdev, event, skb);
> -
>         bt_dev_dbg(hdev, "event 0x%2.2x", event);
>
>         hci_event_func(hdev, event, skb, &opcode, &status, &req_complete,
>                        &req_complete_skb);
>
> +       hci_dev_lock(hdev);
> +       hci_store_wake_reason(hdev, NULL, 0);
> +       hci_dev_unlock(hdev);
> +
>         if (req_complete) {
>                 req_complete(hdev, status, opcode);
>         } else if (req_complete_skb) {
> --
> 2.50.0

https://sashiko.dev/#/patchset/f0d72bf42b33441991665b23e293c879.security%401.0.0.127.in-addr.arpa

Seems valid to me, so we probably need to call hci_store_wake_reason
in more event handlers.

-- 
Luiz Augusto von Dentz