8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter

Reviewed-by: dfuchs
This commit is contained in:
Roger Riggs 2016-12-07 15:32:31 -05:00
parent 26bcf5dba0
commit 1bcb7f93c0
3 changed files with 79 additions and 38 deletions

View File

@ -350,16 +350,24 @@ public interface ObjectInputFilter {
* The first pattern that matches, working from left to right, determines
* the {@link Status#ALLOWED Status.ALLOWED}
* or {@link Status#REJECTED Status.REJECTED} result.
* If nothing matches, the result is {@link Status#UNDECIDED Status.UNDECIDED}.
* If the limits are not exceeded and no pattern matches the class,
* the result is {@link Status#UNDECIDED Status.UNDECIDED}.
*
* @param pattern the pattern string to parse; not null
* @return a filter to check a class being deserialized; may be null;
* @return a filter to check a class being deserialized;
* {@code null} if no patterns
* @throws IllegalArgumentException
* if a limit is missing the name, or the long value
* is not a number or is negative,
* or the module name is missing if the pattern contains "/"
* or if the package is missing for ".*" and ".**"
* @throws IllegalArgumentException if the pattern string is illegal or
* malformed and cannot be parsed.
* In particular, if any of the following is true:
* <ul>
* <li> if a limit is missing the name or the name is not one of
* "maxdepth", "maxrefs", "maxbytes", or "maxarray"
* <li> if the value of the limit can not be parsed by
* {@link Long#parseLong Long.parseLong} or is negative
* <li> if the pattern contains "/" and the module name is missing
* or the remaining pattern is empty
* <li> if the package is missing for ".*" and ".**"
* </ul>
*/
public static ObjectInputFilter createFilter(String pattern) {
Objects.requireNonNull(pattern, "pattern");
@ -402,14 +410,19 @@ public interface ObjectInputFilter {
* Returns an ObjectInputFilter from a string of patterns.
*
* @param pattern the pattern string to parse
* @return a filter to check a class being deserialized; not null
* @return a filter to check a class being deserialized;
* {@code null} if no patterns
* @throws IllegalArgumentException if the parameter is malformed
* if the pattern is missing the name, the long value
* is not a number or is negative.
*/
static ObjectInputFilter createFilter(String pattern) {
Global filter = new Global(pattern);
return filter.isEmpty() ? null : filter;
try {
return new Global(pattern);
} catch (UnsupportedOperationException uoe) {
// no non-empty patterns
return null;
}
}
/**
@ -417,8 +430,10 @@ public interface ObjectInputFilter {
*
* @param pattern a pattern string of filters
* @throws IllegalArgumentException if the pattern is malformed
* @throws UnsupportedOperationException if there are no non-empty patterns
*/
private Global(String pattern) {
boolean hasLimits = false;
this.pattern = pattern;
maxArrayLength = Long.MAX_VALUE; // Default values are unlimited
@ -436,6 +451,7 @@ public interface ObjectInputFilter {
}
if (parseLimit(p)) {
// If the pattern contained a limit setting, i.e. type=value
hasLimits = true;
continue;
}
boolean negate = p.charAt(0) == '!';
@ -510,18 +526,9 @@ public interface ObjectInputFilter {
filters.add(c -> moduleName.equals(c.getModule().getName()) ? patternFilter.apply(c) : Status.UNDECIDED);
}
}
}
/**
* Returns if this filter has any checks.
* @return {@code true} if the filter has any checks, {@code false} otherwise
*/
private boolean isEmpty() {
return filters.isEmpty() &&
maxArrayLength == Long.MAX_VALUE &&
maxDepth == Long.MAX_VALUE &&
maxReferences == Long.MAX_VALUE &&
maxStreamBytes == Long.MAX_VALUE;
if (filters.isEmpty() && !hasLimits) {
throw new UnsupportedOperationException("no non-empty patterns");
}
}
/**

View File

@ -1164,6 +1164,13 @@ public class ObjectInputStream
* for each class and reference in the stream.
* The filter can check any or all of the class, the array length, the number
* of references, the depth of the graph, and the size of the input stream.
* The depth is the number of nested {@linkplain #readObject readObject}
* calls starting with the reading of the root of the graph being deserialized
* and the current object being deserialized.
* The number of references is the cumulative number of objects and references
* to objects already read from the stream including the current object being read.
* The filter is invoked only when reading objects from the stream and for
* not primitives.
* <p>
* If the filter returns {@link ObjectInputFilter.Status#REJECTED Status.REJECTED},
* {@code null} or throws a {@link RuntimeException},
@ -1178,8 +1185,9 @@ public class ObjectInputStream
*
* @implSpec
* The filter, when not {@code null}, is invoked during {@link #readObject readObject}
* and {@link #readUnshared readUnshared} for each object
* (regular or class) in the stream including the following:
* and {@link #readUnshared readUnshared} for each object (regular or class) in the stream.
* Strings are treated as primitives and do not invoke the filter.
* The filter is called for:
* <ul>
* <li>each object reference previously deserialized from the stream
* (class is {@code null}, arrayLength is -1),

View File

@ -99,16 +99,6 @@ public class SerialFilterTest implements Serializable {
@DataProvider(name="InvalidPatterns")
static Object[][] invalidPatterns() {
return new Object [][] {
{"maxrefs=-1"},
{"maxdepth=-1"},
{"maxbytes=-1"},
{"maxarray=-1"},
{"xyz=0"},
{"xyz=-1"},
{"maxrefs=0xabc"},
{"maxrefs=abc"},
{"maxrefs="},
{"maxrefs=+"},
{".*"},
{".**"},
{"!"},
@ -121,11 +111,32 @@ public class SerialFilterTest implements Serializable {
@DataProvider(name="Limits")
static Object[][] limits() {
// The numbers are arbitrary > 1
return new Object[][]{
return new Object[][] {
{"maxrefs", 1}, // 0 is tested as n-1
{"maxrefs", 10},
{"maxdepth", 5},
{"maxbytes", 100},
{"maxarray", 16},
{"maxbytes", Long.MAX_VALUE},
};
}
@DataProvider(name="InvalidLimits")
static Object[][] invalidLimits() {
return new Object[][] {
{"maxrefs=-1"},
{"maxdepth=-1"},
{"maxbytes=-1"},
{"maxarray=-1"},
{"xyz=0"},
{"xyz=-1"},
{"maxrefs=0xabc"},
{"maxrefs=abc"},
{"maxrefs="},
{"maxrefs=+"},
{"maxbytes=-1"},
{"maxbytes=9223372036854775808"},
{"maxbytes=-9223372036854775807"},
};
}
@ -305,7 +316,7 @@ public class SerialFilterTest implements Serializable {
* @param value a test value
*/
@Test(dataProvider="Limits")
static void testLimits(String name, int value) {
static void testLimits(String name, long value) {
Class<?> arrayClass = new int[0].getClass();
String pattern = String.format("%s=%d;%s=%d", name, value, name, value - 1);
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern);
@ -319,6 +330,21 @@ public class SerialFilterTest implements Serializable {
"last limit value not used: " + filter);
}
/**
* Test invalid limits.
* Construct a filter with the limit, it should throw IllegalArgumentException.
* @param pattern a pattern to test
*/
@Test(dataProvider="InvalidLimits", expectedExceptions=java.lang.IllegalArgumentException.class)
static void testInvalidLimits(String pattern) {
try {
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern);
} catch (IllegalArgumentException iae) {
System.out.printf(" success exception: %s%n", iae);
throw iae;
}
}
/**
* Test that returning null from a filter causes deserialization to fail.
*/
@ -332,7 +358,7 @@ public class SerialFilterTest implements Serializable {
}
});
} catch (InvalidClassException ice) {
System.out.printf("Success exception: %s%n", ice);
System.out.printf(" success exception: %s%n", ice);
throw ice;
}
}
@ -606,7 +632,7 @@ public class SerialFilterTest implements Serializable {
}
return array;
} else if (pattern.startsWith("maxarray=")) {
return allowed ? new int[(int)value] : new int[(int)value+1];
return allowed ? new int[(int)value] : new int[(int)value+1];
}
Assert.fail("Object could not be generated for pattern: "
+ pattern