Module Name:    src
Committed By:   riastradh
Date:           Fri Mar 28 19:13:23 UTC 2025

Modified Files:
        src/lib/libc/locale: rune.c
        src/tests/lib/libc/gen: t_ctype.c

Log Message:
libc: Put guard pages before locale ctype/tolower/toupper tables.

This way, triggering the undefined behaviour of negative inputs to
the ctype functions leads to instant SIGSEGV, rather than silently
giving bonkers (and likely nondeterministic) answers.  (See ctype(3)
man page for details.)

This only affects non-default locales, i.e., locales other than C.
The C locale's tables are statically linked into libc, and the
symbols defining them are baked into the ABI, so putting a guard page
before them will require either some careful elven surgery (which is
a class I must have missed back in university), or copying them into
dynamically allocated memory (which is a cost I'm reluctant to incur
on all programs using libc).

This also only affects machines where char is signed for now.  (But
maybe it would be worth doing unconditionally; users could still try
to pass in explicit `signed char' inputs.)

PR lib/58208: ctype(3) provides poor runtime feedback of abuse


To generate a diff of this commit:
cvs rdiff -u -r1.47 -r1.48 src/lib/libc/locale/rune.c
cvs rdiff -u -r1.2 -r1.3 src/tests/lib/libc/gen/t_ctype.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/lib/libc/locale/rune.c
diff -u src/lib/libc/locale/rune.c:1.47 src/lib/libc/locale/rune.c:1.48
--- src/lib/libc/locale/rune.c:1.47	Tue Apr 19 20:32:15 2022
+++ src/lib/libc/locale/rune.c	Fri Mar 28 19:13:22 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: rune.c,v 1.47 2022/04/19 20:32:15 rillig Exp $	*/
+/*	$NetBSD: rune.c,v 1.48 2025/03/28 19:13:22 riastradh Exp $	*/
 /*-
  * Copyright (c)2010 Citrus Project,
  * All rights reserved.
@@ -54,9 +54,15 @@
 
 typedef struct {
 	_RuneLocale rl;
+#ifdef __CHAR_UNSIGNED__
 	unsigned short	rlp_ctype_tab  [_CTYPE_NUM_CHARS + 1];
 	short		rlp_tolower_tab[_CTYPE_NUM_CHARS + 1];
 	short		rlp_toupper_tab[_CTYPE_NUM_CHARS + 1];
+#else
+	unsigned short	*rlp_ctype_tab;
+	short		*rlp_tolower_tab;
+	short		*rlp_toupper_tab;
+#endif
 	char		rlp_codeset[33]; /* XXX */
 
 #ifdef __BUILD_LEGACY
@@ -64,6 +70,52 @@ typedef struct {
 #endif
 } _RuneLocalePriv;
 
+#ifndef __CHAR_UNSIGNED__
+
+#define	roundup(X, N)	((((X) + ((N) - 1))/(N))*(N))
+
+static void *
+alloc_guarded(size_t elemsize, size_t nelem)
+{
+	const unsigned page_size = sysconf(_SC_PAGESIZE);
+	size_t nbytes = 0;
+	void *p = MAP_FAILED, *q = NULL;
+
+	_DIAGASSERT(elemsize != 0);
+	if (nelem > SIZE_MAX/elemsize)
+		goto fail;
+	nbytes = page_size + roundup(elemsize*nelem, page_size);
+	p = mmap(NULL, nbytes, PROT_READ|PROT_WRITE, MAP_ANON,
+	    /*fd*/-1, /*offset*/0);
+	if (p == MAP_FAILED)
+		goto fail;
+	if (mprotect(p, page_size, PROT_NONE) == -1)
+		goto fail;
+	q = (char *)p + page_size;
+	return q;
+
+fail:	if (p != MAP_FAILED)
+		(void)munmap(p, nbytes);
+	return NULL;
+}
+
+static void
+free_guarded(void *q, size_t elemsize, size_t nelem)
+{
+	const unsigned page_size = sysconf(_SC_PAGESIZE);
+	size_t nbytes = 0;
+	void *p;
+
+	if (q == NULL)
+		return;
+	_DIAGASSERT(elemsize <= SIZE_MAX/nelem);
+	nbytes = page_size + roundup(elemsize*nelem, page_size);
+	p = (char *)q - page_size;
+	(void)munmap(p, nbytes);
+}
+
+#endif	/* !__CHAR_UNSIGNED__ */
+
 static __inline void
 _rune_wctype_init(_RuneLocale *rl)
 {
@@ -213,6 +265,22 @@ _rune_read_file(const char * __restrict 
 	rlp = (_RuneLocalePriv *)malloc(n);
 	if (rlp == NULL)
 		return ENOMEM;
+#ifndef __CHAR_UNSIGNED__
+	rlp->rlp_ctype_tab = NULL;
+	rlp->rlp_tolower_tab = NULL;
+	rlp->rlp_toupper_tab = NULL;
+	if ((rlp->rlp_ctype_tab = alloc_guarded(sizeof(rlp->rlp_ctype_tab[0]),
+		    _CTYPE_NUM_CHARS + 1)) == NULL ||
+	    (rlp->rlp_tolower_tab =
+		alloc_guarded(sizeof(rlp->rlp_tolower_tab[0]),
+		    _CTYPE_NUM_CHARS + 1)) == NULL ||
+	    (rlp->rlp_toupper_tab =
+		alloc_guarded(sizeof(rlp->rlp_toupper_tab[0]),
+		    _CTYPE_NUM_CHARS + 1)) == NULL) {
+		ret = ENOMEM;
+		goto err;
+	}
+#endif	/* !__CHAR_UNSIGNED__ */
 	_rune_init_priv(rlp);
 
 	rl = &rlp->rl;
@@ -324,6 +392,14 @@ do {									\
 	return 0;
 
 err:
+#ifndef __CHAR_UNSIGNED__
+	free_guarded(rlp->rlp_ctype_tab, sizeof(rlp->rlp_ctype_tab[0]),
+	    _CTYPE_NUM_CHARS + 1);
+	free_guarded(rlp->rlp_tolower_tab, sizeof(rlp->rlp_tolower_tab[0]),
+	    _CTYPE_NUM_CHARS + 1);
+	free_guarded(rlp->rlp_toupper_tab, sizeof(rlp->rlp_toupper_tab[0]),
+	    _CTYPE_NUM_CHARS + 1);
+#endif
 	free(rlp);
 	return ret;
 }

Index: src/tests/lib/libc/gen/t_ctype.c
diff -u src/tests/lib/libc/gen/t_ctype.c:1.2 src/tests/lib/libc/gen/t_ctype.c:1.3
--- src/tests/lib/libc/gen/t_ctype.c:1.2	Fri Mar 28 18:54:09 2025
+++ src/tests/lib/libc/gen/t_ctype.c	Fri Mar 28 19:13:22 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: t_ctype.c,v 1.2 2025/03/28 18:54:09 riastradh Exp $	*/
+/*	$NetBSD: t_ctype.c,v 1.3 2025/03/28 19:13:22 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2025 The NetBSD Foundation, Inc.
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: t_ctype.c,v 1.2 2025/03/28 18:54:09 riastradh Exp $");
+__RCSID("$NetBSD: t_ctype.c,v 1.3 2025/03/28 19:13:22 riastradh Exp $");
 
 #include <atf-c.h>
 #include <ctype.h>
@@ -800,8 +800,6 @@ ATF_TC_BODY(abuse_##FN##_macro_locale, t
 		atf_tc_skip("runtime ctype(3) abuse is impossible with"	      \
 		    " unsigned char");					      \
 	}								      \
-	atf_tc_expect_fail("PR lib/58208:"				      \
-	    " ctype(3) provides poor runtime feedback of abuse");	      \
 	for (i = 0; i < __arraycount(locales); i++) {			      \
 		char buf[128];						      \
 									      \
@@ -825,8 +823,6 @@ ATF_TC_BODY(abuse_##FN##_function_locale
 		atf_tc_skip("runtime ctype(3) abuse is impossible with"	      \
 		    " unsigned char");					      \
 	}								      \
-	atf_tc_expect_fail("PR lib/58208:"				      \
-	    " ctype(3) provides poor runtime feedback of abuse");	      \
 	for (i = 0; i < __arraycount(locales); i++) {			      \
 		char buf[128];						      \
 									      \

Reply via email to