Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH BlueZ] adapter/advertising: fix mgmt endian bug
@ 2026-09-02 14:00 Nicolas Thibert
  2026-09-02 14:21 ` Luiz Augusto von Dentz
  2026-09-02 17:16 ` [BlueZ] " bluez.test.bot
  0 siblings, 2 replies; 6+ messages in thread
From: Nicolas Thibert @ 2026-09-02 14:00 UTC (permalink / raw)
  To: linux-bluetooth

Several places in adapter.c and advertising.c write a native-endian
value directly into a __le16/__le32 field of an mgmt command struct,
skipping the htobs()/htobl() conversion used everywhere else in this
codebase for the same purpose. This is a no-op on little-endian hosts
(where htobs()/htobl() are themselves no-ops), which is why it has
gone unnoticed, but corrupts the value on big-endian hosts.

Confirmed live on MIPS big-endian (OpenWrt/ath79): set_blocked_keys()
sends key_count=2 (2 blocked keys), which the kernel's
__le16_to_cpu() correctly interprets as 512 (0x0002 byte-swapped is
0x0200) since the wire bytes were never actually swapped to little-
endian on the way out -- producing "expected 8706 bytes, got 36
bytes" / "Failed to set blocked keys: Invalid Parameters" errors in
dmesg/bluetoothd logs.

add_advertising()'s cp->duration and refresh_extended_adv()'s
cp.duration/cp.min_interval/cp.max_interval have the identical bug. It
is inert with bluetoothctl's default (0) duration/interval values (0
byte-swapped is still 0), but A/B tested live (patch removed vs.
applied, bluetoothctl's advertise submenu "interval 100 100" set
explicitly) confirms this is not just a theoretical correctness fix:
with the bug present, the device stops being discoverable by a real
BLE scanner the moment a non-default interval is requested, and
becomes discoverable again immediately once patched. Any application
that sets an explicit advertising interval or duration hits this.
---
src/adapter.c     |  5 +++--
src/advertising.c |  6 +++---
2 files changed, 6 insertions(+), 5 deletions(-)

--- a/src/adapter.c
+++ b/src/adapter.c
@@ -9938,10 +9938,11 @@ static bool set_blocked_keys(struct btd_
sizeof(blocked_keys)] = { 0 };
struct mgmt_cp_set_blocked_keys *cp =
(struct mgmt_cp_set_blocked_keys *)buffer;
+     const uint16_t key_count = ARRAY_SIZE(blocked_keys);
int i;

