From 19d725da3c61d4785aec08766408ce32a81a0a0d Mon Sep 17 00:00:00 2001 From: KhaelK-Praetorian Date: Tue, 10 Mar 2026 22:04:55 -0500 Subject: [PATCH] strchriscntrl: reject C1 control bytes (0x80-0x9F) glibc's iscntrl() does not classify C1 control bytes as control characters in any locale. The iscntrl() check was added as part of the fix for CVE-2023-29383, which blocked C0 control characters, but C1 bytes were never considered. The byte 0x9B is the C1 encoding of CSI (Control Sequence Introducer), equivalent to ESC [. On terminals that interpret C1 codes (VTE-based terminals such as GNOME Terminal, Tilix, Terminator, XFCE Terminal), an attacker can inject terminal escape sequences into GECOS fields via chfn. This allows visual spoofing of /etc/passwd output, for example making a user's UID appear as 0 when viewed with cat. Explicitly check for bytes in the 0x80-0x9F range in strchriscntrl() so that valid_field() returns -1 (rejected) instead of 1 (non-ASCII warning) for these bytes. Before (unpatched chfn accepts C1 bytes and writes them to /etc/passwd): $ cat /etc/passwd | grep ubuntu ubuntu:x:1000:1000:ubuntu,,,:/home/ubuntu:/bin/bash $ chfn -r $'\xc2\x9b17D0\xc2\x9b3P\xc2\x9b1C0\xc2\x9b3P\xc2\x9b1Croot\xc2\x9b8m' Password: chfn: room number with non-ASCII characters: [...] $ cat /etc/passwd | grep ubuntu ubuntu:x:0:0:root,,:/home/ubuntu:/bin/bash After (patched chfn rejects C1 bytes, /etc/passwd unchanged): $ chfn -r $'\xc2\x9b17D0\xc2\x9b3P\xc2\x9b1C0\xc2\x9b3P\xc2\x9b1Croot\xc2\x9b8m' Password: chfn: invalid room number: [...] $ cat /etc/passwd | grep ubuntu ubuntu:x:1000:1000:ubuntu,,,:/home/ubuntu:/bin/bash Tested on Shadow 4.17.4-2ubuntu2 (Ubuntu 25.10, aarch64). --- lib/string/ctype/strchrisascii/strchriscntrl.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/string/ctype/strchrisascii/strchriscntrl.h b/lib/string/ctype/strchrisascii/strchriscntrl.h index a1eaca371..aefa7c9cf 100644 --- a/lib/string/ctype/strchrisascii/strchriscntrl.h +++ b/lib/string/ctype/strchrisascii/strchriscntrl.h @@ -18,7 +18,10 @@ inline bool strchriscntrl(const char *s); // string character is [:cntrl:] -// Return true if any iscntrl(3) character is found in the string. +// Return true if any control character is found in the string. +// Check both C0 (0x00-0x1F, 0x7F) via iscntrl(3) and C1 (0x80-0x9F) +// explicitly, since glibc's iscntrl() does not classify C1 bytes as +// control characters in any locale. inline bool strchriscntrl(const char *s) { @@ -27,6 +30,8 @@ strchriscntrl(const char *s) if (iscntrl(c)) return true; + if (c >= 0x80 && c <= 0x9F) + return true; } return false; -- 2.47.3