{"record":{"id":"7d5651b33b4d3058","repo":"pinpoint-apm/pinpoint","slug":"invalid-varlong-start-offset-offset-readoffset-7d5651","errorCode":null,"errorMessage":"invalid varLong. start offset:${offset} readOffset:${offset}","messagePattern":"invalid varLong\\. start offset:(.+?) readOffset:(.+?)","errorType":"exception","errorClass":"ArrayIndexOutOfBoundsException","httpStatus":null,"severity":"error","filePath":"commons/src/main/java/com/navercorp/pinpoint/common/util/BytesUtils.java","lineNumber":250,"sourceCode":"            }\n            return x;\n        }\n        return readVar64SlowPath(buffer, offset);\n    }\n\n    /** Variant of readRawVarint64 for when uncomfortably close to the limit. */\n    /* Visible for testing */\n    static long readVar64SlowPath(final byte[] buffer, int offset) {\n\n        long result = 0;\n        for (int shift = 0; shift < 64; shift += 7) {\n            final byte b = buffer[offset++];\n            result |= (long) (b & 0x7F) << shift;\n            if ((b & 0x80) == 0) {\n                return result;\n            }\n        }\n        throw new ArrayIndexOutOfBoundsException(\"invalid varLong. start offset:\" +  offset + \" readOffset:\" + offset);\n    }\n\n    public static short bytesToShort(final byte byte1, final byte byte2) {\n        return (short) (((byte1 & 0xff) << 8) | ((byte2 & 0xff)));\n    }\n\n\n    public static int writeLong(final long value, final byte[] buf, int offset) {\n        if (buf == null) {\n            throw new NullPointerException(\"buf\");\n        }\n        checkBounds(buf, offset, LONG_BYTE_LENGTH);\n\n        buf[offset++] = (byte) (value >> 56);\n        buf[offset++] = (byte) (value >> 48);\n        buf[offset++] = (byte) (value >> 40);\n        buf[offset++] = (byte) (value >> 32);\n        buf[offset++] = (byte) (value >> 24);","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/commons/src/main/java/com/navercorp/pinpoint/common/util/BytesUtils.java#L232-L268","documentation":"BytesUtils.readVar64SlowPath decodes a LEB128-style variable-length integer (varint/varLong) from a byte array. After 10 continuation bytes (shift reaching 64) without encountering a byte whose high bit is 0, the encoded value is malformed, and the method throws ArrayIndexOutOfBoundsException with 'invalid varLong'. This means the byte stream is corrupted, truncated, or the caller passed a wrong offset. It is thrown by bytesToVar32/bytesToVar64 when reading agents' binary payloads (e.g.agent metadata, network-encoded values).","triggerScenarios":"Calling BytesUtils.bytesToVar32(buffer, offset) or BytesUtils.bytesToVar64(buffer, offset) with (a) 10 or more consecutive bytes all having the continuation bit (0x80) set starting at offset, (b) a buffer truncated so buffer[offset++] itself goes out of bounds while scanning, or (c) a misaligned offset that lands mid-varint so the terminator byte is never seen within 10 bytes.","commonSituations":"Deserializing a Pinpoint agent's binary column or network payload that was written by a different/incompatible version (varint encoder changed), reading from an offset not returned by a previous varint read (manual offset bookkeeping off by one), passing a byte array slice that cuts off mid-varint, or feeding corrupted/truncated data from storage or the wire into bytesToVar64/bytesToVar32.","solutions":["Verify the offset passed to bytesToVar32/bytesToVar64 points at the first byte of a varint, not mid-value; recompute offsets from prior read lengths.","Check that the byte array is complete and not truncated before decoding; confirm the writer and reader use the same Pinpoint version and matching bytesToVar/writeVar (var32 vs var64) pair.","Validate the input bytes: at most 10 bytes for a 64-bit varint and the 10th byte must have high bit 0; reject payloads violating this before calling the API.","Catch ArrayIndexOutOfBoundsException around the decode call and treat the payload as corrupt: log offset and skip/discard the record instead of crashing.","If data comes from the wire, add a checksum/length prefix when writing so truncated or shifted frames can be detected before parsing."],"exampleFix":"// before: decode without validation, mid-array offset from manual bookkeeping\nlong value = BytesUtils.bytesToVar64(buffer, offset);\n\n// after: bounds + continuation-bit sanity check before decoding\nlong value;\nif (offset < 0 || offset >= buffer.length) {\n    throw new IllegalArgumentException(\"bad offset: \" + offset);\n}\nint maxBytes = Math.min(10, buffer.length - offset);\nboolean terminated = false;\nfor (int i = 0; i < maxBytes; i++) {\n    if ((buffer[offset + i] & 0x80) == 0) { terminated = true; break; }\n}\nif (!terminated) {\n    throw new IllegalArgumentException(\"malformed varLong at offset \" + offset);\n}\nvalue = BytesUtils.bytesToVar64(buffer, offset);","handlingStrategy":"validation","validationCode":"// Pre-validate before calling BytesUtils.bytesToVar64/bytesToVar32\npublic static boolean isValidVarLongAt(byte[] buffer, int offset) {\n    if (buffer == null || offset < 0 || offset >= buffer.length) return false;\n    int max = Math.min(10, buffer.length - offset);\n    for (int i = 0; i < max; i++) {\n        if ((buffer[offset + i] & 0x80) == 0) return true; // terminator found\n    }\n    return false; // 10 continuation bytes or buffer ends mid-varint\n}","typeGuard":"// Java has no runtime type narrowing; guard structurally with an Optional-style check\npublic static Integer safeVar32Offset(byte[] buffer, int offset) {\n    return isValidVarLongAt(buffer, offset) ? Integer.valueOf(offset) : null;\n}","tryCatchPattern":"try {\n    long value = BytesUtils.bytesToVar64(buffer, offset);\n    // use value\n} catch (ArrayIndexOutOfBoundsException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"invalid varLong\")) {\n        log.warn(\"Corrupt varLong in payload at offset {}, discarding record\", offset, e);\n        return null; // or skip record / mark payload corrupt\n    }\n    throw e;\n}","preventionTips":["Always derive the next offset from the value returned by each varint read instead of hard-coding byte counts.","Ensure write and read sides use the same BytesUtils.writeVar/bytesToVar pair and the same Pinpoint version.","Never truncate byte arrays mid-record; length-prefix frames before writing to the wire or storage.","Validate a varint is terminated within 10 bytes before decoding it.","Wrap binary deserialization of external/untrusted payloads in error handling that quarantines bad records rather than aborting the batch."],"tags":["varint","byte-array","deserialization","index-out-of-bounds","data-corruption"],"backgroundTag":"invalid-argument-format","analyzedSha":"744c3d3075e595656abb1ae331ad2c0e4c9eb996","analyzedAt":"2026-09-07T18:48:45.289Z","contentChangedAt":"2026-09-07T18:48:45.289Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}