-     cp->key_count = ARRAY_SIZE(blocked_keys);
-     for (i = 0; i < cp->key_count; ++i) {
+     cp->key_count = htobs(key_count);
+     for (i = 0; i < key_count; ++i) {
cp->keys[i].type = blocked_keys[i].type;
memcpy(cp->keys[i].val, blocked_keys[i].val,
sizeof(cp->keys[i].val));
--- a/src/advertising.c
+++ b/src/advertising.c
@@ -995,7 +995,7 @@ static int refresh_legacy_adv(struct btd

cp->flags = htobl(flags);
cp->instance = client->instance;
-     cp->duration = client->duration;
+     cp->duration = htobs(client->duration);
cp->adv_data_len = adv_data_len;
cp->scan_rsp_len = scan_rsp_len;
memcpy(cp->data, adv_data, adv_data_len);
@@ -1046,13 +1046,13 @@ static int refresh_extended_adv(struct b
*/
if (client->duration) {
-             cp.duration = client->duration;
+             cp.duration = htobs(client->duration);
flags |= MGMT_ADV_PARAM_DURATION;
}

if (client->min_interval && client->max_interval) {
-             cp.min_interval = client->min_interval;
-             cp.max_interval = client->max_interval;
+             cp.min_interval = htobl(client->min_interval);
+             cp.max_interval = htobl(client->max_interval);
flags |= MGMT_ADV_PARAM_INTERVALS;
}

--
2.34.1

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH BlueZ] adapter/advertising: fix mgmt endian bug
  2026-09-02 14:00 [PATCH BlueZ] adapter/advertising: fix mgmt endian bug Nicolas Thibert
@ 2026-09-02 14:21 ` Luiz Augusto von Dentz
  2026-09-02 16:07   ` Nicolas Thibert
  2026-09-02 17:16 ` [BlueZ] " bluez.test.bot
  1 sibling, 1 reply; 6+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-02 14:21 UTC (permalink / raw)
  To: Nicolas Thibert; +Cc: linux-bluetooth

Hi Nicolas,

On Wed, Sep 2, 2026 at 10:19 AM Nicolas Thibert <nithibert@gmail.com> wrote:
>
> Several places in adapter.c and advertising.c write a native-endian
> value directly into a __le16/__le32 field of an mgmt command struct,
> skipping the htobs()/htobl() conversion used everywhere else in this
> codebase for the same purpose. This is a no-op on little-endian hosts
> (where htobs()/htobl() are themselves no-ops), which is why it has
> gone unnoticed, but corrupts the value on big-endian hosts.
>
> Confirmed live on MIPS big-endian (OpenWrt/ath79): set_blocked_keys()
> sends key_count=2 (2 blocked keys), which the kernel's
> __le16_to_cpu() correctly interprets as 512 (0x0002 byte-swapped is
> 0x0200) since the wire bytes were never actually swapped to little-
> endian on the way out -- producing "expected 8706 bytes, got 36
> bytes" / "Failed to set blocked keys: Invalid Parameters" errors in
> dmesg/bluetoothd logs.
>
> add_advertising()'s cp->duration and refresh_extended_adv()'s
> cp.duration/cp.min_interval/cp.max_interval have the identical bug. It
> is inert with bluetoothctl's default (0) duration/interval values (0
> byte-swapped is still 0), but A/B tested live (patch removed vs.
> applied, bluetoothctl's advertise submenu "interval 100 100" set
> explicitly) confirms this is not just a theoretical correctness fix:
> with the bug present, the device stops being discoverable by a real
> BLE scanner the moment a non-default interval is requested, and
> becomes discoverable again immediately once patched. Any application
> that sets an explicit advertising interval or duration hits this.
> ---
> src/adapter.c     |  5 +++--
> src/advertising.c |  6 +++---
> 2 files changed, 6 insertions(+), 5 deletions(-)
>
> --- a/src/adapter.c
> +++ b/src/adapter.c
> @@ -9938,10 +9938,11 @@ static bool set_blocked_keys(struct btd_
> sizeof(blocked_keys)] = { 0 };
> struct mgmt_cp_set_blocked_keys *cp =
> (struct mgmt_cp_set_blocked_keys *)buffer;
> +     const uint16_t key_count = ARRAY_SIZE(blocked_keys);
> int i;
>
> -     cp->key_count = ARRAY_SIZE(blocked_keys);
> -     for (i = 0; i < cp->key_count; ++i) {
> +     cp->key_count = htobs(key_count);
> +     for (i = 0; i < key_count; ++i) {
> cp->keys[i].type = blocked_keys[i].type;
> memcpy(cp->keys[i].val, blocked_keys[i].val,
> sizeof(cp->keys[i].val));
> --- a/src/advertising.c
> +++ b/src/advertising.c
> @@ -995,7 +995,7 @@ static int refresh_legacy_adv(struct btd
>
> cp->flags = htobl(flags);
> cp->instance = client->instance;
> -     cp->duration = client->duration;
> +     cp->duration = htobs(client->duration);
> cp->adv_data_len = adv_data_len;
> cp->scan_rsp_len = scan_rsp_len;
> memcpy(cp->data, adv_data, adv_data_len);
> @@ -1046,13 +1046,13 @@ static int refresh_extended_adv(struct b
> */
> if (client->duration) {
> -             cp.duration = client->duration;
> +             cp.duration = htobs(client->duration);
> flags |= MGMT_ADV_PARAM_DURATION;
> }
>
> if (client->min_interval && client->max_interval) {
> -             cp.min_interval = client->min_interval;
> -             cp.max_interval = client->max_interval;
> +             cp.min_interval = htobl(client->min_interval);
> +             cp.max_interval = htobl(client->max_interval);

Use cpu_to_le** instead of htobl.

> flags |= MGMT_ADV_PARAM_INTERVALS;
> }
>
> --
> 2.34.1
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH BlueZ] adapter/advertising: fix mgmt endian bug
  2026-09-02 14:21 ` Luiz Augusto von Dentz
@ 2026-09-02 16:07   ` Nicolas Thibert
  2026-09-03  7:27     ` Nicolas Thibert
  0 siblings, 1 reply; 6+ messages in thread
From: Nicolas Thibert @ 2026-09-02 16:07 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hi Luiz,
Ty for your answer, here is a correction
Regards,
Nicolas

Several places in adapter.c and advertising.c write a native-endian
value directly into a __le16/__le32 field of an mgmt command struct,
skipping the cpu_to_le16()/cpu_to_le32() conversion used everywhere
else in this codebase for the same purpose. This is a no-op on
little-endian hosts (where cpu_to_le16()/cpu_to_le32() are themselves
no-ops), which is why it has gone unnoticed, but corrupts the value on
big-endian hosts.

Confirmed live on MIPS big-endian (OpenWrt/ath79): set_blocked_keys()
sends key_count=2 (2 blocked keys), which the kernel's
__le16_to_cpu() correctly interprets as 512 (0x0002 byte-swapped is
0x0200) since the wire bytes were never actually swapped to little-
endian on the way out -- producing "expected 8706 bytes, got 36
bytes" / "Failed to set blocked keys: Invalid Parameters" errors in
dmesg/bluetoothd logs.

add_advertising()'s cp->duration and refresh_extended_adv()'s
cp.duration/cp.min_interval/cp.max_interval have the identical bug. It
is inert with bluetoothctl's default (0) duration/interval values (0
byte-swapped is still 0), but A/B tested live (patch removed vs.
applied, bluetoothctl's advertise submenu "interval 100 100" set
explicitly) confirms this is not just a theoretical correctness fix:
with the bug present, the device stops being discoverable by a real
BLE scanner the moment a non-default interval is requested, and
becomes discoverable again immediately once patched. Any application
that sets an explicit advertising interval or duration hits this.

v2: use cpu_to_le16()/cpu_to_le32() instead of htobs()/htobl(), as
requested by Luiz. Retested live on the same MIPS big-endian board
(blocked keys + explicit advertising interval): behaves identically
to the v1 fix, no regressions.
---
src/adapter.c     |  5 +++--
src/advertising.c |  6 +++---
2 files changed, 6 insertions(+), 5 deletions(-)

--- a/src/adapter.c
+++ b/src/adapter.c
@@ -9938,10 +9938,11 @@ static bool set_blocked_keys(struct btd_
sizeof(blocked_keys)] = { 0 };
struct mgmt_cp_set_blocked_keys *cp =
(struct mgmt_cp_set_blocked_keys *)buffer;
+     const uint16_t key_count = ARRAY_SIZE(blocked_keys);
int i;

-     cp->key_count = ARRAY_SIZE(blocked_keys);
-     for (i = 0; i < cp->key_count; ++i) {
+     cp->key_count = cpu_to_le16(key_count);
+     for (i = 0; i < key_count; ++i) {
cp->keys[i].type = blocked_keys[i].type;
memcpy(cp->keys[i].val, blocked_keys[i].val,
sizeof(cp->keys[i].val));
+     for (i = 0; i < key_count; ++i) {
cp->keys[i].type = blocked_keys[i].type;
memcpy(cp->keys[i].val, blocked_keys[i].val,
sizeof(cp->keys[i].val));
--- a/src/advertising.c
+++ b/src/advertising.c
memcpy(cp->keys[i].val, blocked_keys[i].val,
sizeof(cp->keys[i].val));
--- a/src/advertising.c
+++ b/src/advertising.c
@@ -995,7 +995,7 @@ static int refresh_legacy_adv(struct btd

cp->flags = htobl(flags);
cp->instance = client->instance;
-     cp->duration = client->duration;
+     cp->duration = cpu_to_le16(client->duration);
cp->adv_data_len = adv_data_len;
cp->scan_rsp_len = scan_rsp_len;
memcpy(cp->data, adv_data, adv_data_len);
@@ -1046,13 +1046,13 @@ static int refresh_extended_adv(struct b
*/

if (client->duration) {
-             cp.duration = client->duration;
+             cp.duration = cpu_to_le16(client->duration);
flags |= MGMT_ADV_PARAM_DURATION;
}

if (client->min_interval && client->max_interval) {
-             cp.min_interval = client->min_interval;
-             cp.max_interval = client->max_interval;
+             cp.min_interval = cpu_to_le32(client->min_interval);
+             cp.max_interval = cpu_to_le32(client->max_interval);
flags |= MGMT_ADV_PARAM_INTERVALS;
}

--
2.34.1

Le mer. 2 sept. 2026 à 16:21, Luiz Augusto von Dentz
<luiz.dentz@gmail.com> a écrit :
>
> Hi Nicolas,
>
> On Wed, Sep 2, 2026 at 10:19 AM Nicolas Thibert <nithibert@gmail.com> wrote:
> >
> > Several places in adapter.c and advertising.c write a native-endian
> > value directly into a __le16/__le32 field of an mgmt command struct,
> > skipping the htobs()/htobl() conversion used everywhere else in this
> > codebase for the same purpose. This is a no-op on little-endian hosts
> > (where htobs()/htobl() are themselves no-ops), which is why it has
> > gone unnoticed, but corrupts the value on big-endian hosts.
> >
> > Confirmed live on MIPS big-endian (OpenWrt/ath79): set_blocked_keys()
> > sends key_count=2 (2 blocked keys), which the kernel's
> > __le16_to_cpu() correctly interprets as 512 (0x0002 byte-swapped is
> > 0x0200) since the wire bytes were never actually swapped to little-
> > endian on the way out -- producing "expected 8706 bytes, got 36
> > bytes" / "Failed to set blocked keys: Invalid Parameters" errors in
> > dmesg/bluetoothd logs.
> >
> > add_advertising()'s cp->duration and refresh_extended_adv()'s
> > cp.duration/cp.min_interval/cp.max_interval have the identical bug. It
> > is inert with bluetoothctl's default (0) duration/interval values (0
> > byte-swapped is still 0), but A/B tested live (patch removed vs.
> > applied, bluetoothctl's advertise submenu "interval 100 100" set
> > explicitly) confirms this is not just a theoretical correctness fix:
> > with the bug present, the device stops being discoverable by a real
> > BLE scanner the moment a non-default interval is requested, and
> > becomes discoverable again immediately once patched. Any application
> > that sets an explicit advertising interval or duration hits this.
> > ---
> > src/adapter.c     |  5 +++--
> > src/advertising.c |  6 +++---
> > 2 files changed, 6 insertions(+), 5 deletions(-)
> >
> > --- a/src/adapter.c
> > +++ b/src/adapter.c
> > @@ -9938,10 +9938,11 @@ static bool set_blocked_keys(struct btd_
> > sizeof(blocked_keys)] = { 0 };
> > struct mgmt_cp_set_blocked_keys *cp =
> > (struct mgmt_cp_set_blocked_keys *)buffer;
> > +     const uint16_t key_count = ARRAY_SIZE(blocked_keys);
> > int i;
> >
> > -     cp->key_count = ARRAY_SIZE(blocked_keys);
> > -     for (i = 0; i < cp->key_count; ++i) {
> > +     cp->key_count = htobs(key_count);
> > +     for (i = 0; i < key_count; ++i) {
> > cp->keys[i].type = blocked_keys[i].type;
> > memcpy(cp->keys[i].val, blocked_keys[i].val,
> > sizeof(cp->keys[i].val));
> > --- a/src/advertising.c
> > +++ b/src/advertising.c
> > @@ -995,7 +995,7 @@ static int refresh_legacy_adv(struct btd
> >
> > cp->flags = htobl(flags);
> > cp->instance = client->instance;
> > -     cp->duration = client->duration;
> > +     cp->duration = htobs(client->duration);
> > cp->adv_data_len = adv_data_len;
> > cp->scan_rsp_len = scan_rsp_len;
> > memcpy(cp->data, adv_data, adv_data_len);
> > @@ -1046,13 +1046,13 @@ static int refresh_extended_adv(struct b
> > */
> > if (client->duration) {
> > -             cp.duration = client->duration;
> > +             cp.duration = htobs(client->duration);
> > flags |= MGMT_ADV_PARAM_DURATION;
> > }
> >
> > if (client->min_interval && client->max_interval) {
> > -             cp.min_interval = client->min_interval;
> > -             cp.max_interval = client->max_interval;
> > +             cp.min_interval = htobl(client->min_interval);
> > +             cp.max_interval = htobl(client->max_interval);
>
> Use cpu_to_le** instead of htobl.
>
> > flags |= MGMT_ADV_PARAM_INTERVALS;
> > }
> >
> > --
> > 2.34.1
> >
>
>
> --
> Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 6+ messages in thread

* RE: [BlueZ] adapter/advertising: fix mgmt endian bug
  2026-09-02 14:00 [PATCH BlueZ] adapter/advertising: fix mgmt endian bug Nicolas Thibert
  2026-09-02 14:21 ` Luiz Augusto von Dentz
@ 2026-09-02 17:16 ` bluez.test.bot
  1 sibling, 0 replies; 6+ messages in thread
From: bluez.test.bot @ 2026-09-02 17:16 UTC (permalink / raw)
  To: linux-bluetooth, nithibert

[-- Attachment #1: Type: text/plain, Size: 477 bytes --]

This is an automated email and please do not reply to this email.

Dear Submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
While preparing the CI tests, the patches you submitted couldn't be applied to the current HEAD of the repository.

----- Output -----

error: corrupt patch at line 9
hint: Use 'git am --show-current-patch' to see the failed patch

Please resolve the issue and submit the patches again.


---
Regards,
Linux Bluetooth


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH BlueZ] adapter/advertising: fix mgmt endian bug
  2026-09-02 16:07   ` Nicolas Thibert
@ 2026-09-03  7:27     ` Nicolas Thibert
  2026-09-03  8:14       ` Bastien Nocera
  0 siblings, 1 reply; 6+ messages in thread
From: Nicolas Thibert @ 2026-09-03  7:27 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hi Luiz it seems the patch didnt apply correctly, here is a corrected version
Regards
Nicolas

---
Several places in adapter.c and advertising.c write a native-endian
value directly into a __le16/__le32 field of an mgmt command struct,
skipping the cpu_to_le16()/cpu_to_le32() conversion used everywhere
else in this codebase for the same purpose. This is a no-op on
little-endian hosts (where cpu_to_le16()/cpu_to_le32() are themselves
no-ops), which is why it has gone unnoticed, but corrupts the value on
big-endian hosts.

Confirmed live on MIPS big-endian (OpenWrt/ath79): set_blocked_keys()
sends key_count=2 (2 blocked keys), which the kernel's
__le16_to_cpu() correctly interprets as 512 (0x0002 byte-swapped is
0x0200) since the wire bytes were never actually swapped to little-
endian on the way out -- producing "expected 8706 bytes, got 36
bytes" / "Failed to set blocked keys: Invalid Parameters" errors in
dmesg/bluetoothd logs.

add_advertising()'s cp->duration and refresh_extended_adv()'s
cp.duration/cp.min_interval/cp.max_interval have the identical bug. It
is inert with bluetoothctl's default (0) duration/interval values (0
byte-swapped is still 0), but A/B tested live (patch removed vs.
applied, bluetoothctl's advertise submenu "interval 100 100" set
explicitly) confirms this is not just a theoretical correctness fix:
with the bug present, the device stops being discoverable by a real
BLE scanner the moment a non-default interval is requested, and
becomes discoverable again immediately once patched. Any application
that sets an explicit advertising interval or duration hits this.

v2: use cpu_to_le16()/cpu_to_le32() instead of htobs()/htobl(), as
requested by Luiz. Retested live on the same MIPS big-endian board
(blocked keys + explicit advertising interval): behaves identically
to the v1 fix, no regressions.
---
src/adapter.c     | 5 +++--
src/advertising.c | 8 ++++----
2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/src/adapter.c b/src/adapter.c
index 7390ceeee..edbdb53b5 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -10219,10 +10219,11 @@ static bool set_blocked_keys(struct
btd_adapter *adapter)
sizeof(blocked_keys)] = { 0 };
struct mgmt_cp_set_blocked_keys *cp =
(struct mgmt_cp_set_blocked_keys *)buffer;
+     const uint16_t key_count = ARRAY_SIZE(blocked_keys);
int i;

-     cp->key_count = ARRAY_SIZE(blocked_keys);
-     for (i = 0; i < cp->key_count; ++i) {
+     cp->key_count = cpu_to_le16(key_count);
+     for (i = 0; i < key_count; ++i) {
cp->keys[i].type = blocked_keys[i].type;
memcpy(cp->keys[i].val, blocked_keys[i].val,
sizeof(cp->keys[i].val));
diff --git a/src/advertising.c b/src/advertising.c
index 1ed09c902..3f70fb4e9 100644
--- a/src/advertising.c
+++ b/src/advertising.c
@@ -1042,7 +1042,7 @@ static int refresh_legacy_adv(struct
btd_adv_client *client,

cp->flags = htobl(flags);
cp->instance = client->instance;
-     cp->duration = client->duration;
+     cp->duration = cpu_to_le16(client->duration);
cp->adv_data_len = adv_data_len;
cp->scan_rsp_len = scan_rsp_len;
memcpy(cp->data, adv_data, adv_data_len);
@@ -1093,13 +1093,13 @@ static int refresh_extended_adv(struct
btd_adv_client *client,
*/

if (client->duration) {
-             cp.duration = client->duration;
+             cp.duration = cpu_to_le16(client->duration);
flags |= MGMT_ADV_PARAM_DURATION;
}

if (client->min_interval && client->max_interval) {
-             cp.min_interval = client->min_interval;
-             cp.max_interval = client->max_interval;
+             cp.min_interval = cpu_to_le32(client->min_interval);
+             cp.max_interval = cpu_to_le32(client->max_interval);
flags |= MGMT_ADV_PARAM_INTERVALS;
}

--
2.34.1

Le mer. 2 sept. 2026 à 18:07, Nicolas Thibert <nithibert@gmail.com> a écrit :
>
> Hi Luiz,
> Ty for your answer, here is a correction
> Regards,
> Nicolas
>
> Several places in adapter.c and advertising.c write a native-endian
> value directly into a __le16/__le32 field of an mgmt command struct,
> skipping the cpu_to_le16()/cpu_to_le32() conversion used everywhere
> else in this codebase for the same purpose. This is a no-op on
> little-endian hosts (where cpu_to_le16()/cpu_to_le32() are themselves
> no-ops), which is why it has gone unnoticed, but corrupts the value on
> big-endian hosts.
>
> Confirmed live on MIPS big-endian (OpenWrt/ath79): set_blocked_keys()
> sends key_count=2 (2 blocked keys), which the kernel's
> __le16_to_cpu() correctly interprets as 512 (0x0002 byte-swapped is
> 0x0200) since the wire bytes were never actually swapped to little-
> endian on the way out -- producing "expected 8706 bytes, got 36
> bytes" / "Failed to set blocked keys: Invalid Parameters" errors in
> dmesg/bluetoothd logs.
>
> add_advertising()'s cp->duration and refresh_extended_adv()'s
> cp.duration/cp.min_interval/cp.max_interval have the identical bug. It
> is inert with bluetoothctl's default (0) duration/interval values (0
> byte-swapped is still 0), but A/B tested live (patch removed vs.
> applied, bluetoothctl's advertise submenu "interval 100 100" set
> explicitly) confirms this is not just a theoretical correctness fix:
> with the bug present, the device stops being discoverable by a real
> BLE scanner the moment a non-default interval is requested, and
> becomes discoverable again immediately once patched. Any application
> that sets an explicit advertising interval or duration hits this.
>
> v2: use cpu_to_le16()/cpu_to_le32() instead of htobs()/htobl(), as
> requested by Luiz. Retested live on the same MIPS big-endian board
> (blocked keys + explicit advertising interval): behaves identically
> to the v1 fix, no regressions.
> ---
> src/adapter.c     |  5 +++--
> src/advertising.c |  6 +++---
> 2 files changed, 6 insertions(+), 5 deletions(-)
>
> --- a/src/adapter.c
> +++ b/src/adapter.c
> @@ -9938,10 +9938,11 @@ static bool set_blocked_keys(struct btd_
> sizeof(blocked_keys)] = { 0 };
> struct mgmt_cp_set_blocked_keys *cp =
> (struct mgmt_cp_set_blocked_keys *)buffer;
> +     const uint16_t key_count = ARRAY_SIZE(blocked_keys);
> int i;
>
> -     cp->key_count = ARRAY_SIZE(blocked_keys);
> -     for (i = 0; i < cp->key_count; ++i) {
> +     cp->key_count = cpu_to_le16(key_count);
> +     for (i = 0; i < key_count; ++i) {
> cp->keys[i].type = blocked_keys[i].type;
> memcpy(cp->keys[i].val, blocked_keys[i].val,
> sizeof(cp->keys[i].val));
> +     for (i = 0; i < key_count; ++i) {
> cp->keys[i].type = blocked_keys[i].type;
> memcpy(cp->keys[i].val, blocked_keys[i].val,
> sizeof(cp->keys[i].val));
> --- a/src/advertising.c
> +++ b/src/advertising.c
> memcpy(cp->keys[i].val, blocked_keys[i].val,
> sizeof(cp->keys[i].val));
> --- a/src/advertising.c
> +++ b/src/advertising.c
> @@ -995,7 +995,7 @@ static int refresh_legacy_adv(struct btd
>
> cp->flags = htobl(flags);
> cp->instance = client->instance;
> -     cp->duration = client->duration;
> +     cp->duration = cpu_to_le16(client->duration);
> cp->adv_data_len = adv_data_len;
> cp->scan_rsp_len = scan_rsp_len;
> memcpy(cp->data, adv_data, adv_data_len);
> @@ -1046,13 +1046,13 @@ static int refresh_extended_adv(struct b
> */
>
> if (client->duration) {
> -             cp.duration = client->duration;
> +             cp.duration = cpu_to_le16(client->duration);
> flags |= MGMT_ADV_PARAM_DURATION;
> }
>
> if (client->min_interval && client->max_interval) {
> -             cp.min_interval = client->min_interval;
> -             cp.max_interval = client->max_interval;
> +             cp.min_interval = cpu_to_le32(client->min_interval);
> +             cp.max_interval = cpu_to_le32(client->max_interval);
> flags |= MGMT_ADV_PARAM_INTERVALS;
> }
>
> --
> 2.34.1
>
> Le mer. 2 sept. 2026 à 16:21, Luiz Augusto von Dentz
> <luiz.dentz@gmail.com> a écrit :
> >
> > Hi Nicolas,
> >
> > On Wed, Sep 2, 2026 at 10:19 AM Nicolas Thibert <nithibert@gmail.com> wrote:
> > >
> > > Several places in adapter.c and advertising.c write a native-endian
> > > value directly into a __le16/__le32 field of an mgmt command struct,
> > > skipping the htobs()/htobl() conversion used everywhere else in this
> > > codebase for the same purpose. This is a no-op on little-endian hosts
> > > (where htobs()/htobl() are themselves no-ops), which is why it has
> > > gone unnoticed, but corrupts the value on big-endian hosts.
> > >
> > > Confirmed live on MIPS big-endian (OpenWrt/ath79): set_blocked_keys()
> > > sends key_count=2 (2 blocked keys), which the kernel's
> > > __le16_to_cpu() correctly interprets as 512 (0x0002 byte-swapped is
> > > 0x0200) since the wire bytes were never actually swapped to little-
> > > endian on the way out -- producing "expected 8706 bytes, got 36
> > > bytes" / "Failed to set blocked keys: Invalid Parameters" errors in
> > > dmesg/bluetoothd logs.
> > >
> > > add_advertising()'s cp->duration and refresh_extended_adv()'s
> > > cp.duration/cp.min_interval/cp.max_interval have the identical bug. It
> > > is inert with bluetoothctl's default (0) duration/interval values (0
> > > byte-swapped is still 0), but A/B tested live (patch removed vs.
> > > applied, bluetoothctl's advertise submenu "interval 100 100" set
> > > explicitly) confirms this is not just a theoretical correctness fix:
> > > with the bug present, the device stops being discoverable by a real
> > > BLE scanner the moment a non-default interval is requested, and
> > > becomes discoverable again immediately once patched. Any application
> > > that sets an explicit advertising interval or duration hits this.
> > > ---
> > > src/adapter.c     |  5 +++--
> > > src/advertising.c |  6 +++---
> > > 2 files changed, 6 insertions(+), 5 deletions(-)
> > >
> > > --- a/src/adapter.c
> > > +++ b/src/adapter.c
> > > @@ -9938,10 +9938,11 @@ static bool set_blocked_keys(struct btd_
> > > sizeof(blocked_keys)] = { 0 };
> > > struct mgmt_cp_set_blocked_keys *cp =
> > > (struct mgmt_cp_set_blocked_keys *)buffer;
> > > +     const uint16_t key_count = ARRAY_SIZE(blocked_keys);
> > > int i;
> > >
> > > -     cp->key_count = ARRAY_SIZE(blocked_keys);
> > > -     for (i = 0; i < cp->key_count; ++i) {
> > > +     cp->key_count = htobs(key_count);
> > > +     for (i = 0; i < key_count; ++i) {
> > > cp->keys[i].type = blocked_keys[i].type;
> > > memcpy(cp->keys[i].val, blocked_keys[i].val,
> > > sizeof(cp->keys[i].val));
> > > --- a/src/advertising.c
> > > +++ b/src/advertising.c
> > > @@ -995,7 +995,7 @@ static int refresh_legacy_adv(struct btd
> > >
> > > cp->flags = htobl(flags);
> > > cp->instance = client->instance;
> > > -     cp->duration = client->duration;
> > > +     cp->duration = htobs(client->duration);
> > > cp->adv_data_len = adv_data_len;
> > > cp->scan_rsp_len = scan_rsp_len;
> > > memcpy(cp->data, adv_data, adv_data_len);
> > > @@ -1046,13 +1046,13 @@ static int refresh_extended_adv(struct b
> > > */
> > > if (client->duration) {
> > > -             cp.duration = client->duration;
> > > +             cp.duration = htobs(client->duration);
> > > flags |= MGMT_ADV_PARAM_DURATION;
> > > }
> > >
> > > if (client->min_interval && client->max_interval) {
> > > -             cp.min_interval = client->min_interval;
> > > -             cp.max_interval = client->max_interval;
> > > +             cp.min_interval = htobl(client->min_interval);
> > > +             cp.max_interval = htobl(client->max_interval);
> >
> > Use cpu_to_le** instead of htobl.
> >
> > > flags |= MGMT_ADV_PARAM_INTERVALS;
> > > }
> > >
> > > --
> > > 2.34.1
> > >
> >
> >
> > --
> > Luiz Augusto von Dentz

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH BlueZ] adapter/advertising: fix mgmt endian bug
  2026-09-03  7:27     ` Nicolas Thibert
@ 2026-09-03  8:14       ` Bastien Nocera
  0 siblings, 0 replies; 6+ messages in thread
From: Bastien Nocera @ 2026-09-03  8:14 UTC (permalink / raw)
  To: Nicolas Thibert, Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hello Nicolas,

I think you'll want to double-check your setup for sending bluez
patches, as there were multiple problems with it.

The canonical documentation for sending patches to kernel mailing-lists
is here:
https://www.kernel.org/doc/html/v7.0/process/submitting-patches.html

1) The subject prefix mentions BlueZ (good) but not v2
2) The "---" in your message will make git stop trying to process the
rest of the mail, so your commit message and patch will be dropped
3) version changelog should go below the "---" that's below the commit
message, this will be dropped by git so that the changelog only appears
on the mailing-list, not in the git repo.
4) your mail client (or website) mangled the patch

Best solution is either to figure out how to set up "git-send-email" or
use a desktop mail client that can take patches without modifying them
(I know that in Evolution, you can drag'n'drop .patch files on the
drafts folder, and set the whole text to "preformatted")

Cheers

On Thu, 2026-09-03 at 09:27 +0200, Nicolas Thibert wrote:
> Hi Luiz it seems the patch didnt apply correctly, here is a corrected
> version
> Regards
> Nicolas
> 
> ---
> Several places in adapter.c and advertising.c write a native-endian
> value directly into a __le16/__le32 field of an mgmt command struct,
> skipping the cpu_to_le16()/cpu_to_le32() conversion used everywhere
> else in this codebase for the same purpose. This is a no-op on
> little-endian hosts (where cpu_to_le16()/cpu_to_le32() are themselves
> no-ops), which is why it has gone unnoticed, but corrupts the value
> on
> big-endian hosts.
> 
> Confirmed live on MIPS big-endian (OpenWrt/ath79): set_blocked_keys()
> sends key_count=2 (2 blocked keys), which the kernel's
> __le16_to_cpu() correctly interprets as 512 (0x0002 byte-swapped is
> 0x0200) since the wire bytes were never actually swapped to little-
> endian on the way out -- producing "expected 8706 bytes, got 36
> bytes" / "Failed to set blocked keys: Invalid Parameters" errors in
> dmesg/bluetoothd logs.
> 
> add_advertising()'s cp->duration and refresh_extended_adv()'s
> cp.duration/cp.min_interval/cp.max_interval have the identical bug.
> It
> is inert with bluetoothctl's default (0) duration/interval values (0
> byte-swapped is still 0), but A/B tested live (patch removed vs.
> applied, bluetoothctl's advertise submenu "interval 100 100" set
> explicitly) confirms this is not just a theoretical correctness fix:
> with the bug present, the device stops being discoverable by a real
> BLE scanner the moment a non-default interval is requested, and
> becomes discoverable again immediately once patched. Any application
> that sets an explicit advertising interval or duration hits this.
> 
> v2: use cpu_to_le16()/cpu_to_le32() instead of htobs()/htobl(), as
> requested by Luiz. Retested live on the same MIPS big-endian board
> (blocked keys + explicit advertising interval): behaves identically
> to the v1 fix, no regressions.
> ---
> src/adapter.c     | 5 +++--
> src/advertising.c | 8 ++++----
> 2 files changed, 7 insertions(+), 6 deletions(-)
> 
> diff --git a/src/adapter.c b/src/adapter.c
> index 7390ceeee..edbdb53b5 100644
> --- a/src/adapter.c
> +++ b/src/adapter.c
> @@ -10219,10 +10219,11 @@ static bool set_blocked_keys(struct
> btd_adapter *adapter)
> sizeof(blocked_keys)] = { 0 };
> struct mgmt_cp_set_blocked_keys *cp =
> (struct mgmt_cp_set_blocked_keys *)buffer;
> +     const uint16_t key_count = ARRAY_SIZE(blocked_keys);
> int i;
> 
> -     cp->key_count = ARRAY_SIZE(blocked_keys);
> -     for (i = 0; i < cp->key_count; ++i) {
> +     cp->key_count = cpu_to_le16(key_count);
> +     for (i = 0; i < key_count; ++i) {
> cp->keys[i].type = blocked_keys[i].type;
> memcpy(cp->keys[i].val, blocked_keys[i].val,
> sizeof(cp->keys[i].val));
> diff --git a/src/advertising.c b/src/advertising.c
> index 1ed09c902..3f70fb4e9 100644
> --- a/src/advertising.c
> +++ b/src/advertising.c
> @@ -1042,7 +1042,7 @@ static int refresh_legacy_adv(struct
> btd_adv_client *client,
> 
> cp->flags = htobl(flags);
> cp->instance = client->instance;
> -     cp->duration = client->duration;
> +     cp->duration = cpu_to_le16(client->duration);
> cp->adv_data_len = adv_data_len;
> cp->scan_rsp_len = scan_rsp_len;
> memcpy(cp->data, adv_data, adv_data_len);
> @@ -1093,13 +1093,13 @@ static int refresh_extended_adv(struct
> btd_adv_client *client,
> */
> 
> if (client->duration) {
> -             cp.duration = client->duration;
> +             cp.duration = cpu_to_le16(client->duration);
> flags |= MGMT_ADV_PARAM_DURATION;
> }
> 
> if (client->min_interval && client->max_interval) {
> -             cp.min_interval = client->min_interval;
> -             cp.max_interval = client->max_interval;
> +             cp.min_interval = cpu_to_le32(client->min_interval);
> +             cp.max_interval = cpu_to_le32(client->max_interval);
> flags |= MGMT_ADV_PARAM_INTERVALS;
> }
> 
> --
> 2.34.1
> 
> Le mer. 2 sept. 2026 à 18:07, Nicolas Thibert <nithibert@gmail.com> a
> écrit :
> > 
> > Hi Luiz,
> > Ty for your answer, here is a correction
> > Regards,
> > Nicolas
> > 
> > Several places in adapter.c and advertising.c write a native-endian
> > value directly into a __le16/__le32 field of an mgmt command
> > struct,
> > skipping the cpu_to_le16()/cpu_to_le32() conversion used everywhere
> > else in this codebase for the same purpose. This is a no-op on
> > little-endian hosts (where cpu_to_le16()/cpu_to_le32() are
> > themselves
> > no-ops), which is why it has gone unnoticed, but corrupts the value
> > on
> > big-endian hosts.
> > 
> > Confirmed live on MIPS big-endian (OpenWrt/ath79):
> > set_blocked_keys()
> > sends key_count=2 (2 blocked keys), which the kernel's
> > __le16_to_cpu() correctly interprets as 512 (0x0002 byte-swapped is
> > 0x0200) since the wire bytes were never actually swapped to little-
> > endian on the way out -- producing "expected 8706 bytes, got 36
> > bytes" / "Failed to set blocked keys: Invalid Parameters" errors in
> > dmesg/bluetoothd logs.
> > 
> > add_advertising()'s cp->duration and refresh_extended_adv()'s
> > cp.duration/cp.min_interval/cp.max_interval have the identical bug.
> > It
> > is inert with bluetoothctl's default (0) duration/interval values
> > (0
> > byte-swapped is still 0), but A/B tested live (patch removed vs.
> > applied, bluetoothctl's advertise submenu "interval 100 100" set
> > explicitly) confirms this is not just a theoretical correctness
> > fix:
> > with the bug present, the device stops being discoverable by a real
> > BLE scanner the moment a non-default interval is requested, and
> > becomes discoverable again immediately once patched. Any
> > application
> > that sets an explicit advertising interval or duration hits this.
> > 
> > v2: use cpu_to_le16()/cpu_to_le32() instead of htobs()/htobl(), as
> > requested by Luiz. Retested live on the same MIPS big-endian board
> > (blocked keys + explicit advertising interval): behaves identically
> > to the v1 fix, no regressions.
> > ---
> > src/adapter.c     |  5 +++--
> > src/advertising.c |  6 +++---
> > 2 files changed, 6 insertions(+), 5 deletions(-)
> > 
> > --- a/src/adapter.c
> > +++ b/src/adapter.c
> > @@ -9938,10 +9938,11 @@ static bool set_blocked_keys(struct btd_
> > sizeof(blocked_keys)] = { 0 };
> > struct mgmt_cp_set_blocked_keys *cp =
> > (struct mgmt_cp_set_blocked_keys *)buffer;
> > +     const uint16_t key_count = ARRAY_SIZE(blocked_keys);
> > int i;
> > 
> > -     cp->key_count = ARRAY_SIZE(blocked_keys);
> > -     for (i = 0; i < cp->key_count; ++i) {
> > +     cp->key_count = cpu_to_le16(key_count);
> > +     for (i = 0; i < key_count; ++i) {
> > cp->keys[i].type = blocked_keys[i].type;
> > memcpy(cp->keys[i].val, blocked_keys[i].val,
> > sizeof(cp->keys[i].val));
> > +     for (i = 0; i < key_count; ++i) {
> > cp->keys[i].type = blocked_keys[i].type;
> > memcpy(cp->keys[i].val, blocked_keys[i].val,
> > sizeof(cp->keys[i].val));
> > --- a/src/advertising.c
> > +++ b/src/advertising.c
> > memcpy(cp->keys[i].val, blocked_keys[i].val,
> > sizeof(cp->keys[i].val));
> > --- a/src/advertising.c
> > +++ b/src/advertising.c
> > @@ -995,7 +995,7 @@ static int refresh_legacy_adv(struct btd
> > 
> > cp->flags = htobl(flags);
> > cp->instance = client->instance;
> > -     cp->duration = client->duration;
> > +     cp->duration = cpu_to_le16(client->duration);
> > cp->adv_data_len = adv_data_len;
> > cp->scan_rsp_len = scan_rsp_len;
> > memcpy(cp->data, adv_data, adv_data_len);
> > @@ -1046,13 +1046,13 @@ static int refresh_extended_adv(struct b
> > */
> > 
> > if (client->duration) {
> > -             cp.duration = client->duration;
> > +             cp.duration = cpu_to_le16(client->duration);
> > flags |= MGMT_ADV_PARAM_DURATION;
> > }
> > 
> > if (client->min_interval && client->max_interval) {
> > -             cp.min_interval = client->min_interval;
> > -             cp.max_interval = client->max_interval;
> > +             cp.min_interval = cpu_to_le32(client->min_interval);
> > +             cp.max_interval = cpu_to_le32(client->max_interval);
> > flags |= MGMT_ADV_PARAM_INTERVALS;
> > }
> > 
> > --
> > 2.34.1
> > 
> > Le mer. 2 sept. 2026 à 16:21, Luiz Augusto von Dentz
> > <luiz.dentz@gmail.com> a écrit :
> > > 
> > > Hi Nicolas,
> > > 
> > > On Wed, Sep 2, 2026 at 10:19 AM Nicolas Thibert
> > > <nithibert@gmail.com> wrote:
> > > > 
> > > > Several places in adapter.c and advertising.c write a native-
> > > > endian
> > > > value directly into a __le16/__le32 field of an mgmt command
> > > > struct,
> > > > skipping the htobs()/htobl() conversion used everywhere else in
> > > > this
> > > > codebase for the same purpose. This is a no-op on little-endian
> > > > hosts
> > > > (where htobs()/htobl() are themselves no-ops), which is why it
> > > > has
> > > > gone unnoticed, but corrupts the value on big-endian hosts.
> > > > 
> > > > Confirmed live on MIPS big-endian (OpenWrt/ath79):
> > > > set_blocked_keys()
> > > > sends key_count=2 (2 blocked keys), which the kernel's
> > > > __le16_to_cpu() correctly interprets as 512 (0x0002 byte-
> > > > swapped is
> > > > 0x0200) since the wire bytes were never actually swapped to
> > > > little-
> > > > endian on the way out -- producing "expected 8706 bytes, got 36
> > > > bytes" / "Failed to set blocked keys: Invalid Parameters"
> > > > errors in
> > > > dmesg/bluetoothd logs.
> > > > 
> > > > add_advertising()'s cp->duration and refresh_extended_adv()'s
> > > > cp.duration/cp.min_interval/cp.max_interval have the identical
> > > > bug. It
> > > > is inert with bluetoothctl's default (0) duration/interval
> > > > values (0
> > > > byte-swapped is still 0), but A/B tested live (patch removed
> > > > vs.
> > > > applied, bluetoothctl's advertise submenu "interval 100 100"
> > > > set
> > > > explicitly) confirms this is not just a theoretical correctness
> > > > fix:
> > > > with the bug present, the device stops being discoverable by a
> > > > real
> > > > BLE scanner the moment a non-default interval is requested, and
> > > > becomes discoverable again immediately once patched. Any
> > > > application
> > > > that sets an explicit advertising interval or duration hits
> > > > this.
> > > > ---
> > > > src/adapter.c     |  5 +++--
> > > > src/advertising.c |  6 +++---
> > > > 2 files changed, 6 insertions(+), 5 deletions(-)
> > > > 
> > > > --- a/src/adapter.c
> > > > +++ b/src/adapter.c
> > > > @@ -9938,10 +9938,11 @@ static bool set_blocked_keys(struct
> > > > btd_
> > > > sizeof(blocked_keys)] = { 0 };
> > > > struct mgmt_cp_set_blocked_keys *cp =
> > > > (struct mgmt_cp_set_blocked_keys *)buffer;
> > > > +     const uint16_t key_count = ARRAY_SIZE(blocked_keys);
> > > > int i;
> > > > 
> > > > -     cp->key_count = ARRAY_SIZE(blocked_keys);
> > > > -     for (i = 0; i < cp->key_count; ++i) {
> > > > +     cp->key_count = htobs(key_count);
> > > > +     for (i = 0; i < key_count; ++i) {
> > > > cp->keys[i].type = blocked_keys[i].type;
> > > > memcpy(cp->keys[i].val, blocked_keys[i].val,
> > > > sizeof(cp->keys[i].val));
> > > > --- a/src/advertising.c
> > > > +++ b/src/advertising.c
> > > > @@ -995,7 +995,7 @@ static int refresh_legacy_adv(struct btd
> > > > 
> > > > cp->flags = htobl(flags);
> > > > cp->instance = client->instance;
> > > > -     cp->duration = client->duration;
> > > > +     cp->duration = htobs(client->duration);
> > > > cp->adv_data_len = adv_data_len;
> > > > cp->scan_rsp_len = scan_rsp_len;
> > > > memcpy(cp->data, adv_data, adv_data_len);
> > > > @@ -1046,13 +1046,13 @@ static int refresh_extended_adv(struct
> > > > b
> > > > */
> > > > if (client->duration) {
> > > > -             cp.duration = client->duration;
> > > > +             cp.duration = htobs(client->duration);
> > > > flags |= MGMT_ADV_PARAM_DURATION;
> > > > }
> > > > 
> > > > if (client->min_interval && client->max_interval) {
> > > > -             cp.min_interval = client->min_interval;
> > > > -             cp.max_interval = client->max_interval;
> > > > +             cp.min_interval = htobl(client->min_interval);
> > > > +             cp.max_interval = htobl(client->max_interval);
> > > 
> > > Use cpu_to_le** instead of htobl.
> > > 
> > > > flags |= MGMT_ADV_PARAM_INTERVALS;
> > > > }
> > > > 
> > > > --
> > > > 2.34.1
> > > > 
> > > 
> > > 
> > > --
> > > Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-09-03  8:14 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-02 14:00 [PATCH BlueZ] adapter/advertising: fix mgmt endian bug Nicolas Thibert
2026-09-02 14:21 ` Luiz Augusto von Dentz
2026-09-02 16:07   ` Nicolas Thibert
2026-09-03  7:27     ` Nicolas Thibert
2026-09-03  8:14       ` Bastien Nocera
2026-09-02 17:16 ` [BlueZ] " bluez.test.bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox