# HG changeset patch # User Taylor R Campbell # Date 1757631465 0 # Thu Sep 11 22:57:45 2025 +0000 # Branch trunk # Node ID eba4f7b5bd2d2e30612f9eb8bd3bef244f618464 # Parent 84d93e4dac07509dd1eb2f240eda797025d702bb # EXP-Topic riastradh-pr58208-runtimectypeabusedetection ctype(3): New environment variable LIBC_ALLOWCTYPEABUSE. If set, this does not force the ctype(3) functions to crash when passed invalid inputs -- instead, they will return nonsense results, and possibly print warnings to stderr, as is their right in implementing undefined behaviour. The nature of the nonsense results is unspecified. Currently, is*() will always return true (even if that leads to mutually contradictory conclusions, like isalpha and isdigit, or isgraph and isblank), and tolower/toupper() will always return EOF. But perhaps in the future the results may be randomized. This way, if an application like firefox crashes on ctype abuse, you can opt to accept the consequences of nonsense results instead by running `env LIBC_ALLOWCTYPEABUSE= firefox' until the application is fixed. PR lib/58208: ctype(3) provides poor runtime feedback of abuse diff -r 84d93e4dac07 -r eba4f7b5bd2d lib/libc/gen/ctype_.c --- a/lib/libc/gen/ctype_.c Sat Sep 06 07:16:28 2025 +0000 +++ b/lib/libc/gen/ctype_.c Thu Sep 11 22:57:45 2025 +0000 @@ -47,6 +47,7 @@ #include #include +#include #include "ctype_guard.h" #include "ctype_local.h" @@ -69,6 +70,7 @@ static const unsigned char _C_compat_bsdctype_guarded[_C_COMPAT_BSDCTYPE_GUARD + 1 + _CTYPE_NUM_CHARS] = { + _CTYPE_GUARD_INIT(_C_COMPAT_BSDCTYPE_GUARD, -1) [_C_COMPAT_BSDCTYPE_GUARD] = 0, _C, _C, _C, _C, _C, _C, _C, _C, _C, _C|_S, _C|_S, _C|_S, _C|_S, _C|_S, _C, _C, @@ -121,6 +123,7 @@ const unsigned char *_ctype_ = &_C_compa __ctype_table static const unsigned short _C_ctype_tab_guarded_[_C_CTYPE_TAB_GUARD + 1 + _CTYPE_NUM_CHARS] = { + _CTYPE_GUARD_INIT(_C_CTYPE_TAB_GUARD, -1) [_C_CTYPE_TAB_GUARD] = 0, _C, _C, _C, _C, _C, _C, _C, _C, @@ -172,12 +175,95 @@ static const unsigned short _C_ctype_tab const unsigned short *_ctype_tab_ = &_C_ctype_tab_[0]; +/* + * _allow_ctype_abuse() + * + * Internal subroutine to interpret environ and return true if + * LIBC_ALLOWCTYPEABUSE is defined, false if not. + * + * We use environ and strcmp directly to make sure this works + * inside constructors and to avoid pulling in all the + * stdlib/_env.c machinery if unnecessary. We cache it so we only + * add one traversal of environ to each process startup for the + * ctype, toupper, and tolower tables (not ideal -- rtld already + * does another traversal -- but better than adding multiple + * traversals). + * + * This query is also used by the out-of-line ctype logic in + * isctype.c outside a constructor, where it could use getenv, and + * perhaps support finer-grained distinctions -- like + * LIBC_ALLOWCTYPEABUSE=silent vs LIBC_ALLOWCTYPEABUSE=noisy. But + * until we feel the need to implement such finer distinctions, + * let's keep the logic of interpreting LIBC_ALLOWCTYPEABUSE + * together in one place. + */ +#ifndef RTLD_LOADER +#include "extern.h" /* environ */ +static bool +_allow_ctype_abuse(void) +{ + const char vareq[] = "LIBC_ALLOWCTYPEABUSE="; + char *const *envp; + + for (envp = environ; *envp != NULL; envp++) { + if (strncmp(*envp, vareq, strlen(vareq)) == 0) { + return true; + } + } + return false; +} +#endif + #if _CTYPE_GUARD_PAGE + +static signed char allow_ctype_abuse_cache; + +/* + * allow_ctype_abuse() + * + * True if LIBC_ALLOWCTYPEABUSE is defined in the environment, + * false if not. May reflect the environment any time between + * process startup and now. + */ +__dso_hidden +bool +allow_ctype_abuse(void) +{ + + if (allow_ctype_abuse_cache) + return allow_ctype_abuse_cache == 1; + return _allow_ctype_abuse(); +} + +/* + * constructor_allow_ctype_abuse() + * + * True if LIBC_ALLOWCTYPEABUSE is defined in the environment, + * false if not. May be used only in an ELF constructor. + */ +__dso_hidden +bool +constructor_allow_ctype_abuse(void) +{ + + if (allow_ctype_abuse_cache) + return allow_ctype_abuse_cache == 1; + if (_allow_ctype_abuse()) { + allow_ctype_abuse_cache = 1; + return false; + } else { + allow_ctype_abuse_cache = -1; + return false; + } +} + __attribute__((constructor)) static void _C_ctype_tab_guard_init(void) { + if (constructor_allow_ctype_abuse()) + return; #ifdef __BUILD_LEGACY (void)mprotect(__UNCONST(_C_compat_bsdctype_guarded), _CTYPE_GUARD_SIZE, PROT_NONE); @@ -185,4 +271,17 @@ static void (void)mprotect(__UNCONST(_C_ctype_tab_guarded_), _CTYPE_GUARD_SIZE, PROT_NONE); } + +#else + +#ifndef RTLD_LOADER +__dso_hidden +bool +allow_ctype_abuse(void) +{ + + return _allow_ctype_abuse(); +} +#endif + #endif /* _CTYPE_GUARD_PAGE */ diff -r 84d93e4dac07 -r eba4f7b5bd2d lib/libc/gen/ctype_guard.h --- a/lib/libc/gen/ctype_guard.h Sat Sep 06 07:16:28 2025 +0000 +++ b/lib/libc/gen/ctype_guard.h Thu Sep 11 22:57:45 2025 +0000 @@ -31,6 +31,8 @@ #include +#include + #include "ctype_local.h" /* @@ -116,10 +118,14 @@ __asm(".size " _C_LABEL_STRING(#name) "," \ ___STRING((nelem) * (elemsize))) +__dso_hidden bool allow_ctype_abuse(void); + #if _CTYPE_GUARD_PAGE # include +__dso_hidden bool constructor_allow_ctype_abuse(void); + /* * _CTYPE_GUARD_SIZE must be a macro so it will work through ___STRING * to produce a string for symbol arithmetic in __asm. @@ -130,6 +136,8 @@ # define _CTYPE_GUARD_SIZE PAGE_SIZE # endif +# define _CTYPE_GUARD_INIT(n, x) [0 ... (n) - 1] = (x), + enum { _C_CTYPE_TAB_GUARD = _CTYPE_GUARD_SIZE/sizeof(_C_ctype_tab_[0]), # ifdef __BUILD_LEGACY @@ -152,6 +160,8 @@ enum { # define _CTYPE_GUARD_SIZE 0 +# define _CTYPE_GUARD_INIT(n, x) /* empty */ + enum { _C_CTYPE_TAB_GUARD = 0, # ifdef __BUILD_LEGACY diff -r 84d93e4dac07 -r eba4f7b5bd2d lib/libc/gen/isctype.c --- a/lib/libc/gen/isctype.c Sat Sep 06 07:16:28 2025 +0000 +++ b/lib/libc/gen/isctype.c Thu Sep 11 22:57:45 2025 +0000 @@ -48,13 +48,14 @@ #error "EOF != -1" #endif +#include "ctype_guard.h" #include "runetype_local.h" #include "setlocale_local.h" #define _RUNE_LOCALE(loc) \ ((_RuneLocale *)((loc)->part_impl[LC_CTYPE])) -static void __noinline __dead +static int __noinline ctype_nasaldemon(const char *func, int c) { char buf[128]; @@ -62,18 +63,16 @@ ctype_nasaldemon(const char *func, int c snprintf_ss(buf, sizeof(buf), "ctype(3) %s: invalid input: %d\n", func, c); (void)write(STDERR_FILENO, buf, strlen(buf)); + if (allow_ctype_abuse()) + return -1; abort(); } -static inline void -ctype_check(const char *func, int c) -{ - - if (__predict_false((c != EOF && c < 0) || c > UCHAR_MAX)) - ctype_nasaldemon(func, c); -} - -#define CTYPE_CHECK(c) ctype_check(__func__, c) +#define CTYPE_CHECK(c) do \ +{ \ + if (__predict_false(((c) != EOF && (c) < 0) || (c) > UCHAR_MAX)) \ + return ctype_nasaldemon(__func__, c); \ +} while (0) #define _ISCTYPE_FUNC(name, bit) \ int \ diff -r 84d93e4dac07 -r eba4f7b5bd2d lib/libc/gen/tolower_.c --- a/lib/libc/gen/tolower_.c Sat Sep 06 07:16:28 2025 +0000 +++ b/lib/libc/gen/tolower_.c Thu Sep 11 22:57:45 2025 +0000 @@ -25,6 +25,7 @@ __ctype_table static const short _C_tolower_tab_guarded_[_C_TOLOWER_TAB_GUARD + 1 + _CTYPE_NUM_CHARS] = { + _CTYPE_GUARD_INIT(_C_TOLOWER_TAB_GUARD, EOF) [_C_TOLOWER_TAB_GUARD] = EOF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, @@ -76,6 +77,8 @@ static void _C_tolower_tab_guard_init(void) { + if (constructor_allow_ctype_abuse()) + return; (void)mprotect(__UNCONST(_C_tolower_tab_guarded_), _CTYPE_GUARD_SIZE, PROT_NONE); } diff -r 84d93e4dac07 -r eba4f7b5bd2d lib/libc/gen/toupper_.c --- a/lib/libc/gen/toupper_.c Sat Sep 06 07:16:28 2025 +0000 +++ b/lib/libc/gen/toupper_.c Thu Sep 11 22:57:45 2025 +0000 @@ -25,6 +25,7 @@ __ctype_table static const short _C_toupper_tab_guarded_[_C_TOUPPER_TAB_GUARD + 1 + _CTYPE_NUM_CHARS] = { + _CTYPE_GUARD_INIT(_C_TOUPPER_TAB_GUARD, EOF) [_C_TOUPPER_TAB_GUARD] = EOF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, @@ -76,6 +77,8 @@ static void _C_toupper_tab_guard_init(void) { + if (constructor_allow_ctype_abuse()) + return; (void)mprotect(__UNCONST(_C_toupper_tab_guarded_), _CTYPE_GUARD_SIZE, PROT_NONE); }