libinput/0001-util-sanitize-control-characters-in-str_sanitize.patch
2026-06-12 17:09:08 +10:00

125 lines
3.5 KiB
Diff

From fc2262e1c1847021239065e84f39f15492ef05cc Mon Sep 17 00:00:00 2001
From: Peter Hutterer <peter.hutterer@who-t.net>
Date: Mon, 1 Jun 2026 10:12:29 +1000
Subject: [PATCH] util: sanitize control characters in str_sanitize()
str_sanitize() only escaped '%' characters for format string safety.
Device names from uinput devices can contain arbitrary bytes including
ANSI escape sequences (ESC, 0x1b) and other control characters. When
these strings are included in log messages and printed to a terminal,
the escape sequences are interpreted by the terminal emulator. This
could allow an attacker to manipulate terminal output (change colors,
set window title, clear screen) when an administrator views libinput
logs.
Replace all control characters (0x00-0x1f and 0x7f) with '?' in
addition to the existing '%' escaping. This prevents terminal escape
sequence injection through device names in log output.
Assisted-by: Claude:claude-opus-4-6
(cherry picked from commit 71a2c5cae2a80a1e3bb29e3f3a07ccc3f3de5acb)
Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1489>
---
src/util-strings.h | 30 ++++++++++++++++++++++++------
test/test-utils.c | 10 ++++++++++
2 files changed, 34 insertions(+), 6 deletions(-)
diff --git a/src/util-strings.h b/src/util-strings.h
index 94236c481706..3c6626331a91 100644
--- a/src/util-strings.h
+++ b/src/util-strings.h
@@ -538,34 +538,52 @@ strstartswith(const char *str, const char *prefix)
const char *
safe_basename(const char *filename);
char *
trunkname(const char *filename);
/**
* Return a copy of str with all % converted to %% to make the string
- * acceptable as printf format.
+ * acceptable as printf format, and all non-NUL control characters
+ * (bytes 0x01-0x1f, 0x7f) replaced with '?' to prevent terminal
+ * escape sequence injection. NUL bytes are excluded implicitly
+ * because the string is null-terminated.
*/
static inline char *
str_sanitize(const char *str)
{
if (!str)
return NULL;
- if (!strchr(str, '%'))
- return strdup(str);
-
size_t slen = strlen(str);
slen = min(slen, 512);
+
+ bool needs_sanitization = false;
+ for (size_t i = 0; i < slen; i++) {
+ unsigned char c = str[i];
+ if (c == '%' || c < 0x20 || c == 0x7f) {
+ needs_sanitization = true;
+ break;
+ }
+ }
+ if (!needs_sanitization)
+ return strdup(str);
+
char *sanitized = zalloc(2 * slen + 1);
const char *src = str;
char *dst = sanitized;
for (size_t i = 0; i < slen; i++) {
- if (*src == '%')
+ unsigned char c = *src++;
+ if (c == '%') {
*dst++ = '%';
- *dst++ = *src++;
+ *dst++ = '%';
+ } else if (c < 0x20 || c == 0x7f) {
+ *dst++ = '?';
+ } else {
+ *dst++ = c;
+ }
}
*dst = '\0';
return sanitized;
}
diff --git a/test/test-utils.c b/test/test-utils.c
index 7c938b0af8ac..ee61ba13cc9c 100644
--- a/test/test-utils.c
+++ b/test/test-utils.c
@@ -2081,16 +2081,26 @@ START_TEST(strsanitize_test)
{ "foobar", "foobar" },
{ "", "" },
{ "%", "%%" },
{ "%%%%", "%%%%%%%%" },
{ "x %s", "x %%s" },
{ "x %", "x %%" },
{ "%sx", "%%sx" },
{ "%s%s", "%%s%%s" },
+ { "\t", "?" },
+ { "\n", "?" },
+ { "\r", "?" },
+ { "\x1b[31m", "?[31m" },
+ { "foo\tbar", "foo?bar" },
+ { "foo\nbar", "foo?bar" },
+ { "\x01\x1f\x7f", "???" },
+ { "clean", "clean" },
+ { "a\x1b[0mb", "a?[0mb" },
+ { "%\n", "%%?" },
{ NULL, NULL },
};
/* clang-format on */
for (struct strsanitize_test *t = tests; t->string; t++) {
char *sanitized = str_sanitize(t->string);
litest_assert_str_eq(sanitized, t->expected);
free(sanitized);
--
2.54.0