Fix bdecode busy-loop on truncated torrent payloads.

Malformed bencode could set pos.i to NaN and hang Jest forever;
guard the decoder and align the no-url-list fixture with current qbt behavior.
This commit is contained in:
2026-08-07 18:47:55 +02:00
parent 5b71d9f54e
commit ac97862edb
3 changed files with 71 additions and 34 deletions

View File

@@ -17,38 +17,57 @@ function bencode(v) {
]);
}
// Minimal bencode decoder — returns Buffers for byte strings
// Minimal bencode decoder — returns Buffers for byte strings.
// Throws on truncated / malformed input (never busy-loops).
function bdecode(buf, pos = { i: 0 }) {
if (!Buffer.isBuffer(buf) || pos.i < 0 || pos.i >= buf.length) {
throw new Error('bdecode: truncated');
}
const c = buf[pos.i];
if (c === 0x69) { // 'i'
pos.i++;
const end = buf.indexOf(0x65, pos.i); // 'e'
if (end < 0) throw new Error('bdecode: bad int');
const n = parseInt(buf.slice(pos.i, end).toString(), 10);
if (!Number.isFinite(n)) throw new Error('bdecode: bad int');
pos.i = end + 1;
return n;
}
if (c === 0x6c) { // 'l'
pos.i++;
const arr = [];
while (buf[pos.i] !== 0x65) arr.push(bdecode(buf, pos));
while (pos.i < buf.length && buf[pos.i] !== 0x65) {
arr.push(bdecode(buf, pos));
}
if (pos.i >= buf.length || buf[pos.i] !== 0x65) {
throw new Error('bdecode: unclosed list');
}
pos.i++;
return arr;
}
if (c === 0x64) { // 'd'
pos.i++;
const obj = {};
while (buf[pos.i] !== 0x65) {
while (pos.i < buf.length && buf[pos.i] !== 0x65) {
const key = bdecode(buf, pos).toString('utf8');
obj[key] = bdecode(buf, pos);
}
if (pos.i >= buf.length || buf[pos.i] !== 0x65) {
throw new Error('bdecode: unclosed dict');
}
pos.i++;
return obj;
}
// byte string: "N:..."
const colon = buf.indexOf(0x3a, pos.i); // ':'
if (colon < 0 || colon >= buf.length) throw new Error('bdecode: bad string');
const len = parseInt(buf.slice(pos.i, colon).toString(), 10);
pos.i = colon + 1 + len;
return buf.slice(colon + 1, pos.i);
if (!Number.isFinite(len) || len < 0) throw new Error('bdecode: bad string length');
const start = colon + 1;
const end = start + len;
if (end > buf.length) throw new Error('bdecode: string past end');
pos.i = end;
return buf.slice(start, end);
}
// Build a minimal .torrent with an HTTP webseed and zeroed piece hashes.