8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
Reviewed-by: dfuchs
This commit is contained in:
parent
26bcf5dba0
commit
1bcb7f93c0
@ -350,16 +350,24 @@ public interface ObjectInputFilter {
|
|||||||
* The first pattern that matches, working from left to right, determines
|
* The first pattern that matches, working from left to right, determines
|
||||||
* the {@link Status#ALLOWED Status.ALLOWED}
|
* the {@link Status#ALLOWED Status.ALLOWED}
|
||||||
* or {@link Status#REJECTED Status.REJECTED} result.
|
* 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
|
* @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
|
* {@code null} if no patterns
|
||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException if the pattern string is illegal or
|
||||||
* if a limit is missing the name, or the long value
|
* malformed and cannot be parsed.
|
||||||
* is not a number or is negative,
|
* In particular, if any of the following is true:
|
||||||
* or the module name is missing if the pattern contains "/"
|
* <ul>
|
||||||
* or if the package is missing for ".*" and ".**"
|
* <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) {
|
public static ObjectInputFilter createFilter(String pattern) {
|
||||||
Objects.requireNonNull(pattern, "pattern");
|
Objects.requireNonNull(pattern, "pattern");
|
||||||
@ -402,14 +410,19 @@ public interface ObjectInputFilter {
|
|||||||
* Returns an ObjectInputFilter from a string of patterns.
|
* Returns an ObjectInputFilter from a string of patterns.
|
||||||
*
|
*
|
||||||
* @param pattern the pattern string to parse
|
* @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
|
* @throws IllegalArgumentException if the parameter is malformed
|
||||||
* if the pattern is missing the name, the long value
|
* if the pattern is missing the name, the long value
|
||||||
* is not a number or is negative.
|
* is not a number or is negative.
|
||||||
*/
|
*/
|
||||||
static ObjectInputFilter createFilter(String pattern) {
|
static ObjectInputFilter createFilter(String pattern) {
|
||||||
Global filter = new Global(pattern);
|
try {
|
||||||
return filter.isEmpty() ? null : filter;
|
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
|
* @param pattern a pattern string of filters
|
||||||
* @throws IllegalArgumentException if the pattern is malformed
|
* @throws IllegalArgumentException if the pattern is malformed
|
||||||
|
* @throws UnsupportedOperationException if there are no non-empty patterns
|
||||||
*/
|
*/
|
||||||
private Global(String pattern) {
|
private Global(String pattern) {
|
||||||
|
boolean hasLimits = false;
|
||||||
this.pattern = pattern;
|
this.pattern = pattern;
|
||||||
|
|
||||||
maxArrayLength = Long.MAX_VALUE; // Default values are unlimited
|
maxArrayLength = Long.MAX_VALUE; // Default values are unlimited
|
||||||
@ -436,6 +451,7 @@ public interface ObjectInputFilter {
|
|||||||
}
|
}
|
||||||
if (parseLimit(p)) {
|
if (parseLimit(p)) {
|
||||||
// If the pattern contained a limit setting, i.e. type=value
|
// If the pattern contained a limit setting, i.e. type=value
|
||||||
|
hasLimits = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
boolean negate = p.charAt(0) == '!';
|
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);
|
filters.add(c -> moduleName.equals(c.getModule().getName()) ? patternFilter.apply(c) : Status.UNDECIDED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (filters.isEmpty() && !hasLimits) {
|
||||||
|
throw new UnsupportedOperationException("no non-empty patterns");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1164,6 +1164,13 @@ public class ObjectInputStream
|
|||||||
* for each class and reference in the stream.
|
* for each class and reference in the stream.
|
||||||
* The filter can check any or all of the class, the array length, the number
|
* 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.
|
* 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>
|
* <p>
|
||||||
* If the filter returns {@link ObjectInputFilter.Status#REJECTED Status.REJECTED},
|
* If the filter returns {@link ObjectInputFilter.Status#REJECTED Status.REJECTED},
|
||||||
* {@code null} or throws a {@link RuntimeException},
|
* {@code null} or throws a {@link RuntimeException},
|
||||||
@ -1178,8 +1185,9 @@ public class ObjectInputStream
|
|||||||
*
|
*
|
||||||
* @implSpec
|
* @implSpec
|
||||||
* The filter, when not {@code null}, is invoked during {@link #readObject readObject}
|
* The filter, when not {@code null}, is invoked during {@link #readObject readObject}
|
||||||
* and {@link #readUnshared readUnshared} for each object
|
* and {@link #readUnshared readUnshared} for each object (regular or class) in the stream.
|
||||||
* (regular or class) in the stream including the following:
|
* Strings are treated as primitives and do not invoke the filter.
|
||||||
|
* The filter is called for:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>each object reference previously deserialized from the stream
|
* <li>each object reference previously deserialized from the stream
|
||||||
* (class is {@code null}, arrayLength is -1),
|
* (class is {@code null}, arrayLength is -1),
|
||||||
|
@ -99,16 +99,6 @@ public class SerialFilterTest implements Serializable {
|
|||||||
@DataProvider(name="InvalidPatterns")
|
@DataProvider(name="InvalidPatterns")
|
||||||
static Object[][] invalidPatterns() {
|
static Object[][] invalidPatterns() {
|
||||||
return new Object [][] {
|
return new Object [][] {
|
||||||
{"maxrefs=-1"},
|
|
||||||
{"maxdepth=-1"},
|
|
||||||
{"maxbytes=-1"},
|
|
||||||
{"maxarray=-1"},
|
|
||||||
{"xyz=0"},
|
|
||||||
{"xyz=-1"},
|
|
||||||
{"maxrefs=0xabc"},
|
|
||||||
{"maxrefs=abc"},
|
|
||||||
{"maxrefs="},
|
|
||||||
{"maxrefs=+"},
|
|
||||||
{".*"},
|
{".*"},
|
||||||
{".**"},
|
{".**"},
|
||||||
{"!"},
|
{"!"},
|
||||||
@ -122,10 +112,31 @@ public class SerialFilterTest implements Serializable {
|
|||||||
static Object[][] limits() {
|
static Object[][] limits() {
|
||||||
// The numbers are arbitrary > 1
|
// The numbers are arbitrary > 1
|
||||||
return new Object[][] {
|
return new Object[][] {
|
||||||
|
{"maxrefs", 1}, // 0 is tested as n-1
|
||||||
{"maxrefs", 10},
|
{"maxrefs", 10},
|
||||||
{"maxdepth", 5},
|
{"maxdepth", 5},
|
||||||
{"maxbytes", 100},
|
{"maxbytes", 100},
|
||||||
{"maxarray", 16},
|
{"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
|
* @param value a test value
|
||||||
*/
|
*/
|
||||||
@Test(dataProvider="Limits")
|
@Test(dataProvider="Limits")
|
||||||
static void testLimits(String name, int value) {
|
static void testLimits(String name, long value) {
|
||||||
Class<?> arrayClass = new int[0].getClass();
|
Class<?> arrayClass = new int[0].getClass();
|
||||||
String pattern = String.format("%s=%d;%s=%d", name, value, name, value - 1);
|
String pattern = String.format("%s=%d;%s=%d", name, value, name, value - 1);
|
||||||
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern);
|
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern);
|
||||||
@ -319,6 +330,21 @@ public class SerialFilterTest implements Serializable {
|
|||||||
"last limit value not used: " + filter);
|
"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.
|
* Test that returning null from a filter causes deserialization to fail.
|
||||||
*/
|
*/
|
||||||
@ -332,7 +358,7 @@ public class SerialFilterTest implements Serializable {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (InvalidClassException ice) {
|
} catch (InvalidClassException ice) {
|
||||||
System.out.printf("Success exception: %s%n", ice);
|
System.out.printf(" success exception: %s%n", ice);
|
||||||
throw ice;
|
throw ice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user