From b55ee0b5832531f4afa8924f01762f5be195a4be Mon Sep 17 00:00:00 2001 From: Bernd Engelhardt Date: Sat, 5 Sep 2026 11:09:03 +0200 Subject: [PATCH] Allow an upper bound on the number of indirect objects Enumerating a document's objects (getObjects, getObjectsByType, getObjectsMap) materialises one entry per cross-reference key. A small compressed input can declare an extreme number of objects through an object stream, so enumeration can exhaust the heap and fail with an OutOfMemoryError, which is not recoverable and affects the whole process. Add COSDocument.setMaxNumberOfObjects. When a bound is set, a document that declares more indirect objects than the bound is rejected up front with a VeraPDFParserException, before the objects are materialised. The check uses the cross-reference key count, so it costs nothing when no bound is set. Default is unbounded, so behaviour is unchanged unless a caller opts in. --- .../java/org/verapdf/cos/COSDocument.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/main/java/org/verapdf/cos/COSDocument.java b/src/main/java/org/verapdf/cos/COSDocument.java index f373e0a2..2c5143ec 100644 --- a/src/main/java/org/verapdf/cos/COSDocument.java +++ b/src/main/java/org/verapdf/cos/COSDocument.java @@ -50,6 +50,8 @@ public class COSDocument { private static final Logger LOGGER = Logger.getLogger(COSDocument.class.getCanonicalName()); + private static volatile int maxNumberOfObjects = -1; + private PDDocument doc; private IReader reader; private COSHeader header; @@ -136,7 +138,39 @@ public void setHeader(String header) { this.header.setHeader(header); } + /** + * Sets an upper bound on the number of indirect objects the document may declare in its + * cross-reference table. Enumerating the objects materialises one entry per key, so a document that + * declares an extreme number of objects (a small compressed input can, via an object stream) can + * exhaust the heap; with a bound set it is rejected with a {@link VeraPDFParserException} before the + * objects are materialised, instead of failing with an OutOfMemoryError. A negative value (the + * default) removes the bound, keeping the historical behaviour. + * + * @param max maximum number of indirect objects, or a negative value for no bound + */ + public static void setMaxNumberOfObjects(int max) { + maxNumberOfObjects = max; + } + + /** + * @return the current upper bound on the number of indirect objects, or {@code -1} if unbounded + */ + public static int getMaxNumberOfObjects() { + return maxNumberOfObjects; + } + + private void checkObjectCountLimit() { + if (maxNumberOfObjects >= 0) { + int declared = this.xref.getAllKeys().size(); + if (declared > maxNumberOfObjects) { + throw new VeraPDFParserException("Number of indirect objects (" + declared + + ") exceeds the configured maximum of " + maxNumberOfObjects); + } + } + } + public List getObjects() { + checkObjectCountLimit(); List result = new ArrayList<>(); for (COSKey key : this.xref.getAllKeys()) { COSObject obj = this.body.get(key); @@ -161,6 +195,7 @@ public List getObjects() { } public List getObjectsByType(ASAtom type) { + checkObjectCountLimit(); List result = new ArrayList<>(); for (COSKey key : this.xref.getAllKeys()) { COSObject obj = this.body.get(key); @@ -192,6 +227,7 @@ private static void addObjectWithTypeKeyCheck(List objects, } public Map getObjectsMap() { + checkObjectCountLimit(); Map result = new HashMap<>(); for (COSKey key : this.xref.getAllKeys()) { COSObject obj = this.body.get(key);