{"record":{"id":"3235cf27bcfadef4","repo":"apache/iceberg","slug":"insufficient-bytes-in-buffer-b-length-off","errorCode":null,"errorMessage":"Insufficient bytes in buffer: ${b.length} - ${off} < ${len}","messagePattern":"Insufficient bytes in buffer: (.+?) - (.+?) < (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/encryption/AesGcmOutputStream.java","lineNumber":82,"sourceCode":"\n  @Override\n  public void write(int b) throws IOException {\n    singleByte[0] = (byte) (b & 0x000000FF);\n    write(singleByte);\n  }\n\n  @Override\n  public void write(byte[] b, int off, int len) throws IOException {\n    if (isClosed) {\n      throw new IOException(\"Writing to closed stream\");\n    }\n\n    if (!isHeaderWritten) {\n      writeHeader();\n    }\n\n    if (b.length - off < len) {\n      throw new IOException(\n          \"Insufficient bytes in buffer: \" + b.length + \" - \" + off + \" < \" + len);\n    }\n\n    int remaining = len;\n    int offset = off;\n\n    while (remaining > 0) {\n      int freeBlockBytes = plainBlock.length - positionInPlainBlock;\n      int toWrite = Math.min(freeBlockBytes, remaining);\n\n      System.arraycopy(b, offset, plainBlock, positionInPlainBlock, toWrite);\n      positionInPlainBlock += toWrite;\n      offset += toWrite;\n      remaining -= toWrite;\n\n      if (positionInPlainBlock == plainBlock.length) {\n        encryptAndWriteBlock();\n      }","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/encryption/AesGcmOutputStream.java#L64-L100","documentation":"write(byte[], int off, int len) validates that the buffer actually holds len bytes starting at off (b.length - off >= len). If not, the requested range would run past the end of the array, so it throws IOException before touching the cipher.","triggerScenarios":"Calling write(b, off, len) where off + len > b.length, e.g. passing a wrong off, a len taken from another buffer's length, or a slice length computed incorrectly.","commonSituations":"Wrapping AesGcmOutputStream in code that copies buffer-handling arithmetic from a different stream; off-by-one errors when encrypting a sub-range; passing the capacity rather than the filled length of a reusable buffer.","solutions":["Fix off/len so that off + len <= b.length","Pass the buffer's actual used length, not its capacity or another buffer's length","Validate the range in the caller before invoking write"],"exampleFix":"// before\nout.write(buffer, 0, otherBuffer.length);\n// after\nout.write(buffer, 0, buffer.length);","handlingStrategy":"validation","validationCode":"static void checkRange(byte[] b, int off, int len) {\n  if (off < 0 || len < 0 || b.length - off < len) {\n    throw new IllegalArgumentException(\"off+len exceeds buffer: \" + off + \"+\" + len + \">\" + b.length);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  out.write(buf, off, len);\n} catch (IOException e) {\n  if (e.getMessage().startsWith(\"Insufficient bytes in buffer\")) {\n    throw new IllegalArgumentException(\"bad off/len for buffer of size \" + buf.length, e);\n  } else { throw e; }\n}","preventionTips":["Always derive len from the same buffer you pass","Never pass another buffer's length/capacity as len","Validate off + len <= b.length at call sites that compute slices dynamically"],"tags":["io","stream","buffer","argument-validation"],"backgroundTag":"index-out-of-bounds","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}