125 lines
3.5 KiB
Diff
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
|
|
|