It would be better to bring this to security-dev to discuss.
-Alan
On 11/03/2026 12:05, wenshao wrote:
Hi,
I found that javax.crypto.Mac.doFinal(byte[], int) does not validate
negative outOffset, causing it to throw ArrayIndexOutOfBoundsException
instead of a proper exception.
The bug:
// Mac.java, line 624
if (output == null || output.length - outOffset < macLen) {
throw new ShortBufferException("Cannot store MAC in output buffer");
}
byte[] mac = doFinal();
System.arraycopy(mac, 0, output, outOffset, macLen);
When outOffset = -1 and output.length = 96:
- 96 - (-1) = 97 >= 32 → check passes
- System.arraycopy(mac, 0, output, -1, 32) → throws
ArrayIndexOutOfBoundsException
This is inconsistent with the rest of the JCE API:
- Mac.update(byte[], int, int) checks offset < 0 → IllegalArgumentException
- Cipher.doFinal(byte[], int) checks outputOffset < 0 →
IllegalArgumentException
- Signature.sign(byte[], int, int) checks offset < 0 →
IllegalArgumentException
Reproducer:
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(new byte[32], "HmacSHA256"));
mac.update(new byte[]{1, 2, 3});
mac.doFinal(new byte[96], -1);
// Expected: IllegalArgumentException or ShortBufferException
// Actual: ArrayIndexOutOfBoundsException
The fix:
Separate null/negative-offset validation (IllegalArgumentException) from
buffer-too-small validation (ShortBufferException):
if (output == null || outOffset < 0) {
throw new IllegalArgumentException("Bad arguments");
}
int macLen = getMacLength();
if (output.length - outOffset < macLen) {
throw new ShortBufferException("Cannot store MAC in output buffer");
}
The patch includes a jtreg test covering: offset=-1, Integer.MIN_VALUE,
null output, small buffer, and valid offsets.
Webrev: https://github.com/wenshao/jdk/tree/fix/mac-doFinal-negative-offset
<https://github.com/wenshao/jdk/tree/fix/mac-doFinal-negative-offset >
Thanks,
Shaojin Wen