Skip to content

Commit

Permalink
Fix StackOverflowException when binary property lists contain cyclic …
Browse files Browse the repository at this point in the history
…refernces (e.g. dictionary containing itself)
  • Loading branch information
3breadt committed Nov 24, 2022
1 parent a2deddb commit 850f019
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 15 deletions.
31 changes: 16 additions & 15 deletions src/main/java/com/dd/plist/BinaryPropertyListParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ private NSObject doParse(byte[] data) throws PropertyListFormatException, Unsupp
throw new PropertyListFormatException("The binary property list contains a corrupted object offset table.");
}

return this.parseObject(topObject);
return this.parseObject(ParsedObjectStack.empty(), topObject);
}

/**
Expand All @@ -194,12 +194,14 @@ private NSObject doParse(byte[] data) throws PropertyListFormatException, Unsupp
* <a href="http://www.opensource.apple.com/source/CF/CF-855.17/CFBinaryPList.c">
* Apple's binary property list parser implementation</a>.
*
* @param obj The object ID.
* @param stack The stack to keep track of parsed objects and detect cyclic references.
* @param obj The object ID.
* @return The parsed object.
* @throws PropertyListFormatException When the property list's format could not be parsed.
* @throws java.io.UnsupportedEncodingException If a {@link NSString} object could not be decoded.
*/
private NSObject parseObject(int obj) throws PropertyListFormatException, UnsupportedEncodingException {
private NSObject parseObject(ParsedObjectStack stack, int obj) throws PropertyListFormatException, UnsupportedEncodingException {
stack = stack.push(obj);
int offset = this.getObjectOffset(obj);
byte type = this.bytes[offset];
int objType = (type & 0xF0) >> 4; //First 4 bits
Expand All @@ -222,18 +224,17 @@ private NSObject parseObject(int obj) throws PropertyListFormatException, Unsupp
}
case 0xC: {
//URL with no base URL (v1.0 and later)
//TODO Implement binary URL parsing (not yet even implemented in Core Foundation as of revision 855.17)
throw new UnsupportedOperationException("The given binary property list contains a URL object. Parsing of this object type is not yet implemented.");
//TODO Implement binary URL parsing (not implemented in Core Foundation)
throw new PropertyListFormatException("The given binary property list contains a URL object. Parsing of this object type is not yet implemented.");
}
case 0xD: {
//URL with base URL (v1.0 and later)
//TODO Implement binary URL parsing (not yet even implemented in Core Foundation as of revision 855.17)
throw new UnsupportedOperationException("The given binary property list contains a URL object. Parsing of this object type is not yet implemented.");
//TODO Implement binary URL parsing (not implemented in Core Foundation)
throw new PropertyListFormatException("The given binary property list contains a URL object. Parsing of this object type is not yet implemented.");
}
case 0xE: {
//16-byte UUID (v1.0 and later)
//TODO Implement binary UUID parsing (not yet even implemented in Core Foundation as of revision 855.17)
throw new UnsupportedOperationException("The given binary property list contains a UUID object. Parsing of this object type is not yet implemented.");
return new UID(String.valueOf(obj), copyOfRange(this.bytes, offset + 1, offset + 1 + 16));
}
default: {
throw new PropertyListFormatException("The given binary property list contains an object of unknown type (" + objType + ")");
Expand Down Expand Up @@ -305,7 +306,7 @@ private NSObject parseObject(int obj) throws PropertyListFormatException, Unsupp
NSArray array = new NSArray(length);
for (int i = 0; i < length; i++) {
int objRef = (int) parseUnsignedInt(this.bytes, offset + arrayOffset + i * this.objectRefSize, offset + arrayOffset + (i + 1) * this.objectRefSize);
array.setValue(i, this.parseObject(objRef));
array.setValue(i, this.parseObject(stack, objRef));
}
return array;
}
Expand All @@ -318,7 +319,7 @@ private NSObject parseObject(int obj) throws PropertyListFormatException, Unsupp
NSSet set = new NSSet(true);
for (int i = 0; i < length; i++) {
int objRef = (int) parseUnsignedInt(this.bytes, offset + contentOffset + i * this.objectRefSize, offset + contentOffset + (i + 1) * this.objectRefSize);
set.addObject(this.parseObject(objRef));
set.addObject(this.parseObject(stack, objRef));
}
return set;
}
Expand All @@ -331,7 +332,7 @@ private NSObject parseObject(int obj) throws PropertyListFormatException, Unsupp
NSSet set = new NSSet();
for (int i = 0; i < length; i++) {
int objRef = (int) parseUnsignedInt(this.bytes, offset + contentOffset + i * this.objectRefSize, offset + contentOffset + (i + 1) * this.objectRefSize);
set.addObject(this.parseObject(objRef));
set.addObject(this.parseObject(stack, objRef));
}
return set;
}
Expand All @@ -345,8 +346,8 @@ private NSObject parseObject(int obj) throws PropertyListFormatException, Unsupp
for (int i = 0; i < length; i++) {
int keyRef = (int) parseUnsignedInt(this.bytes, offset + contentOffset + i * this.objectRefSize, offset + contentOffset + (i + 1) * this.objectRefSize);
int valRef = (int) parseUnsignedInt(this.bytes, offset + contentOffset + (length * this.objectRefSize) + i * this.objectRefSize, offset + contentOffset + (length * this.objectRefSize) + (i + 1) * this.objectRefSize);
NSObject key = this.parseObject(keyRef);
NSObject val = this.parseObject(valRef);
NSObject key = this.parseObject(stack, keyRef);
NSObject val = this.parseObject(stack, valRef);
assert key != null; //Encountering a null object at this point would either be a fundamental error in the parser or an error in the property list
dict.put(key.toString(), val);
}
Expand All @@ -372,7 +373,7 @@ private int[] readLengthAndOffset(int objInfo, int offset) {
int int_type = this.bytes[offset + 1];
int intType = (int_type & 0xF0) >> 4;
if (intType != 0x1) {
System.err.println("BinaryPropertyListParser: Length integer has an unexpected type" + intType + ". Attempting to parse anyway...");
System.err.println("BinaryPropertyListParser: Length integer has an unexpected type (" + intType + "). Attempting to parse anyway...");
}
int intInfo = int_type & 0x0F;
int intLength = (int) Math.pow(2, intInfo);
Expand Down
69 changes: 69 additions & 0 deletions src/main/java/com/dd/plist/ParsedObjectStack.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* plist - An open source library to parse and generate property lists
* Copyright (C) 2022 Daniel Dreibrodt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package com.dd.plist;

/**
* Keeps track of the stack of parsed objects in a binary property list.
* @author Daniel Dreibrodt
*/
class ParsedObjectStack {

private ParsedObjectStack parent;
private int object;

private ParsedObjectStack(ParsedObjectStack parent, int object) {
this.parent = parent;
this.object = object;
}

/**
* Creates a new stack containing only the specified object identifier.
* @return The stack.
*/
public static ParsedObjectStack empty() {
return new ParsedObjectStack(null, -1);
}

/**
* Tries to push the specified object identifier onto the stack, checking that it is not already on the stack.
* @param obj The object identifier.
* @return The new stack with the added object identifier.
* @throws PropertyListFormatException The stack already contained that object identifier,
* indicating a cyclic reference in the property list.
*/
public ParsedObjectStack push(int obj) throws PropertyListFormatException {
this.throwIfOnStack(obj);
return new ParsedObjectStack(this, obj);
}

private void throwIfOnStack(int obj) throws PropertyListFormatException {
if (this.parent != null) {
if (this.object == obj) {
throw new PropertyListFormatException("The given binary property list contains a cyclic reference.");
}

this.parent.throwIfOnStack(obj);
}
}
}
18 changes: 18 additions & 0 deletions src/test/java/com/dd/plist/test/IssueTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

import com.dd.plist.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.xml.sax.SAXParseException;

import java.io.*;
import java.nio.file.Files;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

Expand Down Expand Up @@ -148,4 +154,16 @@ public void testIssue73_InvalidBinaryPropertyListHeader() {
File plistFile = new File("test-files/github-issue73-6.plist");
assertThrows(PropertyListFormatException.class, () -> PropertyListParser.parse(plistFile));
}
@ParameterizedTest
@MethodSource("provideIssue75ErrorFiles")
public void testIssue75(File file) {
assertThrows(PropertyListFormatException.class, (() -> PropertyListParser.parse(file)));
}


private static Stream<Arguments> provideIssue75ErrorFiles() {
return Stream.of(Objects.requireNonNull(new File("test-files/github-issue75/").listFiles()))
.filter(Objects::nonNull)
.map(Arguments::of);
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit 850f019

Please sign in to comment.