Skip to content

Commit 10ed617

Browse files
Pull up following revision(s) (requested by riastradh in ticket #45):
tests/lib/libc/gen/t_ctype.c: revision 1.12 lib/libc/gen/ctype_.c: revision 1.24 lib/libc/locale/rune.c: revision 1.50 tests/lib/libc/gen/Makefile: revision 1.61 lib/libc/gen/tolower_.c: revision 1.18 lib/libc/gen/isctype.c: revision 1.29 distrib/sets/lists/tests/mi: revision 1.1394 lib/libc/gen/toupper_.c: revision 1.18 lib/libc/gen/ctype_guard.h: revision 1.8 lib/libc/locale/Makefile.inc: revision 1.69 lib/libc/gen/ctype.3: revision 1.32 lib/libc/gen/ctype.3: revision 1.33 distrib/sets/lists/debug/mi: revision 1.486 tests/lib/libc/gen/h_ctype_abuse.c: revision 1.1 tests/lib/libc/gen/h_ctype_abuse.c: revision 1.2 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 ctype(3): Document LIBC_ALLOWCTYPEABUSE. If this is pulled up to netbsd-11, we should tweak the text to make it apply to 11 too. PR lib/58208: ctype(3) provides poor runtime feedback of abuse ctype(3): Fix build of tests on machines with unsigned char. Could maybe phrase this better but this'll do for now. PR lib/58208: ctype(3) provides poor runtime feedback of abuse
1 parent 1840bfa commit 10ed617

13 files changed

Lines changed: 457 additions & 30 deletions

File tree

distrib/sets/lists/debug/mi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $NetBSD: mi,v 1.485 2025/07/17 19:50:39 kre Exp $
1+
# $NetBSD: mi,v 1.485.2.1 2025/10/01 17:41:15 martin Exp $
22
#
33
./etc/mtree/set.debug comp-sys-root
44
./usr/lib comp-sys-usr compatdir
@@ -2026,6 +2026,7 @@
20262026
./usr/libdata/debug/usr/tests/lib/libc/db/t_db_hash_seq.debug tests-lib-debug debug,atf,compattestfile
20272027
./usr/libdata/debug/usr/tests/lib/libc/gen/exect/t_exect.debug tests-kernel-obsolete obsolete
20282028
./usr/libdata/debug/usr/tests/lib/libc/gen/execve/t_execve.debug tests-kernel-tests debug,atf,compattestfile
2029+
./usr/libdata/debug/usr/tests/lib/libc/gen/h_ctype_abuse.debug tests-kernel-tests debug,atf,compattestfile
20292030
./usr/libdata/debug/usr/tests/lib/libc/gen/h_execsig.debug tests-kernel-tests debug,atf,compattestfile
20302031
./usr/libdata/debug/usr/tests/lib/libc/gen/posix_spawn/h_fileactions.debug tests-kernel-tests debug,atf,compattestfile
20312032
./usr/libdata/debug/usr/tests/lib/libc/gen/posix_spawn/h_spawn.debug tests-kernel-tests debug,atf,compattestfile

distrib/sets/lists/tests/mi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $NetBSD: mi,v 1.1387 2025/07/20 12:25:53 joe Exp $
1+
# $NetBSD: mi,v 1.1387.2.1 2025/10/01 17:41:15 martin Exp $
22
#
33
# Note: don't delete entries from here - mark them as "obsolete" instead.
44
./etc/mtree/set.tests tests-sys-root
@@ -3080,6 +3080,7 @@
30803080
./usr/tests/lib/libc/gen/execve/Atffile tests-kernel-tests compattestfile,atf
30813081
./usr/tests/lib/libc/gen/execve/Kyuafile tests-kernel-tests compattestfile,atf,kyua
30823082
./usr/tests/lib/libc/gen/execve/t_execve tests-kernel-tests compattestfile,atf
3083+
./usr/tests/lib/libc/gen/h_ctype_abuse tests-kernel-tests compattestfile,atf
30833084
./usr/tests/lib/libc/gen/h_execsig tests-kernel-tests compattestfile,atf
30843085
./usr/tests/lib/libc/gen/posix_spawn tests-kernel-tests compattestfile,atf
30853086
./usr/tests/lib/libc/gen/posix_spawn/Atffile tests-kernel-tests compattestfile,atf

lib/libc/gen/ctype.3

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.\" $NetBSD: ctype.3,v 1.31 2019/01/15 07:01:01 wiz Exp $
1+
.\" $NetBSD: ctype.3,v 1.31.14.1 2025/10/01 17:41:15 martin Exp $
22
.\"
33
.\" Copyright (c) 1991 Regents of the University of California.
44
.\" All rights reserved.
@@ -30,7 +30,7 @@
3030
.\"
3131
.\" @(#)ctype.3 6.5 (Berkeley) 4/19/91
3232
.\"
33-
.Dd January 15, 2019
33+
.Dd September 14, 2025
3434
.Dt CTYPE 3
3535
.Os
3636
.Sh NAME
@@ -60,6 +60,20 @@ The above functions perform character tests and conversions on the integer
6060
.Pp
6161
See the specific manual pages for information about the
6262
test or conversion performed by each function.
63+
.Sh ENVIRONMENT
64+
In
65+
.Nx 10 ,
66+
the
67+
.Nm
68+
functions will crash with a signal on certain invalid inputs as a
69+
diagnostic aid for applications; see
70+
.Sx CAVEATS .
71+
In
72+
.Nx 11 ,
73+
setting the environment variable
74+
.Ev LIBC_ALLOWCTYPEABUSE
75+
before starting a program will make the functions return nonsense
76+
answers instead of crashing.
6377
.Sh EXAMPLES
6478
To print an upper-case version of a string to stdout,
6579
the following code can be used:

lib/libc/gen/ctype_.c

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* $NetBSD: ctype_.c,v 1.23 2025/03/30 00:07:51 riastradh Exp $ */
1+
/* $NetBSD: ctype_.c,v 1.23.2.1 2025/10/01 17:41:14 martin Exp $ */
22

33
/*
44
* Copyright (c) 1989 The Regents of the University of California.
@@ -39,14 +39,15 @@
3939
#if 0
4040
/*static char *sccsid = "from: @(#)ctype_.c 5.6 (Berkeley) 6/1/90";*/
4141
#else
42-
__RCSID("$NetBSD: ctype_.c,v 1.23 2025/03/30 00:07:51 riastradh Exp $");
42+
__RCSID("$NetBSD: ctype_.c,v 1.23.2.1 2025/10/01 17:41:14 martin Exp $");
4343
#endif
4444
#endif /* LIBC_SCCS and not lint */
4545

4646
#include <sys/ctype_bits.h>
4747
#include <sys/mman.h>
4848

4949
#include <stdio.h>
50+
#include <string.h>
5051

5152
#include "ctype_guard.h"
5253
#include "ctype_local.h"
@@ -69,6 +70,7 @@ __ctype_table
6970
static
7071
const unsigned char _C_compat_bsdctype_guarded[_C_COMPAT_BSDCTYPE_GUARD +
7172
1 + _CTYPE_NUM_CHARS] = {
73+
_CTYPE_GUARD_INIT(_C_COMPAT_BSDCTYPE_GUARD, -1)
7274
[_C_COMPAT_BSDCTYPE_GUARD] = 0,
7375
_C, _C, _C, _C, _C, _C, _C, _C,
7476
_C, _C|_S, _C|_S, _C|_S, _C|_S, _C|_S, _C, _C,
@@ -121,6 +123,7 @@ const unsigned char *_ctype_ = &_C_compat_bsdctype[0];
121123
__ctype_table
122124
static const unsigned short _C_ctype_tab_guarded_[_C_CTYPE_TAB_GUARD +
123125
1 + _CTYPE_NUM_CHARS] = {
126+
_CTYPE_GUARD_INIT(_C_CTYPE_TAB_GUARD, -1)
124127
[_C_CTYPE_TAB_GUARD] = 0,
125128
_C, _C, _C, _C,
126129
_C, _C, _C, _C,
@@ -172,17 +175,117 @@ __ctype_table_guarded(_C_ctype_tab_, _C_ctype_tab_guarded_,
172175

173176
const unsigned short *_ctype_tab_ = &_C_ctype_tab_[0];
174177

178+
/*
179+
* _allow_ctype_abuse()
180+
*
181+
* Internal subroutine to interpret environ and return true if
182+
* LIBC_ALLOWCTYPEABUSE is defined, false if not.
183+
*
184+
* We use environ and strcmp directly to make sure this works
185+
* inside constructors and to avoid pulling in all the
186+
* stdlib/_env.c machinery if unnecessary. We cache it so we only
187+
* add one traversal of environ to each process startup for the
188+
* ctype, toupper, and tolower tables (not ideal -- rtld already
189+
* does another traversal -- but better than adding multiple
190+
* traversals).
191+
*
192+
* This query is also used by the out-of-line ctype logic in
193+
* isctype.c outside a constructor, where it could use getenv, and
194+
* perhaps support finer-grained distinctions -- like
195+
* LIBC_ALLOWCTYPEABUSE=silent vs LIBC_ALLOWCTYPEABUSE=noisy. But
196+
* until we feel the need to implement such finer distinctions,
197+
* let's keep the logic of interpreting LIBC_ALLOWCTYPEABUSE
198+
* together in one place.
199+
*/
200+
#ifndef RTLD_LOADER
201+
#include "extern.h" /* environ */
202+
static bool
203+
_allow_ctype_abuse(void)
204+
{
205+
const char vareq[] = "LIBC_ALLOWCTYPEABUSE=";
206+
char *const *envp;
207+
208+
for (envp = environ; *envp != NULL; envp++) {
209+
if (strncmp(*envp, vareq, strlen(vareq)) == 0) {
210+
return true;
211+
}
212+
}
213+
return false;
214+
}
215+
#endif
216+
175217
#if _CTYPE_GUARD_PAGE
218+
219+
static enum {
220+
ABUSE_UNDETERMINED = 0,
221+
ABUSE_ALLOWED,
222+
ABUSE_TRAPPED,
223+
} allow_ctype_abuse_cache;
224+
225+
/*
226+
* allow_ctype_abuse()
227+
*
228+
* True if LIBC_ALLOWCTYPEABUSE is defined in the environment,
229+
* false if not. May reflect the environment any time between
230+
* process startup and now.
231+
*/
232+
__dso_hidden
233+
bool
234+
allow_ctype_abuse(void)
235+
{
236+
237+
if (allow_ctype_abuse_cache != ABUSE_UNDETERMINED)
238+
return allow_ctype_abuse_cache == ABUSE_ALLOWED;
239+
return _allow_ctype_abuse();
240+
}
241+
242+
/*
243+
* constructor_allow_ctype_abuse()
244+
*
245+
* True if LIBC_ALLOWCTYPEABUSE is defined in the environment,
246+
* false if not. May be used only in an ELF constructor.
247+
*/
248+
__dso_hidden
249+
bool
250+
constructor_allow_ctype_abuse(void)
251+
{
252+
253+
if (allow_ctype_abuse_cache != ABUSE_UNDETERMINED)
254+
return allow_ctype_abuse_cache == ABUSE_ALLOWED;
255+
if (_allow_ctype_abuse()) {
256+
allow_ctype_abuse_cache = ABUSE_ALLOWED;
257+
return true;
258+
} else {
259+
allow_ctype_abuse_cache = ABUSE_TRAPPED;
260+
return false;
261+
}
262+
}
263+
176264
__attribute__((constructor))
177265
static void
178266
_C_ctype_tab_guard_init(void)
179267
{
180268

269+
if (constructor_allow_ctype_abuse())
270+
return;
181271
#ifdef __BUILD_LEGACY
182272
(void)mprotect(__UNCONST(_C_compat_bsdctype_guarded),
183273
_CTYPE_GUARD_SIZE, PROT_NONE);
184274
#endif /* __BUILD_LEGACY */
185275
(void)mprotect(__UNCONST(_C_ctype_tab_guarded_),
186276
_CTYPE_GUARD_SIZE, PROT_NONE);
187277
}
278+
279+
#else
280+
281+
#ifndef RTLD_LOADER
282+
__dso_hidden
283+
bool
284+
allow_ctype_abuse(void)
285+
{
286+
287+
return _allow_ctype_abuse();
288+
}
289+
#endif
290+
188291
#endif /* _CTYPE_GUARD_PAGE */

lib/libc/gen/ctype_guard.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* $NetBSD: ctype_guard.h,v 1.7 2025/03/31 23:48:06 riastradh Exp $ */
1+
/* $NetBSD: ctype_guard.h,v 1.7.2.1 2025/10/01 17:41:15 martin Exp $ */
22

33
/*-
44
* Copyright (c) 2025 The NetBSD Foundation, Inc.
@@ -31,6 +31,8 @@
3131

3232
#include <sys/cdefs.h>
3333

34+
#include <stdbool.h>
35+
3436
#include "ctype_local.h"
3537

3638
/*
@@ -116,10 +118,14 @@
116118
__asm(".size " _C_LABEL_STRING(#name) "," \
117119
___STRING((nelem) * (elemsize)))
118120

121+
__dso_hidden bool allow_ctype_abuse(void);
122+
119123
#if _CTYPE_GUARD_PAGE
120124

121125
# include <machine/vmparam.h>
122126

127+
__dso_hidden bool constructor_allow_ctype_abuse(void);
128+
123129
/*
124130
* _CTYPE_GUARD_SIZE must be a macro so it will work through ___STRING
125131
* to produce a string for symbol arithmetic in __asm.
@@ -130,6 +136,8 @@
130136
# define _CTYPE_GUARD_SIZE PAGE_SIZE
131137
# endif
132138

139+
# define _CTYPE_GUARD_INIT(n, x) [0 ... (n) - 1] = (x),
140+
133141
enum {
134142
_C_CTYPE_TAB_GUARD = _CTYPE_GUARD_SIZE/sizeof(_C_ctype_tab_[0]),
135143
# ifdef __BUILD_LEGACY
@@ -152,6 +160,8 @@ enum {
152160

153161
# define _CTYPE_GUARD_SIZE 0
154162

163+
# define _CTYPE_GUARD_INIT(n, x) /* empty */
164+
155165
enum {
156166
_C_CTYPE_TAB_GUARD = 0,
157167
# ifdef __BUILD_LEGACY

lib/libc/gen/isctype.c

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* $NetBSD: isctype.c,v 1.28 2025/03/29 21:45:08 riastradh Exp $ */
1+
/* $NetBSD: isctype.c,v 1.28.2.1 2025/10/01 17:41:15 martin Exp $ */
22

33
/*-
44
* Copyright (c)2008 Citrus Project,
@@ -28,7 +28,7 @@
2828

2929
#include <sys/cdefs.h>
3030
#if defined(LIBC_SCCS) && !defined(lint)
31-
__RCSID("$NetBSD: isctype.c,v 1.28 2025/03/29 21:45:08 riastradh Exp $");
31+
__RCSID("$NetBSD: isctype.c,v 1.28.2.1 2025/10/01 17:41:15 martin Exp $");
3232
#endif /* LIBC_SCCS and not lint */
3333

3434
#include "namespace.h"
@@ -48,32 +48,31 @@ __RCSID("$NetBSD: isctype.c,v 1.28 2025/03/29 21:45:08 riastradh Exp $");
4848
#error "EOF != -1"
4949
#endif
5050

51+
#include "ctype_guard.h"
5152
#include "runetype_local.h"
5253
#include "setlocale_local.h"
5354

5455
#define _RUNE_LOCALE(loc) \
5556
((_RuneLocale *)((loc)->part_impl[LC_CTYPE]))
5657

57-
static void __noinline __dead
58+
static int __noinline
5859
ctype_nasaldemon(const char *func, int c)
5960
{
6061
char buf[128];
6162

6263
snprintf_ss(buf, sizeof(buf), "ctype(3) %s: invalid input: %d\n", func,
6364
c);
6465
(void)write(STDERR_FILENO, buf, strlen(buf));
66+
if (allow_ctype_abuse())
67+
return -1;
6568
abort();
6669
}
6770

68-
static inline void
69-
ctype_check(const char *func, int c)
70-
{
71-
72-
if (__predict_false((c != EOF && c < 0) || c > UCHAR_MAX))
73-
ctype_nasaldemon(func, c);
74-
}
75-
76-
#define CTYPE_CHECK(c) ctype_check(__func__, c)
71+
#define CTYPE_CHECK(c) do \
72+
{ \
73+
if (__predict_false(((c) != EOF && (c) < 0) || (c) > UCHAR_MAX)) \
74+
return ctype_nasaldemon(__func__, c); \
75+
} while (0)
7776

7877
#define _ISCTYPE_FUNC(name, bit) \
7978
int \

lib/libc/gen/tolower_.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* $NetBSD: tolower_.c,v 1.17 2025/03/30 00:07:51 riastradh Exp $ */
1+
/* $NetBSD: tolower_.c,v 1.17.2.1 2025/10/01 17:41:14 martin Exp $ */
22

33
/*
44
* Written by J.T. Conklin <jtc@NetBSD.org>.
@@ -7,7 +7,7 @@
77

88
#include <sys/cdefs.h>
99
#if defined(LIBC_RCS) && !defined(lint)
10-
__RCSID("$NetBSD: tolower_.c,v 1.17 2025/03/30 00:07:51 riastradh Exp $");
10+
__RCSID("$NetBSD: tolower_.c,v 1.17.2.1 2025/10/01 17:41:14 martin Exp $");
1111
#endif /* LIBC_RCS and not lint */
1212

1313
#include <sys/ctype_bits.h>
@@ -25,6 +25,7 @@ __RCSID("$NetBSD: tolower_.c,v 1.17 2025/03/30 00:07:51 riastradh Exp $");
2525
__ctype_table
2626
static const short _C_tolower_tab_guarded_[_C_TOLOWER_TAB_GUARD +
2727
1 + _CTYPE_NUM_CHARS] = {
28+
_CTYPE_GUARD_INIT(_C_TOLOWER_TAB_GUARD, EOF)
2829
[_C_TOLOWER_TAB_GUARD] = EOF,
2930
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
3031
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
@@ -76,6 +77,8 @@ static void
7677
_C_tolower_tab_guard_init(void)
7778
{
7879

80+
if (constructor_allow_ctype_abuse())
81+
return;
7982
(void)mprotect(__UNCONST(_C_tolower_tab_guarded_), _CTYPE_GUARD_SIZE,
8083
PROT_NONE);
8184
}

lib/libc/gen/toupper_.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* $NetBSD: toupper_.c,v 1.17 2025/03/30 00:07:51 riastradh Exp $ */
1+
/* $NetBSD: toupper_.c,v 1.17.2.1 2025/10/01 17:41:15 martin Exp $ */
22

33
/*
44
* Written by J.T. Conklin <jtc@NetBSD.org>.
@@ -7,7 +7,7 @@
77

88
#include <sys/cdefs.h>
99
#if defined(LIBC_RCS) && !defined(lint)
10-
__RCSID("$NetBSD: toupper_.c,v 1.17 2025/03/30 00:07:51 riastradh Exp $");
10+
__RCSID("$NetBSD: toupper_.c,v 1.17.2.1 2025/10/01 17:41:15 martin Exp $");
1111
#endif /* LIBC_RCS and not lint */
1212

1313
#include <sys/ctype_bits.h>
@@ -25,6 +25,7 @@ __RCSID("$NetBSD: toupper_.c,v 1.17 2025/03/30 00:07:51 riastradh Exp $");
2525
__ctype_table
2626
static const short _C_toupper_tab_guarded_[_C_TOUPPER_TAB_GUARD +
2727
1 + _CTYPE_NUM_CHARS] = {
28+
_CTYPE_GUARD_INIT(_C_TOUPPER_TAB_GUARD, EOF)
2829
[_C_TOUPPER_TAB_GUARD] = EOF,
2930
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
3031
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
@@ -76,6 +77,8 @@ static void
7677
_C_toupper_tab_guard_init(void)
7778
{
7879

80+
if (constructor_allow_ctype_abuse())
81+
return;
7982
(void)mprotect(__UNCONST(_C_toupper_tab_guarded_), _CTYPE_GUARD_SIZE,
8083
PROT_NONE);
8184
}

0 commit comments

Comments
 (0)