VYPR
Low severity2.0NVD Advisory· Published Jul 8, 2024· Updated Apr 15, 2026

CVE-2024-38372

CVE-2024-38372

Description

Undici is an HTTP/1.1 client, written from scratch for Node.js. Depending on network and process conditions of a fetch() request, response.arrayBuffer() might include portion of memory from the Node.js process. This has been patched in v6.19.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
undicinpm
>= 6.14.0, < 6.19.26.19.2

Patches

1
f979ec3204ca

perf: avoid unnecessary clone (#3117)

https://github.com/nodejs/undicitsctxApr 15, 2024via ghsa
3 files changed · +46 13
  • benchmarks/fetch/body-arraybuffer.mjs+24 0 added
    @@ -0,0 +1,24 @@
    +import { group, bench, run } from 'mitata'
    +import { Response } from '../../lib/web/fetch/response.js'
    +
    +const settings = {
    +  small: 2 << 8,
    +  middle: 2 << 12,
    +  long: 2 << 16
    +}
    +
    +for (const [name, length] of Object.entries(settings)) {
    +  const buffer = Buffer.allocUnsafe(length).map(() => (Math.random() * 100) | 0)
    +  group(`${name} (length ${length})`, () => {
    +    bench('Response#arrayBuffer', async () => {
    +      return await new Response(buffer).arrayBuffer()
    +    })
    +
    +    // for comparison
    +    bench('Response#text', async () => {
    +      return await new Response(buffer).text()
    +    })
    +  })
    +}
    +
    +await run()
    
  • lib/web/fetch/body.js+11 9 modified
    @@ -309,7 +309,7 @@ function bodyMixinMethods (instance) {
             // Return a Blob whose contents are bytes and type attribute
             // is mimeType.
             return new Blob([bytes], { type: mimeType })
    -      }, instance)
    +      }, instance, false)
         },
     
         arrayBuffer () {
    @@ -318,20 +318,21 @@ function bodyMixinMethods (instance) {
           // given a byte sequence bytes: return a new ArrayBuffer
           // whose contents are bytes.
           return consumeBody(this, (bytes) => {
    -        return new Uint8Array(bytes).buffer
    -      }, instance)
    +        // Note: arrayBuffer already cloned.
    +        return bytes.buffer
    +      }, instance, true)
         },
     
         text () {
           // The text() method steps are to return the result of running
           // consume body with this and UTF-8 decode.
    -      return consumeBody(this, utf8DecodeBytes, instance)
    +      return consumeBody(this, utf8DecodeBytes, instance, false)
         },
     
         json () {
           // The json() method steps are to return the result of running
           // consume body with this and parse JSON from bytes.
    -      return consumeBody(this, parseJSONFromBytes, instance)
    +      return consumeBody(this, parseJSONFromBytes, instance, false)
         },
     
         formData () {
    @@ -383,7 +384,7 @@ function bodyMixinMethods (instance) {
             throw new TypeError(
               'Content-Type was not one of "multipart/form-data" or "application/x-www-form-urlencoded".'
             )
    -      }, instance)
    +      }, instance, false)
         }
       }
     
    @@ -399,8 +400,9 @@ function mixinBody (prototype) {
      * @param {Response|Request} object
      * @param {(value: unknown) => unknown} convertBytesToJSValue
      * @param {Response|Request} instance
    + * @param {boolean} [shouldClone]
      */
    -async function consumeBody (object, convertBytesToJSValue, instance) {
    +async function consumeBody (object, convertBytesToJSValue, instance, shouldClone) {
       webidl.brandCheck(object, instance)
     
       // 1. If object is unusable, then return a promise rejected
    @@ -432,13 +434,13 @@ async function consumeBody (object, convertBytesToJSValue, instance) {
       // 5. If object’s body is null, then run successSteps with an
       //    empty byte sequence.
       if (object[kState].body == null) {
    -    successSteps(new Uint8Array())
    +    successSteps(Buffer.allocUnsafe(0))
         return promise.promise
       }
     
       // 6. Otherwise, fully read object’s body given successSteps,
       //    errorSteps, and object’s relevant global object.
    -  await fullyReadBody(object[kState].body, successSteps, errorSteps)
    +  await fullyReadBody(object[kState].body, successSteps, errorSteps, shouldClone)
     
       // 7. Return promise.
       return promise.promise
    
  • lib/web/fetch/util.js+11 4 modified
    @@ -1043,7 +1043,7 @@ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueInde
     /**
      * @see https://fetch.spec.whatwg.org/#body-fully-read
      */
    -async function fullyReadBody (body, processBody, processBodyError) {
    +async function fullyReadBody (body, processBody, processBodyError, shouldClone) {
       // 1. If taskDestination is null, then set taskDestination to
       //    the result of starting a new parallel queue.
     
    @@ -1069,8 +1069,7 @@ async function fullyReadBody (body, processBody, processBodyError) {
     
       // 5. Read all bytes from reader, given successSteps and errorSteps.
       try {
    -    const result = await readAllBytes(reader)
    -    successSteps(result)
    +    successSteps(await readAllBytes(reader, shouldClone))
       } catch (e) {
         errorSteps(e)
       }
    @@ -1118,8 +1117,9 @@ function isomorphicEncode (input) {
      * @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
      * @see https://streams.spec.whatwg.org/#read-loop
      * @param {ReadableStreamDefaultReader} reader
    + * @param {boolean} [shouldClone]
      */
    -async function readAllBytes (reader) {
    +async function readAllBytes (reader, shouldClone) {
       const bytes = []
       let byteLength = 0
     
    @@ -1128,6 +1128,13 @@ async function readAllBytes (reader) {
     
         if (done) {
           // 1. Call successSteps with bytes.
    +      if (bytes.length === 1) {
    +        const { buffer, byteOffset, byteLength } = bytes[0]
    +        if (shouldClone === false) {
    +          return Buffer.from(buffer, byteOffset, byteLength)
    +        }
    +        return Buffer.from(buffer.slice(byteOffset, byteOffset + byteLength), 0, byteLength)
    +      }
           return Buffer.concat(bytes, byteLength)
         }
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.