tech-toolchain archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Statically allocated guard pages in ELF



[followups to tech-toolchain, bcc tech-userlevel tech-kern]

The attached patch adds a guard page to the ctype(3) tables for the
default C locale in libc, so that if you try to do, e.g.,

	char x = 0x80;
	if (isalpha(x)) ...

then it is guaranteed to trigger SIGSEGV on machines where char is
signed, rather than merely return unpredictable results for this input
on which it is undefined.

Setting aside the copied & pasted mmap/mprotect logic across three
files, I'm not entirely happy with the patch because it adds several
mmap/mprotect calls to every program startup in libc.

Can it be done with an ELF trick to request a guard page instead, so
there's no additional syscall overhead but the kernel sets up a
PROT_NONE page before each table it already maps from the file?

(Perhaps the syscall overhead is negligible and this is premature
optimization -- I haven't measured -- but it seems prudent to
consider, at least, when the context is libc startup.)
# HG changeset patch
# User Taylor R Campbell <riastradh%NetBSD.org@localhost>
# Date 1743186173 0
#      Fri Mar 28 18:22:53 2025 +0000
# Branch trunk
# Node ID bba63cf2ff04b904ef7f8bbb878d10265275c639
# Parent  1335f96e94bb06b331691a6b2072b0c039467aa9
# EXP-Topic riastradh-pr58208-runtimectypeabusedetection
WIP: libc: Put guard pages before the C ctype/tolower/toupper tables.

CAVEAT: This adds three additional mmap/mprotect calls on libc startup,
which is not great.  I tried to find a way to make ELF do this, in
order to avoid adding overhead to pretty much every process startup,
but I couldn't find a way.

CAVEAT: The guard pages don't remain if you setlocale(LC_CTYPE, "C")
explicitly.  To be handled.

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

diff -r 1335f96e94bb -r bba63cf2ff04 lib/libc/gen/ctype_.c
--- a/lib/libc/gen/ctype_.c	Fri Mar 28 19:21:29 2025 +0000
+++ b/lib/libc/gen/ctype_.c	Fri Mar 28 18:22:53 2025 +0000
@@ -44,7 +44,12 @@
 #endif /* LIBC_SCCS and not lint */
 
 #include <sys/ctype_bits.h>
+#include <sys/mman.h>
+
 #include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
 #include "ctype_local.h"
 
 #if EOF != -1
