From fe0675770b66a9ed393884d96e276b8d116fa2a2 Mon Sep 17 00:00:00 2001
From: Sylvain Henry <sylvain@haskus.fr>
Date: Fri, 6 Oct 2023 14:01:52 +0200
Subject: [PATCH] Avoid out-of-bound array access in bigNatIsPowerOf2 (fix
 #24066)

bigNatIndex# in the `where` clause wasn't guarded by "bigNatIsZero a".
---
 libraries/ghc-bignum/src/GHC/Num/BigNat.hs       | 15 ++++++++-------
 testsuite/tests/numeric/should_run/T24066.hs     | 16 ++++++++++++++++
 testsuite/tests/numeric/should_run/T24066.stdout |  1 +
 testsuite/tests/numeric/should_run/all.T         |  1 +
 4 files changed, 26 insertions(+), 7 deletions(-)
 create mode 100644 testsuite/tests/numeric/should_run/T24066.hs
 create mode 100644 testsuite/tests/numeric/should_run/T24066.stdout

diff --git a/libraries/ghc-bignum/src/GHC/Num/BigNat.hs b/libraries/ghc-bignum/src/GHC/Num/BigNat.hs
index 21b07cb8300..c7f8afa0275 100644
--- a/libraries/ghc-bignum/src/GHC/Num/BigNat.hs
+++ b/libraries/ghc-bignum/src/GHC/Num/BigNat.hs
@@ -135,13 +135,8 @@ bigNatIsTwo# ba =
 bigNatIsPowerOf2# :: BigNat# -> (# (# #) | Word# #)
 bigNatIsPowerOf2# a
    | bigNatIsZero a                      = (# (# #) | #)
-   | True = case wordIsPowerOf2# msw of
-               (# (# #) | #) -> (# (# #) | #)
-               (# | c  #) -> case checkAllZeroes (imax -# 1#) of
-                  0# -> (# (# #) | #)
-                  _  -> (# | c `plusWord#`
-                              (int2Word# imax `uncheckedShiftL#` WORD_SIZE_BITS_SHIFT#) #)
-   where
+   | True =
+    let
       msw  = bigNatIndex# a imax
       sz   = bigNatSize# a
       imax = sz -# 1#
@@ -150,6 +145,12 @@ bigNatIsPowerOf2# a
          | True = case bigNatIndex# a i of
                      0## -> checkAllZeroes (i -# 1#)
                      _   -> 0#
+    in case wordIsPowerOf2# msw of
+               (# (# #) | #) -> (# (# #) | #)
+               (# | c  #) -> case checkAllZeroes (imax -# 1#) of
+                  0# -> (# (# #) | #)
+                  _  -> (# | c `plusWord#`
+                              (int2Word# imax `uncheckedShiftL#` WORD_SIZE_BITS_SHIFT#) #)
 
 -- | Return the Word# at the given index
 bigNatIndex# :: BigNat# -> Int# -> Word#
diff --git a/testsuite/tests/numeric/should_run/T24066.hs b/testsuite/tests/numeric/should_run/T24066.hs
new file mode 100644
index 00000000000..dc173154bab
--- /dev/null
+++ b/testsuite/tests/numeric/should_run/T24066.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE UnboxedTuples #-}
+
+module Main where
+
+import GHC.Num.BigNat
+import GHC.Exts
+
+-- just to ensure that (future) rewrite rules don't mess with the test
+{-# NOINLINE foo #-}
+foo (# #) = bigNatZero# (# #)
+
+main = do
+  case bigNatIsPowerOf2# (foo (# #)) of
+    (# _ | #) -> putStrLn "Zero isn't a power of two"
+    (# | w #) -> putStrLn $ "Zero is 2^" ++ show (W# w)
diff --git a/testsuite/tests/numeric/should_run/T24066.stdout b/testsuite/tests/numeric/should_run/T24066.stdout
new file mode 100644
index 00000000000..73aeea9203d
--- /dev/null
+++ b/testsuite/tests/numeric/should_run/T24066.stdout
@@ -0,0 +1 @@
+Zero isn't a power of two
diff --git a/testsuite/tests/numeric/should_run/all.T b/testsuite/tests/numeric/should_run/all.T
index 26bb1fe2c62..074155bb6ec 100644
--- a/testsuite/tests/numeric/should_run/all.T
+++ b/testsuite/tests/numeric/should_run/all.T
@@ -81,3 +81,4 @@ test('T20291', normal, compile_and_run, [''])
 test('T22282', normal, compile_and_run, [''])
 test('T22671', normal, compile_and_run, [''])
 test('foundation', [when(js_arch(), run_timeout_multiplier(2))], compile_and_run, ['-O -package transformers'])
+test('T24066', normal, compile_and_run, [''])
-- 
GitLab