Fixed potential length miscalculations by limiting max size of arrays and strings.
authorlrn@chromium.org <lrn@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 7 Jan 2010 13:17:18 +0000 (13:17 +0000)
committerlrn@chromium.org <lrn@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 7 Jan 2010 13:17:18 +0000 (13:17 +0000)
Review URL: http://codereview.chromium.org/525064

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@3560 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/heap.cc
src/objects.cc
src/objects.h
src/runtime.cc
src/utils.cc

index 7c2dd46..6852f53 100644 (file)
@@ -806,7 +806,8 @@ void Heap::ScavengeExternalStringTable() {
     }
   }
 
-  ExternalStringTable::ShrinkNewStrings(last - start);
+  ASSERT(last <= end);
+  ExternalStringTable::ShrinkNewStrings(static_cast<int>(last - start));
 }
 
 
@@ -2038,6 +2039,9 @@ Object* Heap::LookupSingleCharacterStringFromCode(uint16_t code) {
 
 
 Object* Heap::AllocateByteArray(int length, PretenureFlag pretenure) {
+  if (length < 0 || length > ByteArray::kMaxLength) {
+    return Failure::OutOfMemoryException();
+  }
   if (pretenure == NOT_TENURED) {
     return AllocateByteArray(length);
   }
@@ -2054,6 +2058,9 @@ Object* Heap::AllocateByteArray(int length, PretenureFlag pretenure) {
 
 
 Object* Heap::AllocateByteArray(int length) {
+  if (length < 0 || length > ByteArray::kMaxLength) {
+    return Failure::OutOfMemoryException();
+  }
   int size = ByteArray::SizeFor(length);
   AllocationSpace space =
       (size > MaxObjectSizeInPagedSpace()) ? LO_SPACE : NEW_SPACE;
@@ -2656,12 +2663,16 @@ Map* Heap::SymbolMapForString(String* string) {
 Object* Heap::AllocateInternalSymbol(unibrow::CharacterStream* buffer,
                                      int chars,
                                      uint32_t hash_field) {
+  ASSERT(chars >= 0);
   // Ensure the chars matches the number of characters in the buffer.
   ASSERT(static_cast<unsigned>(chars) == buffer->Length());
   // Determine whether the string is ascii.
   bool is_ascii = true;
-  while (buffer->has_more() && is_ascii) {
-    if (buffer->GetNext() > unibrow::Utf8::kMaxOneByteChar) is_ascii = false;
+  while (buffer->has_more()) {
+    if (buffer->GetNext() > unibrow::Utf8::kMaxOneByteChar) {
+      is_ascii = false;
+      break;
+    }
   }
   buffer->Rewind();
 
@@ -2670,9 +2681,15 @@ Object* Heap::AllocateInternalSymbol(unibrow::CharacterStream* buffer,
   Map* map;
 
   if (is_ascii) {
+    if (chars > SeqAsciiString::kMaxLength) {
+      return Failure::OutOfMemoryException();
+    }
     map = ascii_symbol_map();
     size = SeqAsciiString::SizeFor(chars);
   } else {
+    if (chars > SeqTwoByteString::kMaxLength) {
+      return Failure::OutOfMemoryException();
+    }
     map = symbol_map();
     size = SeqTwoByteString::SizeFor(chars);
   }
@@ -2700,7 +2717,13 @@ Object* Heap::AllocateInternalSymbol(unibrow::CharacterStream* buffer,
 
 
 Object* Heap::AllocateRawAsciiString(int length, PretenureFlag pretenure) {
+  if (length < 0 || length > SeqAsciiString::kMaxLength) {
+    return Failure::OutOfMemoryException();
+  }
+
   int size = SeqAsciiString::SizeFor(length);
+  ASSERT(size <= SeqAsciiString::kMaxSize);
+
   AllocationSpace space = (pretenure == TENURED) ? OLD_DATA_SPACE : NEW_SPACE;
   AllocationSpace retry_space = OLD_DATA_SPACE;
 
@@ -2728,7 +2751,11 @@ Object* Heap::AllocateRawAsciiString(int length, PretenureFlag pretenure) {
 
 
 Object* Heap::AllocateRawTwoByteString(int length, PretenureFlag pretenure) {
+  if (length < 0 || length > SeqTwoByteString::kMaxLength) {
+    return Failure::OutOfMemoryException();
+  }
   int size = SeqTwoByteString::SizeFor(length);
+  ASSERT(size <= SeqTwoByteString::kMaxSize);
   AllocationSpace space = (pretenure == TENURED) ? OLD_DATA_SPACE : NEW_SPACE;
   AllocationSpace retry_space = OLD_DATA_SPACE;
 
@@ -2767,6 +2794,9 @@ Object* Heap::AllocateEmptyFixedArray() {
 
 
 Object* Heap::AllocateRawFixedArray(int length) {
+  if (length < 0 || length > FixedArray::kMaxLength) {
+    return Failure::OutOfMemoryException();
+  }
   // Use the general function if we're forced to always allocate.
   if (always_allocate()) return AllocateFixedArray(length, TENURED);
   // Allocate the raw data for a fixed array.
@@ -2818,7 +2848,11 @@ Object* Heap::AllocateFixedArray(int length) {
 
 
 Object* Heap::AllocateFixedArray(int length, PretenureFlag pretenure) {
+  ASSERT(length >= 0);
   ASSERT(empty_fixed_array()->IsFixedArray());
+  if (length < 0 || length > FixedArray::kMaxLength) {
+    return Failure::OutOfMemoryException();
+  }
   if (length == 0) return empty_fixed_array();
 
   AllocationSpace space =
index 8f9b108..118c489 100644 (file)
@@ -6834,10 +6834,14 @@ void HashTable<Shape, Key>::IterateElements(ObjectVisitor* v) {
 
 
 template<typename Shape, typename Key>
-Object* HashTable<Shape, Key>::Allocate(
-    int at_least_space_for) {
+Object* HashTable<Shape, Key>::Allocate(int at_least_space_for) {
   int capacity = RoundUpToPowerOf2(at_least_space_for);
-  if (capacity < 4) capacity = 4;  // Guarantee min capacity.
+  if (capacity < 4) {
+    capacity = 4;  // Guarantee min capacity.
+  } else if (capacity > HashTable::kMaxCapacity) {
+    return Failure::OutOfMemoryException();
+  }
+
   Object* obj = Heap::AllocateHashTable(EntryToIndex(capacity));
   if (!obj->IsFailure()) {
     HashTable::cast(obj)->SetNumberOfElements(0);
index cf883e4..b4d638d 100644 (file)
@@ -1508,6 +1508,10 @@ class JSObject: public HeapObject {
 #endif
   Object* SlowReverseLookup(Object* value);
 
+  // Maximal number of elements (numbered 0 .. kMaxElementCount - 1).
+  // Also maximal value of JSArray's length property.
+  static const uint32_t kMaxElementCount = 0xffffffffu;
+
   static const uint32_t kMaxGap = 1024;
   static const int kMaxFastElementsLength = 5000;
   static const int kInitialMaxFastElementArray = 100000;
@@ -1634,8 +1638,14 @@ class FixedArray: public Array {
   // Casting.
   static inline FixedArray* cast(Object* obj);
 
-  // Align data at kPointerSize, even if Array.kHeaderSize isn't aligned.
-  static const int kHeaderSize = POINTER_SIZE_ALIGN(Array::kHeaderSize);
+  static const int kHeaderSize = Array::kAlignedSize;
+
+  // Maximal allowed size, in bytes, of a single FixedArray.
+  // Prevents overflowing size computations, as well as extreme memory
+  // consumption.
+  static const int kMaxSize = 512 * MB;
+  // Maximally allowed length of a FixedArray.
+  static const int kMaxLength = (kMaxSize - kHeaderSize) / kPointerSize;
 
   // Dispatched behavior.
   int FixedArraySize() { return SizeFor(length()); }
@@ -1948,6 +1958,12 @@ class HashTable: public FixedArray {
   // Constant used for denoting a absent entry.
   static const int kNotFound = -1;
 
+  // Maximal capacity of HashTable. Based on maximal length of underlying
+  // FixedArray. Staying below kMaxCapacity also ensures that EntryToIndex
+  // cannot overflow.
+  static const int kMaxCapacity =
+      (FixedArray::kMaxLength - kElementsStartOffset) / kEntrySize;
+
   // Find entry for key otherwise return -1.
   int FindEntry(Key key);
 
@@ -1978,6 +1994,7 @@ class HashTable: public FixedArray {
     // use bit-wise AND with a mask, so the capacity must be positive
     // and non-zero.
     ASSERT(capacity > 0);
+    ASSERT(capacity <= kMaxCapacity);
     fast_set(this, kCapacityIndex, Smi::FromInt(capacity));
   }
 
@@ -2325,6 +2342,11 @@ class ByteArray: public Array {
   static const int kHeaderSize = Array::kHeaderSize;
   static const int kAlignedSize = Array::kAlignedSize;
 
+  // Maximal memory consumption for a single ByteArray.
+  static const int kMaxSize = 512 * MB;
+  // Maximal length of a single ByteArray.
+  static const int kMaxLength = kMaxSize - kHeaderSize;
+
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(ByteArray);
 };
@@ -4042,6 +4064,12 @@ class SeqAsciiString: public SeqString {
   static const int kHeaderSize = String::kSize;
   static const int kAlignedSize = POINTER_SIZE_ALIGN(kHeaderSize);
 
+  // Maximal memory usage for a single sequential ASCII string.
+  static const int kMaxSize = 512 * MB;
+  // Maximal length of a single sequential ASCII string.
+  // Q.v. String::kMaxLength which is the maximal size of concatenated strings.
+  static const int kMaxLength = (kMaxSize - kHeaderSize);
+
   // Support for StringInputBuffer.
   inline void SeqAsciiStringReadBlockIntoBuffer(ReadBlockBuffer* buffer,
                                                 unsigned* offset,
@@ -4088,6 +4116,12 @@ class SeqTwoByteString: public SeqString {
   static const int kHeaderSize = String::kSize;
   static const int kAlignedSize = POINTER_SIZE_ALIGN(kHeaderSize);
 
+  // Maximal memory usage for a single sequential two-byte string.
+  static const int kMaxSize = 512 * MB;
+  // Maximal length of a single sequential two-byte string.
+  // Q.v. String::kMaxLength which is the maximal size of concatenated strings.
+  static const int kMaxLength = (kMaxSize - kHeaderSize) / sizeof(uint16_t);
+
   // Support for StringInputBuffer.
   inline void SeqTwoByteStringReadBlockIntoBuffer(ReadBlockBuffer* buffer,
                                                   unsigned* offset_ptr,
index 9472890..b69dca5 100644 (file)
@@ -5386,8 +5386,8 @@ class ArrayConcatVisitor {
       fast_elements_(fast_elements), index_offset_(0) { }
 
   void visit(uint32_t i, Handle<Object> elm) {
-    uint32_t index = i + index_offset_;
-    if (index >= index_limit_) return;
+    if (i >= index_limit_ - index_offset_) return;
+    uint32_t index = index_offset_ + i;
 
     if (fast_elements_) {
       ASSERT(index < static_cast<uint32_t>(storage_->length()));
@@ -5403,16 +5403,23 @@ class ArrayConcatVisitor {
   }
 
   void increase_index_offset(uint32_t delta) {
-    index_offset_ += delta;
+    if (index_limit_ - index_offset_ < delta) {
+      index_offset_ = index_limit_;
+    } else {
+      index_offset_ += delta;
+    }
   }
 
   Handle<FixedArray> storage() { return storage_; }
 
  private:
   Handle<FixedArray> storage_;
+  // Limit on the accepted indices. Elements with indices larger than the
+  // limit are ignored by the visitor.
   uint32_t index_limit_;
-  bool fast_elements_;
+  // Index after last seen index. Always less than or equal to index_limit_.
   uint32_t index_offset_;
+  bool fast_elements_;
 };
 
 
@@ -5584,6 +5591,11 @@ static uint32_t IterateElements(Handle<JSObject> receiver,
  *
  * If a ArrayConcatVisitor object is given, the visitor is called with
  * parameters, element's index + visitor_index_offset and the element.
+ *
+ * The returned number of elements is an upper bound on the actual number
+ * of elements added. If the same element occurs in more than one object
+ * in the array's prototype chain, it will be counted more than once, but
+ * will only occur once in the result.
  */
 static uint32_t IterateArrayAndPrototypeElements(Handle<JSArray> array,
                                                  ArrayConcatVisitor* visitor) {
@@ -5606,8 +5618,14 @@ static uint32_t IterateArrayAndPrototypeElements(Handle<JSArray> array,
   uint32_t nof_elements = 0;
   for (int i = objects.length() - 1; i >= 0; i--) {
     Handle<JSObject> obj = objects[i];
-    nof_elements +=
+    uint32_t encountered_elements =
         IterateElements(Handle<JSObject>::cast(obj), range, visitor);
+
+    if (encountered_elements > JSObject::kMaxElementCount - nof_elements) {
+      nof_elements = JSObject::kMaxElementCount;
+    } else {
+      nof_elements += encountered_elements;
+    }
   }
 
   return nof_elements;
@@ -5624,13 +5642,16 @@ static uint32_t IterateArrayAndPrototypeElements(Handle<JSArray> array,
  * elements.  If an argument is not an Array object, the function
  * visits the object as if it is an one-element array.
  *
- * If the result array index overflows 32-bit integer, the rounded
+ * If the result array index overflows 32-bit unsigned integer, the rounded
  * non-negative number is used as new length. For example, if one
  * array length is 2^32 - 1, second array length is 1, the
  * concatenated array length is 0.
+ * TODO(lrn) Change length behavior to ECMAScript 5 specification (length
+ * is one more than the last array index to get a value assigned).
  */
 static uint32_t IterateArguments(Handle<JSArray> arguments,
                                  ArrayConcatVisitor* visitor) {
+  const uint32_t max_length = JSObject::kMaxElementCount;
   uint32_t visited_elements = 0;
   uint32_t num_of_args = static_cast<uint32_t>(arguments->length()->Number());
 
@@ -5643,16 +5664,23 @@ static uint32_t IterateArguments(Handle<JSArray> arguments,
           IterateArrayAndPrototypeElements(array, visitor);
       // Total elements of array and its prototype chain can be more than
       // the array length, but ArrayConcat can only concatenate at most
-      // the array length number of elements.
-      visited_elements += (nof_elements > len) ? len : nof_elements;
+      // the array length number of elements. We use the length as an estimate
+      // for the actual number of elements added.
+      uint32_t added_elements = (nof_elements > len) ? len : nof_elements;
+      if (JSArray::kMaxElementCount - visited_elements < added_elements) {
+        visited_elements = JSArray::kMaxElementCount;
+      } else {
+        visited_elements += added_elements;
+      }
       if (visitor) visitor->increase_index_offset(len);
-
     } else {
       if (visitor) {
         visitor->visit(0, obj);
         visitor->increase_index_offset(1);
       }
-      visited_elements++;
+      if (visited_elements < JSArray::kMaxElementCount) {
+        visited_elements++;
+      }
     }
   }
   return visited_elements;
@@ -5662,6 +5690,8 @@ static uint32_t IterateArguments(Handle<JSArray> arguments,
 /**
  * Array::concat implementation.
  * See ECMAScript 262, 15.4.4.4.
+ * TODO(lrn): Fix non-compliance for very large concatenations and update to
+ * following the ECMAScript 5 specification.
  */
 static Object* Runtime_ArrayConcat(Arguments args) {
   ASSERT(args.length() == 1);
@@ -5678,12 +5708,18 @@ static Object* Runtime_ArrayConcat(Arguments args) {
   { AssertNoAllocation nogc;
     for (uint32_t i = 0; i < num_of_args; i++) {
       Object* obj = arguments->GetElement(i);
+      uint32_t length_estimate;
       if (obj->IsJSArray()) {
-        result_length +=
+        length_estimate =
             static_cast<uint32_t>(JSArray::cast(obj)->length()->Number());
       } else {
-        result_length++;
+        length_estimate = 1;
+      }
+      if (JSObject::kMaxElementCount - result_length < length_estimate) {
+        result_length = JSObject::kMaxElementCount;
+        break;
       }
+      result_length += length_estimate;
     }
   }
 
index 08ee16f..374385b 100644 (file)
@@ -40,6 +40,7 @@ namespace internal {
 // Implementation is from "Hacker's Delight" by Henry S. Warren, Jr.,
 // figure 3-3, page 48, where the function is called clp2.
 uint32_t RoundUpToPowerOf2(uint32_t x) {
+  ASSERT(x <= 0x80000000u);
   x = x - 1;
   x = x | (x >> 1);
   x = x | (x >> 2);