]> git.eshelyaron.com Git - emacs.git/commitdiff
Fix bool vector length overflow
authorPaul Eggert <eggert@cs.ucla.edu>
Sat, 20 Jul 2024 22:52:05 +0000 (15:52 -0700)
committerEshel Yaron <me@eshelyaron.com>
Mon, 22 Jul 2024 10:38:28 +0000 (12:38 +0200)
* src/alloc.c (make_clear_bool_vector): It’s now the caller’s
responsibility to make sure the bool vector length is in range.
Add an eassert to double-check this.  This lets some locals be
ptrdiff_t not EMACS_INT.
(Fmake_bool_vector, Fbool_vector):
Check that bool vector lengths are in range.
* src/lisp.h (BOOL_VECTOR_LENGTH_MAX): New macro.
(bool_vector_words, bool_vector_bytes): Avoid undefined
behavior if size == EMACS_INT_MAX - (BITS_PER_BITS_WORD - 1).
This is mostly theoretical but it’s easy to do it right.
* src/lread.c (read_bool_vector): Use EMACS_INT, not just ptrdiff_t.
Check that length doesn’t exceed BOOL_VECTOR_LENGTH_MAX.
This fixes an unlikely integer overflow where the calculated size
went negative.

(cherry picked from commit 515e5ad0de133f0a3d501bd6290ccc51d8462955)

src/alloc.c
src/lisp.h
src/lread.c

index 41679b5270710b3f29e2ccbc44f6e958c8357dae..48b170b866fffad7c4a9d7ac77fdbb774660d056 100644 (file)
@@ -2413,14 +2413,13 @@ bool_vector_fill (Lisp_Object a, Lisp_Object init)
 Lisp_Object
 make_clear_bool_vector (EMACS_INT nbits, bool clearit)
 {
+  eassert (0 <= nbits && nbits <= BOOL_VECTOR_LENGTH_MAX);
   Lisp_Object val;
-  EMACS_INT words = bool_vector_words (nbits);
-  EMACS_INT word_bytes = words * sizeof (bits_word);
-  EMACS_INT needed_elements = ((bool_header_size - header_size + word_bytes
+  ptrdiff_t words = bool_vector_words (nbits);
+  ptrdiff_t word_bytes = words * sizeof (bits_word);
+  ptrdiff_t needed_elements = ((bool_header_size - header_size + word_bytes
                                + word_size - 1)
                               / word_size);
-  if (PTRDIFF_MAX < needed_elements)
-    memory_full (SIZE_MAX);
   struct Lisp_Bool_Vector *p
     = (struct Lisp_Bool_Vector *) allocate_clear_vector (needed_elements,
                                                         clearit);
@@ -2449,7 +2448,10 @@ LENGTH must be a number.  INIT matters only in whether it is t or nil.  */)
   (Lisp_Object length, Lisp_Object init)
 {
   CHECK_FIXNAT (length);
-  Lisp_Object val = make_clear_bool_vector (XFIXNAT (length), NILP (init));
+  EMACS_INT len = XFIXNAT (length);
+  if (BOOL_VECTOR_LENGTH_MAX < len)
+    memory_full (SIZE_MAX);
+  Lisp_Object val = make_clear_bool_vector (len, NILP (init));
   return NILP (init) ? val : bool_vector_fill (val, init);
 }
 
@@ -2459,6 +2461,8 @@ Allows any number of arguments, including zero.
 usage: (bool-vector &rest OBJECTS)  */)
   (ptrdiff_t nargs, Lisp_Object *args)
 {
+  if (BOOL_VECTOR_LENGTH_MAX < nargs)
+    memory_full (SIZE_MAX);
   Lisp_Object vector = make_clear_bool_vector (nargs, true);
   for (ptrdiff_t i = 0; i < nargs; i++)
     if (!NILP (args[i]))
index 79eade2f5ae94afe207f316d64f988d82cde3b18..976b7a15251f6a84a137ada0b82b552a9bd06849 100644 (file)
@@ -1840,7 +1840,7 @@ struct Lisp_Bool_Vector
     /* HEADER.SIZE is the vector's size field.  It doesn't have the real size,
        just the subtype information.  */
     union vectorlike_header header;
-    /* This is the size in bits.  */
+    /* The size in bits; at most BOOL_VECTOR_LENGTH_MAX.  */
     EMACS_INT size;
     /* The actual bits, packed into bytes.
        Zeros fill out the last word if needed.
@@ -1868,20 +1868,32 @@ enum
     word_size = sizeof (Lisp_Object)
   };
 
+/* A bool vector's length must be a fixnum for XFIXNUM (Flength (...)).
+   Also, it is limited object size, which must fit in both ptrdiff_t and
+   size_t including header overhead and trailing alignment.  */
+#define BOOL_VECTOR_LENGTH_MAX \
+  min (MOST_POSITIVE_FIXNUM, \
+       ((INT_MULTIPLY_OVERFLOW (min (PTRDIFF_MAX, SIZE_MAX) - bool_header_size,\
+                               (EMACS_INT) BOOL_VECTOR_BITS_PER_CHAR) \
+        ? EMACS_INT_MAX \
+        : ((min (PTRDIFF_MAX, SIZE_MAX) - bool_header_size) \
+           * (EMACS_INT) BOOL_VECTOR_BITS_PER_CHAR)) \
+       - (BITS_PER_BITS_WORD - 1)))
+
 /* The number of data words and bytes in a bool vector with SIZE bits.  */
 
 INLINE EMACS_INT
 bool_vector_words (EMACS_INT size)
 {
   eassume (0 <= size && size <= EMACS_INT_MAX - (BITS_PER_BITS_WORD - 1));
-  return (size + BITS_PER_BITS_WORD - 1) / BITS_PER_BITS_WORD;
+  return (size + (BITS_PER_BITS_WORD - 1)) / BITS_PER_BITS_WORD;
 }
 
 INLINE EMACS_INT
 bool_vector_bytes (EMACS_INT size)
 {
   eassume (0 <= size && size <= EMACS_INT_MAX - (BITS_PER_BITS_WORD - 1));
-  return (size + BOOL_VECTOR_BITS_PER_CHAR - 1) / BOOL_VECTOR_BITS_PER_CHAR;
+  return (size + (BOOL_VECTOR_BITS_PER_CHAR - 1)) / BOOL_VECTOR_BITS_PER_CHAR;
 }
 
 INLINE bits_word
index c1f309866c859be95db7e11333497a9696ea354d..ace7abd80c842e7ea865350ddfc58eb9d1764639 100644 (file)
@@ -3568,7 +3568,7 @@ string_props_from_rev_list (Lisp_Object elems, Lisp_Object readcharfun)
 static Lisp_Object
 read_bool_vector (Lisp_Object readcharfun)
 {
-  ptrdiff_t length = 0;
+  EMACS_INT length = 0;
   for (;;)
     {
       int c = READCHAR;
@@ -3582,6 +3582,8 @@ read_bool_vector (Lisp_Object readcharfun)
          || ckd_add (&length, length, c - '0'))
        invalid_syntax ("#&", readcharfun);
     }
+  if (BOOL_VECTOR_LENGTH_MAX < length)
+    invalid_syntax ("#&", readcharfun);
 
   ptrdiff_t size_in_chars = bool_vector_bytes (length);
   Lisp_Object str = read_string_literal (readcharfun);