~ 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