]> git.ipfire.org Git - thirdparty/shadow.git/commitdiff
strchriscntrl: reject C1 control bytes (0x80-0x9F)
authorKhaelK-Praetorian <khael.kugler@praetorian.com>
Wed, 11 Mar 2026 03:04:55 +0000 (22:04 -0500)
committerAlejandro Colomar <foss+github@alejandro-colomar.es>
Thu, 12 Mar 2026 23:17:04 +0000 (00:17 +0100)
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

index a1eaca371e65738372d3ab44f1b4d64cee7ab23a..aefa7c9cfce0f4ad50f86eb1acf2a335e974fc13 100644 (file)
@@ -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;