~ chicken-core (master) da64a64bf375aec52c61752dc9ef11adb0f0c2fa


commit da64a64bf375aec52c61752dc9ef11adb0f0c2fa
Author:     Peter Bex <peter@more-magic.net>
AuthorDate: Tue Sep 1 12:00:36 2026 +0200
Commit:     felix <felix@call-with-current-continuation.org>
CommitDate: Tue Sep 1 12:13:53 2026 +0200

    Fix out of bounds read in bit->boolean
    
    Because we store only the sign and absolute value in a bignum's
    digits, a negative bignum would need to get its bit-pattern negated as
    per twos complement before checking whether a particular bit is set.
    
    After calculating the digit index to read from the output, we would
    negate the bignum and pass that as the size (so it can stop early if
    we're not interested in higher bits).  The size needs to be big enough
    to accomodate that digit, so it needs to be one higher than the digit
    index.
    
    Reported by crzcrz
    
    Signed-off-by: felix <felix@call-with-current-continuation.org>

diff --git a/NEWS b/NEWS
index e652310f..ebcfb6bc 100644
--- a/NEWS
+++ b/NEWS
@@ -14,6 +14,8 @@
   - The procedures in "(scheme write)" respect any print-length limits
     imposed by the REPL.
   - Fixed a bug in "read-string" when reading from a binary port.
+  - Fixed a bug in bit->boolean when given negative bignums which would give
+    incorrect output or crash due to out of bounds read (reported by crzcrz)
 
 - Syntax expander
   - Fixed bug in handling of "export-all" library declaration which caused
diff --git a/runtime.c b/runtime.c
index 1a2d6715..00fb7c2f 100644
--- a/runtime.c
+++ b/runtime.c
@@ -6588,8 +6588,7 @@ C_regparm C_word C_i_bit_to_bool(C_word n, C_word i)
       d = i / C_BIGNUM_DIGIT_LENGTH;
       if (d >= C_bignum_size(n)) return C_mk_bool(C_bignum_negativep(n));
 
-      /* TODO: this isn't necessary, is it? */
-      if (C_truep(nn = maybe_negate_bignum_for_bitwise_op(n, d))) n = nn;
+      if (C_truep(nn = maybe_negate_bignum_for_bitwise_op(n, d+1))) n = nn;
 
       i %= C_BIGNUM_DIGIT_LENGTH;
       d = C_mk_bool((C_bignum_digits(n)[d] & (C_uword)1 << i) != 0);
diff --git a/tests/numbers-test.scm b/tests/numbers-test.scm
index 2898f2fb..f1d95386 100644
--- a/tests/numbers-test.scm
+++ b/tests/numbers-test.scm
@@ -952,7 +952,15 @@
  (test-equal (bit->boolean -2 0) #f)
  (test-equal (bit->boolean -2 1) #t)
  (test-equal (bit->boolean (expt -2 63) 256) #t)
+ (test-equal (bit->boolean (expt -2 65) 256) #t)
+ (test-equal (bit->boolean (expt -2 65) 1) #f)
+ (test-equal (bit->boolean (expt -2 65) 0) #f)
+ (test-equal (bit->boolean (expt -2 65) 65) #t)
  (test-equal (bit->boolean (expt 2 63) 256) #f)
+ (test-equal (bit->boolean (expt 2 65) 256) #f)
+ (test-equal (bit->boolean (expt 2 65) 65) #t)
+ (test-equal (bit->boolean (expt 2 65) 1) #f)
+ (test-equal (bit->boolean (expt 2 65) 0) #f)
  (test-equal (arithmetic-shift 15 2) 60)
  (test-equal (arithmetic-shift 15 -2) 3)
  (test-equal (arithmetic-shift -15 2) -60)
Trap