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).
// 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)
{
if (iscntrl(c))
return true;
+ if (c >= 0x80 && c <= 0x9F)
+ return true;
}
return false;