@@ -158,3 +163,41 @@ const unsigned short _C_ctype_tab_[1 + _
 #undef _X
 
 const unsigned short *_ctype_tab_ = &_C_ctype_tab_[0];
+
+#ifndef __CHAR_UNSIGNED__
+
+#define	roundup(X, N)	((((X) + ((N) - 1))/(N))*(N))
+
+static const void *
+guard_ctype(const void *tab, size_t size)
+{
+	const unsigned page_size = sysconf(_SC_PAGESIZE);
+	size_t nbytes = 0;
+	void *p = MAP_FAILED, *q = NULL;
+
+	nbytes = page_size + roundup(size, 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;
+	memcpy(q, tab, size);
+	memset((char *)q + size, 0xff, nbytes - size - page_size);
+	return q;
+
+fail:	if (p != MAP_FAILED)
+		(void)munmap(p, nbytes);
+	return tab;
+}
+
+static void __attribute__((constructor))
+ctype_guard_init(void)
+{
+
+	_ctype_ = guard_ctype(_C_compat_bsdctype, sizeof(_C_compat_bsdctype));
+	_ctype_tab_ = guard_ctype(_C_ctype_tab_, sizeof(_C_ctype_tab_));
+}
+
+#endif	/* __CHAR_UNSIGNED__ */
diff -r 1335f96e94bb -r bba63cf2ff04 lib/libc/gen/tolower_.c
--- a/lib/libc/gen/tolower_.c	Fri Mar 28 19:21:29 2025 +0000
+++ b/lib/libc/gen/tolower_.c	Fri Mar 28 18:22:53 2025 +0000
@@ -11,7 +11,12 @@
 #endif /* LIBC_RCS and not lint */
 
 #include <sys/ctype_bits.h>
+#include <sys/mman.h>
+
 #include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
 #include "ctype_local.h"
 
 #if EOF != -1
@@ -61,3 +66,40 @@ const short _C_tolower_tab_[1 + _CTYPE_N
 #endif
 
 const short *_tolower_tab_ = &_C_tolower_tab_[0];
+
+#ifndef __CHAR_UNSIGNED__
+
+#define	roundup(X, N)	((((X) + ((N) - 1))/(N))*(N))
+
+static const void *
+guard_ctype(const void *tab, size_t size)
+{
+	const unsigned page_size = sysconf(_SC_PAGESIZE);
+	size_t nbytes = 0;
+	void *p = MAP_FAILED, *q = NULL;
+
+	nbytes = page_size + roundup(size, 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;
+	memcpy(q, tab, size);
+	memset((char *)q + size, 0xff, nbytes - size - page_size);
+	return q;
+
+fail:	if (p != MAP_FAILED)
+		(void)munmap(p, nbytes);
+	return tab;
+}
+
+static void __attribute__((constructor))
+tolower_guard_init(void)
+{
+
+	_tolower_tab_ = guard_ctype(_C_tolower_tab_, sizeof(_C_tolower_tab_));
+}
+
+#endif	/* __CHAR_UNSIGNED__ */
diff -r 1335f96e94bb -r bba63cf2ff04 lib/libc/gen/toupper_.c
--- a/lib/libc/gen/toupper_.c	Fri Mar 28 19:21:29 2025 +0000
+++ b/lib/libc/gen/toupper_.c	Fri Mar 28 18:22:53 2025 +0000
@@ -11,7 +11,12 @@
 #endif /* LIBC_RCS and not lint */
 
 #include <sys/ctype_bits.h>
+#include <sys/mman.h>
+
 #include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
 #include "ctype_local.h"
 
 #if EOF != -1
@@ -61,3 +66,40 @@ const short _C_toupper_tab_[1 + _CTYPE_N
 #endif
 
 const short *_toupper_tab_ = &_C_toupper_tab_[0];
+
+#ifndef __CHAR_UNSIGNED__
+
+#define	roundup(X, N)	((((X) + ((N) - 1))/(N))*(N))
+
+static const void *
+guard_ctype(const void *tab, size_t size)
+{
+	const unsigned page_size = sysconf(_SC_PAGESIZE);
+	size_t nbytes = 0;
+	void *p = MAP_FAILED, *q = NULL;
+
+	nbytes = page_size + roundup(size, 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;
+	memcpy(q, tab, size);
+	memset((char *)q + size, 0xff, nbytes - size - page_size);
+	return q;
+
+fail:	if (p != MAP_FAILED)
+		(void)munmap(p, nbytes);
+	return tab;
+}
+
+static void __attribute__((constructor))
+toupper_guard_init(void)
+{
+
+	_toupper_tab_ = guard_ctype(_C_toupper_tab_, sizeof(_C_toupper_tab_));
+}
+
+#endif	/* __CHAR_UNSIGNED__ */
diff -r 1335f96e94bb -r bba63cf2ff04 tests/lib/libc/gen/t_ctype.c
--- a/tests/lib/libc/gen/t_ctype.c	Fri Mar 28 19:21:29 2025 +0000
+++ b/tests/lib/libc/gen/t_ctype.c	Fri Mar 28 18:22:53 2025 +0000
@@ -766,8 +766,6 @@ ATF_TC_BODY(abuse_##FN##_macro_c, tc)			
 		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");	      \
 	test_abuse(#FN, &FN##_wrapper);					      \
 }									      \
 ATF_TC(abuse_##FN##_function_c);					      \
@@ -782,8 +780,6 @@ ATF_TC_BODY(abuse_##FN##_function_c, tc)
 		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");	      \
 	test_abuse(#FN, &FN);						      \
 }									      \
 ATF_TC(abuse_##FN##_macro_locale);					      \


Home | Main Index | Thread Index | Old Index