CVE-2021-4327: Integer Overflow or Wraparound (CWE-190)
value: A vulnerability was found in SerenityOS. It has been rated as critical. Affected by this issue is the function initialize_typed_array_from_array_buffer in the library Userland/Libraries/LibJS/Runtime/TypedArray.cpp. The manipulation leads to integer overflow. The exploit has been disclosed to the public and may be used. Continious delivery with rolling releases is used by this product. Therefore, no version details of affected nor updated releases are available. The patch is identified as f6c6047e49f1517778f5565681fb64750b14bf60. It is recommended to apply a patch to fix this issue. VDB-222074 is the identifier assigned to this vulnerability.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 9.8
Published: 2023-03-01T11:15Z
Repository: https://github.com/SerenityOS/serenity
Language: C++
Files changed: Userland/Libraries/LibJS/Runtime/TypedArray.cpp, Userland/Libraries/LibJS/Tests/builtins/TypedArray/typed-array-from-ArrayBuffer-bad-offset.js
Commit date: 2021-01-27 07:57:07 +0100
Commit message: LibJS: Add overflow checks when creating TypedArray from ArrayBuffer

Thanks to Iliad for finding this! :^)

Diff: diff --git a/Userland/Libraries/LibJS/Runtime/TypedArray.cpp b/Userland/Libraries/LibJS/Runtime/TypedArray.cpp
index 13ba159848..60783154ce 100644
--- a/Userland/Libraries/LibJS/Runtime/TypedArray.cpp
+++ b/Userland/Libraries/LibJS/Runtime/TypedArray.cpp
@@ -52,32 +52,48 @@ static void initialize_typed_array_from_array_buffer(GlobalObject& global_object
         if (vm.exception())
             return;
     }
     // FIXME: 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
     auto buffer_byte_length = array_buffer.byte_length();
-    size_t new_byte_length;
+    Checked<size_t> new_byte_length;
     if (length.is_undefined()) {
         if (buffer_byte_length % element_size != 0) {
             vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayInvalidBufferLength, typed_array.class_name(), element_size, buffer_byte_length);
             return;
         }
         if (offset > buffer_byte_length) {
             vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayOutOfRangeByteOffset, offset, buffer_byte_length);
             return;
         }
-        new_byte_length = buffer_byte_length - offset;
+        new_byte_length = buffer_byte_length;
+        new_byte_length -= offset;
     } else {
-        new_byte_length = new_length * element_size;
-        if (offset + new_byte_length > buffer_byte_length) {
-            vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayOutOfRangeByteOffsetOrLength, offset, offset + new_byte_length, buffer_byte_length);
+        new_byte_length = new_length;
+        new_byte_length *= element_size;
+
+        Checked<size_t> new_byte_end = new_byte_length;
+        new_byte_end += offset;
+
+        if (new_byte_end.has_overflow()) {
+            vm.throw_exception<RangeError>(global_object, ErrorType::InvalidLength, "typed array");
+            return;
+        }
+
+        if (new_byte_end.value() > buffer_byte_length) {
+            vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayOutOfRangeByteOffsetOrLength, offset, new_byte_end.value(), buffer_byte_length);
             return;
         }
     }
+    if (new_byte_length.has_overflow()) {
+        vm.throw_exception<RangeError>(global_object, ErrorType::InvalidLength, "typed array");
+        return;
+    }
+
     typed_array.set_viewed_array_buffer(&array_buffer);
-    typed_array.set_byte_length(new_byte_length);
+    typed_array.set_byte_length(new_byte_length.value());
     typed_array.set_byte_offset(offset);
-    typed_array.set_array_length(new_byte_length / element_size);
+    typed_array.set_array_length(new_byte_length.value() / element_size);
 }
 
 void TypedArrayBase::visit_edges(Visitor& visitor)
 {
     Object::visit_edges(visitor);
diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/typed-array-from-ArrayBuffer-bad-offset.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/typed-array-from-ArrayBuffer-bad-offset.js
new file mode 100644
index 0000000000..34c8d5d917
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/typed-array-from-ArrayBuffer-bad-offset.js
@@ -0,0 +1,5 @@
+test("construct Uint32Array with zero-length ArrayBuffer and overflowing offset", () => {
+    expect(() => new Uint32Array(new ArrayBuffer(0), 4, 1024 * 1024 * 1024 - 1)).toThrow(
+        RangeError
+    );
+});

Vulnerable code:     size_t new_byte_length;
        new_byte_length = buffer_byte_length - offset;
        new_byte_length = new_length * element_size;
        if (offset + new_byte_length > buffer_byte_length) {
            vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayOutOfRangeByteOffsetOrLength, offset, offset + new_byte_length, buffer_byte_length);
    typed_array.set_byte_length(new_byte_length);
    typed_array.set_array_length(new_byte_length / element_size);
Fixed code:     Checked<size_t> new_byte_length;
        new_byte_length = buffer_byte_length;
        new_byte_length -= offset;
        new_byte_length = new_length;
        new_byte_length *= element_size;

        Checked<size_t> new_byte_end = new_byte_length;
        new_byte_end += offset;

        if (new_byte_end.has_overflow()) {
            vm.throw_exception<RangeError>(global_object, ErrorType::InvalidLength, "typed array");
            return;
        }

        if (new_byte_end.value() > buffer_byte_length) {
            vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayOutOfRangeByteOffsetOrLength, offset, new_byte_end.value(), buffer_byte_length);
    if (new_byte_length.has_overflow()) {
        vm.throw_exception<RangeError>(global_object, ErrorType::InvalidLength, "typed array");
        return;
    }

    typed_array.set_byte_length(new_byte_length.value());
    typed_array.set_array_length(new_byte_length.value() / element_size);
test("construct Uint32Array with zero-length ArrayBuffer and overflowing offset", () => {
    expect(() => new Uint32Array(new ArrayBuffer(0), 4, 1024 * 1024 * 1024 - 1)).toThrow(
        RangeError
    );
});