-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Description
Describe the bug
The TLVParser library mishandles BER (Basic Encoding Rules) long-form lengths in two ways: it fails to advance past the initial length byte, and it reads multi-byte lengths in little-endian order instead of big-endian.
src/core/lib/TLVParser.mjs, getLength() method, lines 37-52
getLength() {
if (this.basicEncodingRules) {
const bit = this.input[this.location];
if (bit & 0x80) {
this.bytesInLength = bit & ~0x80;
} else {
this.location++;
return bit & ~0x80;
}
}
let length = 0;
for (let i = 0; i < this.bytesInLength; i++) {
length += this.input[this.location] * Math.pow(Math.pow(2, 8), i);
this.location++;
}
return length;
}In BER, when the first length byte has bit 7 set (long form), the low 7 bits encode the number of subsequent bytes that hold the actual length, in big-endian order. Two bugs:
-
Missing location advance. In the short-form branch (
else),this.location++skips the length byte before returning. In the long-form branch (if),this.locationis never advanced, so theforloop re-reads the initial indicator byte as part of the length value, corrupting the result and misaligning all subsequent TLV parsing. -
Wrong byte order. The
forloop accumulates bytes withMath.pow(256, i), weighting byte 0 (the first byte read) as the least significant. BER specifies big-endian: the first byte after the indicator is the most significant.
A secondary issue: the code mutates this.bytesInLength, a persistent instance field, which corrupts length parsing for all subsequent TLV entries on the same parser instance.
To Reproduce
use Parse TLV with BER encoding on any input containing a long-form length (e.g., a length of 256, encoded as 82 01 00). The parser will misread the length and produce garbled output.
Additional context
BER documentation:
https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/basic-encoding-rules.html
Suggested fix:
getLength() {
let bytesInLength = this.bytesInLength;
let bigEndian = false;
if (this.basicEncodingRules) {
const firstLengthByte = this.input[this.location];
this.location++;
if (firstLengthByte & 0x80) {
bytesInLength = firstLengthByte & ~0x80;
bigEndian = true;
} else {
return firstLengthByte & ~0x80;
}
}
let length = 0;
for (let i = 0; i < bytesInLength; i++) {
if (bigEndian) {
length = (length << 8) + this.input[this.location];
} else {
length += this.input[this.location] * Math.pow(Math.pow(2, 8), i);
}
this.location++;
}
return length;
}This always advances past the initial byte, uses big-endian accumulation for BER, preserves little-endian for non-BER callers, and uses a local variable to avoid mutating instance state.