From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ v2 07/10] Replace the name2utf8 copies with str2utf8
Date: Thu, 20 Aug 2026 14:30:34 -0400 [thread overview]
Message-ID: <20260820183037.2713973-8-luiz.dentz@gmail.com> (raw)
In-Reply-To: <20260820183037.2713973-1-luiz.dentz@gmail.com>
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
monitor/att.c, profiles/audio/mcp.c, profiles/gap/gas.c and src/eir.c
each carried their own name2utf8(), and src/shared/ad.c open coded the
same thing in ad_replace_name(), with none of them agreeing.
Use the shared helper instead, which drops around 120 lines and gives
every caller the same behaviour.
Two things change as a result. The monitor used to replace every
non-ASCII byte with a space as soon as one bad byte appeared, mangling
the valid part of the name, and now only the ill-formed sequences are
replaced. Everything else used to truncate at the first ill-formed
sequence, throwing away the rest of the name, and now keeps it.
The unit/test-eir expectations are updated accordingly, and they show
the improvement: the name that used to be reported as "test परी" is now
reported as "test परी<U+FFFD>्षा invalid".
str2utf8() returns memory from malloc(), so the callers that used
g_free() now use free().
Assisted-by: Claude:claude-opus-5
---
monitor/att.c | 66 ++++++++++++++------------------------------
profiles/audio/mcp.c | 24 ++--------------
profiles/gap/gas.c | 20 ++------------
src/eir.c | 22 ++-------------
src/shared/ad.c | 20 ++++++--------
unit/test-eir.c | 11 +++++---
6 files changed, 42 insertions(+), 121 deletions(-)
diff --git a/monitor/att.c b/monitor/att.c
index 7506dc528e85..44965a2aaf3b 100644
--- a/monitor/att.c
+++ b/monitor/att.c
@@ -15,7 +15,6 @@
#endif
#define _GNU_SOURCE
-#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -2325,40 +2324,15 @@ static void vol_flag_notify(const struct l2cap_frame *frame)
print_vcs_flag(frame);
}
-static char *name2utf8(const uint8_t *name, uint16_t len)
-{
- char utf8_name[HCI_MAX_NAME_LENGTH + 2];
- int i;
-
- if (g_utf8_validate((const char *) name, len, NULL))
- return g_strndup((char *) name, len);
-
- len = MIN(len, sizeof(utf8_name) - 1);
-
- memset(utf8_name, 0, sizeof(utf8_name));
- strncpy(utf8_name, (char *) name, len);
-
- /* Assume ASCII, and replace all non-ASCII with spaces */
- for (i = 0; utf8_name[i] != '\0'; i++) {
- if (!isascii(utf8_name[i]))
- utf8_name[i] = ' ';
- }
-
- /* Remove leading and trailing whitespace characters */
- g_strstrip(utf8_name);
-
- return g_strdup(utf8_name);
-}
-
static void print_mp_name(const struct l2cap_frame *frame)
{
char *name;
- name = name2utf8((uint8_t *)frame->data, frame->size);
+ name = str2utf8(frame->data, frame->size);
print_field(" Media Player Name: %s", name);
- g_free(name);
+ free(name);
}
static void mp_name_read(const struct l2cap_frame *frame)
@@ -2385,11 +2359,11 @@ static void print_track_title(const struct l2cap_frame *frame)
{
char *name;
- name = name2utf8((uint8_t *)frame->data, frame->size);
+ name = str2utf8(frame->data, frame->size);
print_field(" Track Title: %s", name);
- g_free(name);
+ free(name);
}
static void track_title_read(const struct l2cap_frame *frame)
@@ -2520,11 +2494,11 @@ static void print_bearer_name(const struct l2cap_frame *frame)
{
char *name;
- name = name2utf8((uint8_t *)frame->data, frame->size);
+ name = str2utf8(frame->data, frame->size);
print_field(" Bearer Name: %s", name);
- g_free(name);
+ free(name);
}
static void bearer_name_read(const struct l2cap_frame *frame)
@@ -2541,11 +2515,11 @@ static void bearer_uci_read(const struct l2cap_frame *frame)
{
char *name;
- name = name2utf8((uint8_t *)frame->data, frame->size);
+ name = str2utf8(frame->data, frame->size);
print_field(" Bearer Uci Name: %s", name);
- g_free(name);
+ free(name);
}
static void print_technology_name(const struct l2cap_frame *frame)
@@ -2612,11 +2586,11 @@ static void print_uri_scheme_list(const struct l2cap_frame *frame)
{
char *name;
- name = name2utf8((uint8_t *)frame->data, frame->size);
+ name = str2utf8(frame->data, frame->size);
print_field(" Uri scheme Name: %s", name);
- g_free(name);
+ free(name);
}
static void bearer_uri_schemes_list_read(const struct l2cap_frame *frame)
@@ -2726,11 +2700,11 @@ static void print_call_list(const struct l2cap_frame *frame)
print_field(" call_flag: 0x%x", call_flag);
- call_uri = name2utf8((uint8_t *)frame->data, frame->size);
+ call_uri = str2utf8(frame->data, frame->size);
print_field(" call_uri: %s", call_uri);
- g_free(call_uri);
+ free(call_uri);
done:
if (frame->size)
@@ -2816,11 +2790,11 @@ static void print_target_uri(const struct l2cap_frame *frame)
print_field(" call_idx: %x", call_idx);
- name = name2utf8((uint8_t *)frame->data, frame->size);
+ name = str2utf8(frame->data, frame->size);
print_field(" Uri: %s", name);
- g_free(name);
+ free(name);
done:
if (frame->size)
@@ -2928,9 +2902,9 @@ static void print_call_cp(const struct l2cap_frame *frame)
break;
case 0x04:
str = "Originate";
- name = name2utf8((uint8_t *)frame->data, frame->size);
+ name = str2utf8(frame->data, frame->size);
print_field(" Operation: %s Uri: %s", str, name);
- g_free(name);
+ free(name);
break;
case 0x05:
str = "Join";
@@ -3124,11 +3098,11 @@ static void print_incom_call(const struct l2cap_frame *frame)
print_field(" Call Index: %u", call_id);
- name = name2utf8((uint8_t *)frame->data, frame->size);
+ name = str2utf8(frame->data, frame->size);
print_field(" call_string: %s", name);
- g_free(name);
+ free(name);
done:
if (frame->size)
@@ -3157,11 +3131,11 @@ static void print_call_friendly_name(const struct l2cap_frame *frame)
print_field(" Call Index: %u", call_id);
- name = name2utf8((uint8_t *)frame->data, frame->size);
+ name = str2utf8(frame->data, frame->size);
print_field(" Friendly Name: %s", name);
- g_free(name);
+ free(name);
done:
if (frame->size)
diff --git a/profiles/audio/mcp.c b/profiles/audio/mcp.c
index 0c2e0de0b156..8adf814e8d73 100644
--- a/profiles/audio/mcp.c
+++ b/profiles/audio/mcp.c
@@ -73,26 +73,6 @@ struct remote_player {
uint8_t playing_order;
};
-static char *name2utf8(const uint8_t *name, uint16_t len)
-{
- char *utf8_name;
-
- utf8_name = malloc(len + 1);
- if (!utf8_name)
- return NULL;
-
- if (len)
- memcpy(utf8_name, name, len);
-
- utf8_name[len] = 0;
- strtoutf8(utf8_name, len);
-
- /* Remove leading and trailing whitespace characters */
- g_strstrip(utf8_name);
-
- return utf8_name;
-}
-
static const char *mcp_status_val_to_string(uint8_t status)
{
switch (status) {
@@ -118,7 +98,7 @@ static void remote_media_player_name(void *data, const uint8_t *value,
struct remote_player *remote = data;
char *name;
- name = name2utf8(value, length);
+ name = str2utf8(value, length);
if (!name)
return;
@@ -145,7 +125,7 @@ static void remote_track_title(void *data, const uint8_t *value,
char *name;
uint16_t len;
- name = name2utf8(value, length);
+ name = str2utf8(value, length);
if (!name)
return;
diff --git a/profiles/gap/gas.c b/profiles/gap/gas.c
index 0f41c9e6c2a5..5184d74e8f07 100644
--- a/profiles/gap/gas.c
+++ b/profiles/gap/gas.c
@@ -66,22 +66,6 @@ static void gas_free(struct gas *gas)
g_free(gas);
}
-static char *name2utf8(const uint8_t *name, uint16_t len)
-{
- char utf8_name[HCI_MAX_NAME_LENGTH + 2];
-
- len = MIN(len, sizeof(utf8_name) - 1);
-
- memset(utf8_name, 0, sizeof(utf8_name));
- strncpy(utf8_name, (char *) name, len);
- strtoutf8(utf8_name, len);
-
- /* Remove leading and trailing whitespace characters */
- g_strstrip(utf8_name);
-
- return g_strdup(utf8_name);
-}
-
static void read_device_name_cb(bool success, uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
@@ -98,13 +82,13 @@ static void read_device_name_cb(bool success, uint8_t att_ecode,
if (!length)
return;
- name = name2utf8(value, length);
+ name = str2utf8(value, length);
DBG("GAP Device Name: %s", name);
btd_device_device_set_name(gas->device, name);
- g_free(name);
+ free(name);
}
static void handle_device_name(struct gas *gas, uint16_t value_handle)
diff --git a/src/eir.c b/src/eir.c
index 4421b1662d65..5c9ebe2af3a3 100644
--- a/src/eir.c
+++ b/src/eir.c
@@ -60,7 +60,7 @@ void eir_data_free(struct eir_data *eir)
{
queue_destroy(eir->services, g_free);
eir->services = NULL;
- g_free(eir->name);
+ free(eir->name);
eir->name = NULL;
free(eir->hash);
eir->hash = NULL;
@@ -133,22 +133,6 @@ static void eir_parse_uuid128(struct eir_data *eir, const uint8_t *data,
}
}
-static char *name2utf8(const uint8_t *name, uint8_t len)
-{
- char utf8_name[HCI_MAX_NAME_LENGTH + 2];
-
- len = MIN(len, HCI_MAX_NAME_LENGTH);
-
- memset(utf8_name, 0, sizeof(utf8_name));
- strncpy(utf8_name, (char *) name, len);
- strtoutf8(utf8_name, len);
-
- /* Remove leading and trailing whitespace characters */
- g_strstrip(utf8_name);
-
- return g_strdup(utf8_name);
-}
-
static void eir_parse_msd(struct eir_data *eir, const uint8_t *data,
uint8_t len)
{
@@ -301,9 +285,9 @@ void eir_parse(struct eir_data *eir, const uint8_t *eir_data, uint8_t eir_len)
while (data_len > 0 && data[data_len - 1] == '\0')
data_len--;
- g_free(eir->name);
+ free(eir->name);
- eir->name = name2utf8(data, data_len);
+ eir->name = str2utf8(data, data_len);
eir->name_complete = eir_data[1] != EIR_NAME_SHORT;
break;
diff --git a/src/shared/ad.c b/src/shared/ad.c
index ebee078500c6..236e719507e4 100644
--- a/src/shared/ad.c
+++ b/src/shared/ad.c
@@ -275,22 +275,18 @@ static bool ad_replace_uuid128(struct bt_ad *ad, struct iovec *iov)
static bool ad_replace_name(struct bt_ad *ad, struct iovec *iov)
{
- char utf8_name[HCI_MAX_NAME_LENGTH + 2];
- size_t len = MIN(iov->iov_len, (size_t) HCI_MAX_NAME_LENGTH);
+ char *utf8_name;
+ bool ret;
- memset(utf8_name, 0, sizeof(utf8_name));
- strncpy(utf8_name, (const char *)iov->iov_base, len);
+ utf8_name = str2utf8(iov->iov_base, iov->iov_len);
+ if (!utf8_name)
+ return false;
- if (strisutf8(utf8_name, len))
- goto done;
+ ret = bt_ad_add_name(ad, utf8_name);
- strtoutf8(utf8_name, len);
+ free(utf8_name);
- /* Remove leading and trailing whitespace characters */
- strstrip(utf8_name);
-
-done:
- return bt_ad_add_name(ad, utf8_name);
+ return ret;
}
static bool ad_replace_uuid16_data(struct bt_ad *ad, struct iovec *iov)
diff --git a/unit/test-eir.c b/unit/test-eir.c
index 326bc899e251..380fcba2f38e 100644
--- a/unit/test-eir.c
+++ b/unit/test-eir.c
@@ -407,7 +407,8 @@ static const unsigned char invalid_utf8_name_data[] = {
static const struct test_data invalid_utf8_name_test = {
.eir_data = invalid_utf8_name_data,
.eir_size = sizeof(invalid_utf8_name_data),
- .name = "test परी",
+ /* The truncated sequence is replaced by U+FFFD, the rest is kept */
+ .name = "test परी" "\xef\xbf\xbd" "्षा invalid",
.name_complete = true,
.tx_power = 127,
};
@@ -435,7 +436,9 @@ static const unsigned char iso_2022_jp_name_data[] = {
static const struct test_data iso_2022_jp_name_test = {
.eir_data = iso_2022_jp_name_data,
.eir_size = sizeof(iso_2022_jp_name_data),
- .name = "test \033$B",
+ /* The 4 JIS bytes are replaced by U+FFFD, the escapes are ASCII */
+ .name = "test \033$B" "\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd"
+ "\033(B OK",
.name_complete = true,
.tx_power = 127,
};
@@ -475,8 +478,8 @@ static void max_name_setup(const void *data)
static unsigned char long_name_data[255];
static char long_name[sizeof(long_name_data) - 2 + 1];
-/* The name does not fit, so it comes back clamped to HCI_MAX_NAME_LENGTH */
-#define LONG_NAME_LEN HCI_MAX_NAME_LENGTH
+/* str2utf8() does not clamp, so the whole name is kept */
+#define LONG_NAME_LEN (sizeof(long_name_data) - 2)
static const struct test_data long_name_test = {
.eir_data = long_name_data,
--
2.54.0
next prev parent reply other threads:[~2026-08-20 18:31 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-20 18:30 [PATCH BlueZ v2 00/10] Replace the name2utf8 copies with str2utf8 Luiz Augusto von Dentz
2026-08-20 18:30 ` [PATCH BlueZ v2 01/10] eir: Fix stack buffer overflow when parsing the remote name Luiz Augusto von Dentz
2026-08-20 20:04 ` Replace the name2utf8 copies with str2utf8 bluez.test.bot
2026-08-20 18:30 ` [PATCH BlueZ v2 02/10] shared/ad: Fix reading past the name that was copied Luiz Augusto von Dentz
2026-08-20 18:30 ` [PATCH BlueZ v2 03/10] unit/test-eir: Add tests for the longest local names Luiz Augusto von Dentz
2026-08-20 18:30 ` [PATCH BlueZ v2 04/10] shared/util: Make strnlenutf8 reject ill-formed sequences Luiz Augusto von Dentz
2026-08-20 18:30 ` [PATCH BlueZ v2 05/10] shared/util: Add str2utf8 Luiz Augusto von Dentz
2026-08-20 18:30 ` [PATCH BlueZ v2 06/10] unit/test-util: Add str2utf8 tests Luiz Augusto von Dentz
2026-08-20 18:30 ` Luiz Augusto von Dentz [this message]
2026-08-20 18:30 ` [PATCH BlueZ v2 08/10] device: Fix the name truncation splitting UTF-8 sequences Luiz Augusto von Dentz
2026-08-20 18:30 ` [PATCH BlueZ v2 09/10] device: Rename btd_device_device_set_name to btd_device_set_name Luiz Augusto von Dentz
2026-08-20 18:30 ` [PATCH BlueZ v2 10/10] unit/test-util: Cover strtoutf8 with the str2utf8 tests Luiz Augusto von Dentz
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260820183037.2713973-8-luiz.dentz@gmail.com \
--to=luiz.dentz@gmail.com \
--cc=linux-bluetooth@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox