From: KhaelK-Praetorian Date: Wed, 11 Mar 2026 03:04:55 +0000 (-0500) Subject: strchriscntrl: reject C1 control bytes (0x80-0x9F) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=19d725da;p=thirdparty%2Fshadow.git 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). --- 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;