Linux bluetooth development
 help / color / mirror / Atom feed
From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ v2 05/10] shared/util: Add str2utf8
Date: Thu, 20 Aug 2026 14:30:32 -0400	[thread overview]
Message-ID: <20260820183037.2713973-6-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>

There are five near copies of the same "turn a remote name into a UTF-8
string" helper, in monitor/att.c, profiles/audio/mcp.c, profiles/gap/gas.c,
src/eir.c and src/shared/ad.c, and they do not agree with each other.

Most truncate at the first ill-formed sequence, which throws away the
rest of the name, while the monitor replaces every non-ASCII byte with a
space, which mangles perfectly valid UTF-8 names as soon as one bad byte
appears. Most also copy into a fixed size stack buffer first, which is
what made the missing clamp in src/eir.c a buffer overflow.

Add a single helper they can share. It allocates the result, so there is
no truncation to a buffer size, and replaces each ill-formed sequence
with U+FFFD REPLACEMENT CHARACTER rather than dropping the rest of the
string, matching what g_utf8_make_valid() and the WHATWG Encoding
Standard do.

The result has been checked byte for byte against Python's
bytes.decode('utf-8', errors='replace') over all one and two byte
sequences, a sample of the three byte ones and 200000 random inputs.

Assisted-by: Claude:claude-opus-5
---
 src/shared/util.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 src/shared/util.h |  7 +++++++
 2 files changed, 53 insertions(+)

diff --git a/src/shared/util.c b/src/shared/util.c
index e946214edbb9..8ec9b52e6401 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -2315,3 +2315,49 @@ char *strtoutf8(char *str, size_t len)
 	memset(str + i, 0, len - i);
 	return str;
 }
+
+char *str2utf8(const uint8_t *str, size_t len)
+{
+	char *utf8, *out, *stripped;
+	size_t i = 0;
+
+	if (!str)
+		return NULL;
+
+	/*
+	 * Invalid bytes are replaced with U+FFFD REPLACEMENT CHARACTER, which
+	 * is 3 bytes long, so that is the worst case size of the result.
+	 */
+	utf8 = malloc(len * 3 + 1);
+	if (!utf8)
+		return NULL;
+
+	out = utf8;
+
+	while (i < len) {
+		size_t sublen;
+		size_t size = utf8_seqlen(str + i, len - i, &sublen);
+
+		if (size) {
+			memcpy(out, str + i, size);
+			out += size;
+			i += size;
+			continue;
+		}
+
+		/* Replace the maximal subpart with U+FFFD */
+		*out++ = 0xef;
+		*out++ = 0xbf;
+		*out++ = 0xbd;
+		i += sublen;
+	}
+
+	*out = '\0';
+
+	/* Remove leading and trailing whitespace characters */
+	stripped = strstrip(utf8);
+	if (stripped != utf8)
+		memmove(utf8, stripped, strlen(stripped) + 1);
+
+	return utf8;
+}
diff --git a/src/shared/util.h b/src/shared/util.h
index 562a5af31751..1984fb75f09e 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -143,6 +143,13 @@ bool strisutf8(const char *str, size_t length);
 bool argsisutf8(int argc, char *argv[]);
 char *strtoutf8(char *str, size_t len);
 
+/*
+ * Return a newly allocated, NUL terminated and whitespace stripped UTF-8
+ * copy of the first len bytes of str, with each ill-formed sequence replaced
+ * by U+FFFD REPLACEMENT CHARACTER. The result must be freed with free().
+ */
+char *str2utf8(const uint8_t *str, size_t len);
+
 void *util_malloc(size_t size);
 void *util_memdup(const void *src, size_t size);
 
-- 
2.54.0


  parent reply	other threads:[~2026-08-20 18:30 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 ` Luiz Augusto von Dentz [this message]
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 ` [PATCH BlueZ v2 07/10] Replace the name2utf8 copies with str2utf8 Luiz Augusto von Dentz
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-6-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