Move the ByteRange validation earlier when parsing signatures

Given that these checks are synchronous, we can avoid a little bit of unnecessary data-fetching if the `ByteRange` is invalid.
This commit is contained in:
Jonas Jenwald 2026-07-28 15:58:11 +02:00
parent f38e8b659e
commit 1ce8e8a320

View File

@ -2087,6 +2087,28 @@ class PDFDocument {
) {
return null;
}
// Slice the two ByteRange byte spans out of the underlying PDF stream.
// ByteRange = [a, b, c, d] means signed bytes are [a..a+b] and [c..c+d];
// the gap covers the /Contents hex blob itself.
const [a, b, c, d] = byteRange;
// `/ByteRange` offsets are absolute, so compare against `stream.end`
// (raw buffer end), not `stream.length` (post-`moveStart` payload).
const fileLength = this.stream.end || 0;
// Reject signatures whose /ByteRange is structurally implausible: it
// must start at the file head, define a non-empty first span, leave
// room for the /Contents blob between the two spans, and fit within
// the file. Without this a crafted PDF can claim to cover the whole
// document while only signing a small prologue.
if (
a !== 0 ||
b <= 0 ||
a + b > c ||
c + d > fileLength ||
fileLength === 0
) {
return null;
}
const contents = await sigDict.getAsync("Contents");
if (typeof contents !== "string" || contents.length === 0) {
return null;
@ -2106,29 +2128,6 @@ class PDFDocument {
signatureType = 1;
}
// Slice the two ByteRange byte spans out of the underlying PDF stream.
// ByteRange = [a, b, c, d] means signed bytes are [a..a+b] and [c..c+d];
// the gap covers the /Contents hex blob itself.
const [a, b, c, d] = byteRange;
const stream = this.stream;
// `/ByteRange` offsets are absolute, so compare against `stream.end`
// (raw buffer end), not `stream.length` (post-`moveStart` payload).
const fileLength = stream.end || 0;
// Reject signatures whose /ByteRange is structurally implausible: it
// must start at the file head, define a non-empty first span, leave
// room for the /Contents blob between the two spans, and fit within
// the file. Without this a crafted PDF can claim to cover the whole
// document while only signing a small prologue.
if (
a !== 0 ||
b <= 0 ||
a + b > c ||
c + d > fileLength ||
fileLength === 0
) {
return null;
}
const [t, name, reason, location, contactInfo, m] = await Promise.all([
field.getAsync("T"),
sigDict.getAsync("Name"),