~ chicken-core (master) 93b65f8eef019c0ea967babb4649b36ede574340
commit 93b65f8eef019c0ea967babb4649b36ede574340
Author: felix <felix@call-with-current-continuation.org>
AuthorDate: Tue Sep 1 12:20:13 2026 +0200
Commit: felix <felix@call-with-current-continuation.org>
CommitDate: Tue Sep 1 12:23:12 2026 +0200
Avoid OOB read in C_[static_]string
Reported by "crzcrz":
utf8_decode always reads 4 bytes, so buffers passed to it need 3 bytes of slack. C_static_bytevector allocates that slack. But the character count is taken over the caller's buffer, which has none.
Reproduce: run anything under ASan.
$ csi -n -q -e '(print (+ 1 2))'
ERROR: AddressSanitizer: global-buffer-overflow
READ of size 1
#0 utf8_decode utf.c:3202
#1 C_utf_count utf.c:3507
#2 C_static_string runtime.c:2815
#3 decode_literal2 runtime.c:12737
0x0001021df04c is located 0 bytes after global variable '.str.2' of size 12
The padding exists and says why (runtime.c:2876):
C_regparm C_word C_static_bytevector(C_word **ptr, int len, C_char *str)
{
/* we need to add 4 here, as utf8_decode does 3-byte lookahead */
C_word *dptr = (C_word *)C_malloc(sizeof(C_header) + C_align(len + 4));
But C_static_string (runtime.c:2805) counts over str, not over the padded copy. C_string (runtime.c:2790) has the same line.
C_regparm C_word C_static_string(C_word **ptr, int len, C_char *str)
{
C_word buf = C_static_bytevector(ptr, len + 1, str);
...
n = C_utf_count(str, len);
Reads up to 3 bytes past a string literal. Harmless until a literal ends on a page boundary.
diff --git a/runtime.c b/runtime.c
index e16ac10a..66348a9d 100644
--- a/runtime.c
+++ b/runtime.c
@@ -2790,7 +2790,7 @@ C_regparm C_word C_string(C_word **ptr, int len, C_char *str)
C_c_bytevector(buf)[ len ] = 0;
C_block_header_init(s, C_STRING_TAG);
C_set_block_item(s, 0, buf);
- n = C_utf_count(str, len);
+ n = C_utf_count((C_char *)C_data_pointer(buf), len);
C_set_block_item(s, 1, C_fix(n));
C_set_block_item(s, 2, C_fix(0));
C_set_block_item(s, 3, C_fix(0));
@@ -2806,7 +2806,7 @@ C_regparm C_word C_static_string(C_word **ptr, int len, C_char *str)
C_c_bytevector(buf)[ len ] = 0;
C_block_header_init(s, C_STRING_TAG);
C_set_block_item(s, 0, buf);
- n = C_utf_count(str, len);
+ n = C_utf_count((C_char *)C_data_pointer(buf), len);
C_set_block_item(s, 1, C_fix(n));
C_set_block_item(s, 2, C_fix(0));
C_set_block_item(s, 3, C_fix(0));
Trap