denoland/deno · error

input length exceeds i32::MAX

Error message

input length exceeds i32::MAX

What it means

Deno's node:crypto chacha20-poly1305 cipher passes each update() input to EVP_CipherUpdate, whose length parameter is a C int. encrypt() converts input.len() to i32 and this expect() panics when a single plaintext chunk handed to cipher.update() is larger than 2147483647 bytes (2 GiB). The panic occurs in native code during the update call and aborts the process.

Source

Thrown at ext/node_crypto/cipher.rs:409

          );
          assert_eq!(ret, 1, "EVP_CipherUpdate for AAD failed");
        }
      }
    }
  }

  fn encrypt(&mut self, input: &[u8], output: &mut [u8]) {
    assert!(output.len() >= input.len());
    self.flush_aad();
    // SAFETY: ctx is valid and initialized for encryption. output is
    // caller-provided with at least input.len() bytes. EVP_CipherUpdate
    // writes at most input.len() bytes for a stream cipher.
    // Length is validated to fit in i32 before casting.
    unsafe {
      let input_len: i32 = input
        .len()
        .try_into()
        .expect("input length exceeds i32::MAX");
      let mut outl: i32 = 0;
      let ret = aws_lc_sys::EVP_CipherUpdate(
        self.ctx,
        output.as_mut_ptr(),
        &mut outl,
        input.as_ptr(),
        input_len,
      );
      assert_eq!(ret, 1, "EVP_CipherUpdate for encryption failed");
    }
  }

  fn decrypt(&mut self, input: &[u8], output: &mut [u8]) {
    assert!(output.len() >= input.len());
    self.flush_aad();
    // SAFETY: ctx is valid and initialized for decryption. output is
    // caller-provided with at least input.len() bytes.
    // Length is validated to fit in i32 before casting.

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Encrypt in chunks well under 2 GiB per update() call — e.g. 4–64 MiB slices: `for (let o = 0; o < data.length; o += CHUNK) cipher.update(data.subarray(o, Math.min(o + CHUNK, data.length)));`
  2. Stream the file instead of buffering it: read with fs.createReadStream and pipe chunks through cipher.update().
  3. Validate input size before calling update() and throw a clear JS error (you control the message) instead of letting the process abort.
  4. For one-shot small payloads (the normal case) nothing changes; guard only the large-file path.

Example fix

// before
const data = await Deno.readFile("big.bin"); // 3 GiB
cipher.update(data); // single >2GiB call -> panic

// after
const CHUNK = 64 * 1024 * 1024;
for (let o = 0; o < data.length; o += CHUNK) {
  cipher.update(data.subarray(o, Math.min(o + CHUNK, data.length)));
}
Defensive patterns

Strategy: validation

Validate before calling

const MAX_I32 = 2 ** 31 - 1;
const CHUNK = 64 * 1024 * 1024;
function updateChunked(cipher, data) {
  if (data.byteLength > MAX_I32) {
    for (let o = 0; o < data.length; o += CHUNK) {
      cipher.update(data.subarray(o, Math.min(o + CHUNK, data.length)));
    }
  } else {
    cipher.update(data);
  }
}

Prevention

When it happens

Trigger: crypto.createCipheriv("chacha20-poly1305", key, iv) followed by cipher.update(buf) where buf is a single Buffer/TypedArray larger than 2 GiB — typically the result of reading an entire large file into memory (await Deno.readFile / fs.promises.readFile) and encrypting it in one call.

Common situations: Backup/sync scripts that read whole files >2 GiB into one Buffer; test code generating one giant buffer; porting Node scripts after a Deno upgrade where sizes previously worked via a different cipher path; concatenating stream chunks into one buffer before encrypting.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/2881c6d45917b858. Report an issue: GitHub.