author | alanb |
Fri, 07 Apr 2017 08:05:54 +0000 | |
changeset 44545 | 83b611b88ac8 |
parent 44263 | 7a9297d467e7 |
child 46160 | c647e44ea1b9 |
permissions | -rw-r--r-- |
41230 | 1 |
/* |
2 |
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. |
|
3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 |
* |
|
5 |
* This code is free software; you can redistribute it and/or modify it |
|
6 |
* under the terms of the GNU General Public License version 2 only, as |
|
7 |
* published by the Free Software Foundation. Oracle designates this |
|
8 |
* particular file as subject to the "Classpath" exception as provided |
|
9 |
* by Oracle in the LICENSE file that accompanied this code. |
|
10 |
* |
|
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that |
|
15 |
* accompanied this code). |
|
16 |
* |
|
17 |
* You should have received a copy of the GNU General Public License version |
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation, |
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 |
* |
|
21 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 |
* or visit www.oracle.com if you need additional information or have any |
|
23 |
* questions. |
|
24 |
*/ |
|
25 |
||
26 |
package java.io; |
|
27 |
||
28 |
import java.security.AccessController; |
|
29 |
import java.security.PrivilegedAction; |
|
30 |
import java.security.Security; |
|
31 |
import java.util.ArrayList; |
|
32 |
import java.util.List; |
|
33 |
import java.util.Objects; |
|
34 |
import java.util.Optional; |
|
35 |
import java.util.function.Function; |
|
36 |
||
37 |
||
38 |
/** |
|
39 |
* Filter classes, array lengths, and graph metrics during deserialization. |
|
40 |
* If set on an {@link ObjectInputStream}, the {@link #checkInput checkInput(FilterInfo)} |
|
41 |
* method is called to validate classes, the length of each array, |
|
42 |
* the number of objects being read from the stream, the depth of the graph, |
|
43 |
* and the total number of bytes read from the stream. |
|
44 |
* <p> |
|
45 |
* A filter can be set via {@link ObjectInputStream#setObjectInputFilter setObjectInputFilter} |
|
46 |
* for an individual ObjectInputStream. |
|
47 |
* A filter can be set via {@link Config#setSerialFilter(ObjectInputFilter) Config.setSerialFilter} |
|
48 |
* to affect every {@code ObjectInputStream} that does not otherwise set a filter. |
|
49 |
* <p> |
|
50 |
* A filter determines whether the arguments are {@link Status#ALLOWED ALLOWED} |
|
51 |
* or {@link Status#REJECTED REJECTED} and should return the appropriate status. |
|
52 |
* If the filter cannot determine the status it should return |
|
53 |
* {@link Status#UNDECIDED UNDECIDED}. |
|
54 |
* Filters should be designed for the specific use case and expected types. |
|
55 |
* A filter designed for a particular use may be passed a class that is outside |
|
56 |
* of the scope of the filter. If the purpose of the filter is to black-list classes |
|
57 |
* then it can reject a candidate class that matches and report UNDECIDED for others. |
|
58 |
* A filter may be called with class equals {@code null}, {@code arrayLength} equal -1, |
|
59 |
* the depth, number of references, and stream size and return a status |
|
60 |
* that reflects only one or only some of the values. |
|
61 |
* This allows a filter to specific about the choice it is reporting and |
|
62 |
* to use other filters without forcing either allowed or rejected status. |
|
63 |
* |
|
64 |
* <p> |
|
65 |
* Typically, a custom filter should check if a process-wide filter |
|
66 |
* is configured and defer to it if so. For example, |
|
67 |
* <pre>{@code |
|
68 |
* ObjectInputFilter.Status checkInput(FilterInfo info) { |
|
69 |
* ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter(); |
|
70 |
* if (serialFilter != null) { |
|
71 |
* ObjectInputFilter.Status status = serialFilter.checkInput(info); |
|
72 |
* if (status != ObjectInputFilter.Status.UNDECIDED) { |
|
73 |
* // The process-wide filter overrides this filter |
|
74 |
* return status; |
|
75 |
* } |
|
76 |
* } |
|
77 |
* if (info.serialClass() != null && |
|
78 |
* Remote.class.isAssignableFrom(info.serialClass())) { |
|
79 |
* return Status.REJECTED; // Do not allow Remote objects |
|
80 |
* } |
|
81 |
* return Status.UNDECIDED; |
|
82 |
* } |
|
83 |
*}</pre> |
|
84 |
* <p> |
|
85 |
* Unless otherwise noted, passing a {@code null} argument to a |
|
86 |
* method in this interface and its nested classes will cause a |
|
87 |
* {@link NullPointerException} to be thrown. |
|
88 |
* |
|
89 |
* @see ObjectInputStream#setObjectInputFilter(ObjectInputFilter) |
|
90 |
* @since 9 |
|
91 |
*/ |
|
92 |
@FunctionalInterface |
|
93 |
public interface ObjectInputFilter { |
|
94 |
||
95 |
/** |
|
96 |
* Check the class, array length, number of object references, depth, |
|
97 |
* stream size, and other available filtering information. |
|
98 |
* Implementations of this method check the contents of the object graph being created |
|
99 |
* during deserialization. The filter returns {@link Status#ALLOWED Status.ALLOWED}, |
|
100 |
* {@link Status#REJECTED Status.REJECTED}, or {@link Status#UNDECIDED Status.UNDECIDED}. |
|
101 |
* |
|
102 |
* @param filterInfo provides information about the current object being deserialized, |
|
103 |
* if any, and the status of the {@link ObjectInputStream} |
|
104 |
* @return {@link Status#ALLOWED Status.ALLOWED} if accepted, |
|
105 |
* {@link Status#REJECTED Status.REJECTED} if rejected, |
|
106 |
* {@link Status#UNDECIDED Status.UNDECIDED} if undecided. |
|
107 |
*/ |
|
108 |
Status checkInput(FilterInfo filterInfo); |
|
109 |
||
110 |
/** |
|
111 |
* FilterInfo provides access to information about the current object |
|
112 |
* being deserialized and the status of the {@link ObjectInputStream}. |
|
113 |
* @since 9 |
|
114 |
*/ |
|
115 |
interface FilterInfo { |
|
116 |
/** |
|
117 |
* The class of an object being deserialized. |
|
118 |
* For arrays, it is the array type. |
|
119 |
* For example, the array class name of a 2 dimensional array of strings is |
|
120 |
* "{@code [[Ljava.lang.String;}". |
|
121 |
* To check the array's element type, iteratively use |
|
122 |
* {@link Class#getComponentType() Class.getComponentType} while the result |
|
123 |
* is an array and then check the class. |
|
124 |
* The {@code serialClass is null} in the case where a new object is not being |
|
125 |
* created and to give the filter a chance to check the depth, number of |
|
126 |
* references to existing objects, and the stream size. |
|
127 |
* |
|
128 |
* @return class of an object being deserialized; may be null |
|
129 |
*/ |
|
130 |
Class<?> serialClass(); |
|
131 |
||
132 |
/** |
|
133 |
* The number of array elements when deserializing an array of the class. |
|
134 |
* |
|
135 |
* @return the non-negative number of array elements when deserializing |
|
136 |
* an array of the class, otherwise -1 |
|
137 |
*/ |
|
138 |
long arrayLength(); |
|
139 |
||
140 |
/** |
|
141 |
* The current depth. |
|
142 |
* The depth starts at {@code 1} and increases for each nested object and |
|
143 |
* decrements when each nested object returns. |
|
144 |
* |
|
145 |
* @return the current depth |
|
146 |
*/ |
|
147 |
long depth(); |
|
148 |
||
149 |
/** |
|
150 |
* The current number of object references. |
|
151 |
* |
|
152 |
* @return the non-negative current number of object references |
|
153 |
*/ |
|
154 |
long references(); |
|
155 |
||
156 |
/** |
|
157 |
* The current number of bytes consumed. |
|
158 |
* @implSpec {@code streamBytes} is implementation specific |
|
159 |
* and may not be directly related to the object in the stream |
|
160 |
* that caused the callback. |
|
161 |
* |
|
162 |
* @return the non-negative current number of bytes consumed |
|
163 |
*/ |
|
164 |
long streamBytes(); |
|
165 |
} |
|
166 |
||
167 |
/** |
|
168 |
* The status of a check on the class, array length, number of references, |
|
169 |
* depth, and stream size. |
|
170 |
* |
|
171 |
* @since 9 |
|
172 |
*/ |
|
173 |
enum Status { |
|
174 |
/** |
|
175 |
* The status is undecided, not allowed and not rejected. |
|
176 |
*/ |
|
177 |
UNDECIDED, |
|
178 |
/** |
|
179 |
* The status is allowed. |
|
180 |
*/ |
|
181 |
ALLOWED, |
|
182 |
/** |
|
183 |
* The status is rejected. |
|
184 |
*/ |
|
185 |
REJECTED; |
|
186 |
} |
|
187 |
||
188 |
/** |
|
189 |
* A utility class to set and get the process-wide filter or create a filter |
|
190 |
* from a pattern string. If a process-wide filter is set, it will be |
|
191 |
* used for each {@link ObjectInputStream} that does not set its own filter. |
|
192 |
* <p> |
|
193 |
* When setting the filter, it should be stateless and idempotent, |
|
194 |
* reporting the same result when passed the same arguments. |
|
195 |
* <p> |
|
42223
a55f957f6e4e
8169645: ObjectInputFilter Config spec is ambiguous regarding overriding the filter via System properties
rriggs
parents:
41230
diff
changeset
|
196 |
* The filter is configured during the initialization of the {@code ObjectInputFilter.Config} |
a55f957f6e4e
8169645: ObjectInputFilter Config spec is ambiguous regarding overriding the filter via System properties
rriggs
parents:
41230
diff
changeset
|
197 |
* class. For example, by calling {@link #getSerialFilter() Config.getSerialFilter}. |
a55f957f6e4e
8169645: ObjectInputFilter Config spec is ambiguous regarding overriding the filter via System properties
rriggs
parents:
41230
diff
changeset
|
198 |
* If the system property {@code jdk.serialFilter} is defined, it is used |
a55f957f6e4e
8169645: ObjectInputFilter Config spec is ambiguous regarding overriding the filter via System properties
rriggs
parents:
41230
diff
changeset
|
199 |
* to configure the filter. |
a55f957f6e4e
8169645: ObjectInputFilter Config spec is ambiguous regarding overriding the filter via System properties
rriggs
parents:
41230
diff
changeset
|
200 |
* If the system property is not defined, and the {@link java.security.Security} |
a55f957f6e4e
8169645: ObjectInputFilter Config spec is ambiguous regarding overriding the filter via System properties
rriggs
parents:
41230
diff
changeset
|
201 |
* property {@code jdk.serialFilter} is defined then it is used to configure the filter. |
a55f957f6e4e
8169645: ObjectInputFilter Config spec is ambiguous regarding overriding the filter via System properties
rriggs
parents:
41230
diff
changeset
|
202 |
* Otherwise, the filter is not configured during initialization. |
a55f957f6e4e
8169645: ObjectInputFilter Config spec is ambiguous regarding overriding the filter via System properties
rriggs
parents:
41230
diff
changeset
|
203 |
* The syntax for each property is the same as for the |
a55f957f6e4e
8169645: ObjectInputFilter Config spec is ambiguous regarding overriding the filter via System properties
rriggs
parents:
41230
diff
changeset
|
204 |
* {@link #createFilter(String) createFilter} method. |
a55f957f6e4e
8169645: ObjectInputFilter Config spec is ambiguous regarding overriding the filter via System properties
rriggs
parents:
41230
diff
changeset
|
205 |
* If a filter is not configured, it can be set with |
a55f957f6e4e
8169645: ObjectInputFilter Config spec is ambiguous regarding overriding the filter via System properties
rriggs
parents:
41230
diff
changeset
|
206 |
* {@link #setSerialFilter(ObjectInputFilter) Config.setSerialFilter}. |
41230 | 207 |
* |
208 |
* @since 9 |
|
209 |
*/ |
|
210 |
final class Config { |
|
211 |
/* No instances. */ |
|
212 |
private Config() {} |
|
213 |
||
214 |
/** |
|
215 |
* Lock object for process-wide filter. |
|
216 |
*/ |
|
217 |
private final static Object serialFilterLock = new Object(); |
|
218 |
||
219 |
/** |
|
220 |
* Debug: Logger |
|
221 |
*/ |
|
222 |
private final static System.Logger configLog; |
|
223 |
||
224 |
/** |
|
225 |
* Logger for debugging. |
|
226 |
*/ |
|
227 |
static void filterLog(System.Logger.Level level, String msg, Object... args) { |
|
228 |
if (configLog != null) { |
|
229 |
configLog.log(level, msg, args); |
|
230 |
} |
|
231 |
} |
|
232 |
||
233 |
/** |
|
234 |
* The name for the process-wide deserialization filter. |
|
235 |
* Used as a system property and a java.security.Security property. |
|
236 |
*/ |
|
237 |
private final static String SERIAL_FILTER_PROPNAME = "jdk.serialFilter"; |
|
238 |
||
239 |
/** |
|
240 |
* The process-wide filter; may be null. |
|
241 |
* Lookup the filter in java.security.Security or |
|
242 |
* the system property. |
|
243 |
*/ |
|
244 |
private final static ObjectInputFilter configuredFilter; |
|
245 |
||
246 |
static { |
|
247 |
configuredFilter = AccessController |
|
248 |
.doPrivileged((PrivilegedAction<ObjectInputFilter>) () -> { |
|
249 |
String props = System.getProperty(SERIAL_FILTER_PROPNAME); |
|
250 |
if (props == null) { |
|
251 |
props = Security.getProperty(SERIAL_FILTER_PROPNAME); |
|
252 |
} |
|
253 |
if (props != null) { |
|
254 |
System.Logger log = |
|
255 |
System.getLogger("java.io.serialization"); |
|
256 |
log.log(System.Logger.Level.INFO, |
|
257 |
"Creating serialization filter from {0}", props); |
|
258 |
try { |
|
259 |
return createFilter(props); |
|
260 |
} catch (RuntimeException re) { |
|
261 |
log.log(System.Logger.Level.ERROR, |
|
262 |
"Error configuring filter: {0}", re); |
|
263 |
} |
|
264 |
} |
|
265 |
return null; |
|
266 |
}); |
|
267 |
configLog = (configuredFilter != null) ? System.getLogger("java.io.serialization") : null; |
|
268 |
} |
|
269 |
||
270 |
/** |
|
271 |
* Current configured filter. |
|
272 |
*/ |
|
273 |
private static ObjectInputFilter serialFilter = configuredFilter; |
|
274 |
||
275 |
/** |
|
276 |
* Returns the process-wide serialization filter or {@code null} if not configured. |
|
277 |
* |
|
278 |
* @return the process-wide serialization filter or {@code null} if not configured |
|
279 |
*/ |
|
280 |
public static ObjectInputFilter getSerialFilter() { |
|
281 |
synchronized (serialFilterLock) { |
|
282 |
return serialFilter; |
|
283 |
} |
|
284 |
} |
|
285 |
||
286 |
/** |
|
287 |
* Set the process-wide filter if it has not already been configured or set. |
|
288 |
* |
|
289 |
* @param filter the serialization filter to set as the process-wide filter; not null |
|
290 |
* @throws SecurityException if there is security manager and the |
|
291 |
* {@code SerializablePermission("serialFilter")} is not granted |
|
292 |
* @throws IllegalStateException if the filter has already been set {@code non-null} |
|
293 |
*/ |
|
294 |
public static void setSerialFilter(ObjectInputFilter filter) { |
|
295 |
Objects.requireNonNull(filter, "filter"); |
|
296 |
SecurityManager sm = System.getSecurityManager(); |
|
297 |
if (sm != null) { |
|
298 |
sm.checkPermission(ObjectStreamConstants.SERIAL_FILTER_PERMISSION); |
|
299 |
} |
|
300 |
synchronized (serialFilterLock) { |
|
301 |
if (serialFilter != null) { |
|
302 |
throw new IllegalStateException("Serial filter can only be set once"); |
|
303 |
} |
|
304 |
serialFilter = filter; |
|
305 |
} |
|
306 |
} |
|
307 |
||
308 |
/** |
|
309 |
* Returns an ObjectInputFilter from a string of patterns. |
|
310 |
* <p> |
|
311 |
* Patterns are separated by ";" (semicolon). Whitespace is significant and |
|
312 |
* is considered part of the pattern. |
|
313 |
* If a pattern includes an equals assignment, "{@code =}" it sets a limit. |
|
314 |
* If a limit appears more than once the last value is used. |
|
315 |
* <ul> |
|
316 |
* <li>maxdepth={@code value} - the maximum depth of a graph</li> |
|
317 |
* <li>maxrefs={@code value} - the maximum number of internal references</li> |
|
318 |
* <li>maxbytes={@code value} - the maximum number of bytes in the input stream</li> |
|
319 |
* <li>maxarray={@code value} - the maximum array length allowed</li> |
|
320 |
* </ul> |
|
321 |
* <p> |
|
322 |
* Other patterns match or reject class or package name |
|
323 |
* as returned from {@link Class#getName() Class.getName()} and |
|
324 |
* if an optional module name is present |
|
44545
83b611b88ac8
8177530: Module system implementation refresh (4/2017)
alanb
parents:
44263
diff
changeset
|
325 |
* {@link Module#getName() class.getModule().getName()}. |
41230 | 326 |
* Note that for arrays the element type is used in the pattern, |
327 |
* not the array type. |
|
328 |
* <ul> |
|
329 |
* <li>If the pattern starts with "!", the class is rejected if the remaining pattern is matched; |
|
330 |
* otherwise the class is allowed if the pattern matches. |
|
331 |
* <li>If the pattern contains "/", the non-empty prefix up to the "/" is the module name; |
|
332 |
* if the module name matches the module name of the class then |
|
333 |
* the remaining pattern is matched with the class name. |
|
334 |
* If there is no "/", the module name is not compared. |
|
335 |
* <li>If the pattern ends with ".**" it matches any class in the package and all subpackages. |
|
336 |
* <li>If the pattern ends with ".*" it matches any class in the package. |
|
337 |
* <li>If the pattern ends with "*", it matches any class with the pattern as a prefix. |
|
338 |
* <li>If the pattern is equal to the class name, it matches. |
|
339 |
* <li>Otherwise, the pattern is not matched. |
|
340 |
* </ul> |
|
341 |
* <p> |
|
342 |
* The resulting filter performs the limit checks and then |
|
343 |
* tries to match the class, if any. If any of the limits are exceeded, |
|
344 |
* the filter returns {@link Status#REJECTED Status.REJECTED}. |
|
345 |
* If the class is an array type, the class to be matched is the element type. |
|
346 |
* Arrays of any number of dimensions are treated the same as the element type. |
|
347 |
* For example, a pattern of "{@code !example.Foo}", |
|
348 |
* rejects creation of any instance or array of {@code example.Foo}. |
|
349 |
* The first pattern that matches, working from left to right, determines |
|
350 |
* the {@link Status#ALLOWED Status.ALLOWED} |
|
351 |
* or {@link Status#REJECTED Status.REJECTED} result. |
|
42446
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
352 |
* If the limits are not exceeded and no pattern matches the class, |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
353 |
* the result is {@link Status#UNDECIDED Status.UNDECIDED}. |
41230 | 354 |
* |
355 |
* @param pattern the pattern string to parse; not null |
|
42446
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
356 |
* @return a filter to check a class being deserialized; |
41230 | 357 |
* {@code null} if no patterns |
42446
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
358 |
* @throws IllegalArgumentException if the pattern string is illegal or |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
359 |
* malformed and cannot be parsed. |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
360 |
* In particular, if any of the following is true: |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
361 |
* <ul> |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
362 |
* <li> if a limit is missing the name or the name is not one of |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
363 |
* "maxdepth", "maxrefs", "maxbytes", or "maxarray" |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
364 |
* <li> if the value of the limit can not be parsed by |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
365 |
* {@link Long#parseLong Long.parseLong} or is negative |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
366 |
* <li> if the pattern contains "/" and the module name is missing |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
367 |
* or the remaining pattern is empty |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
368 |
* <li> if the package is missing for ".*" and ".**" |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
369 |
* </ul> |
41230 | 370 |
*/ |
371 |
public static ObjectInputFilter createFilter(String pattern) { |
|
372 |
Objects.requireNonNull(pattern, "pattern"); |
|
373 |
return Global.createFilter(pattern); |
|
374 |
} |
|
375 |
||
376 |
/** |
|
377 |
* Implementation of ObjectInputFilter that performs the checks of |
|
378 |
* the process-wide serialization filter. If configured, it will be |
|
379 |
* used for all ObjectInputStreams that do not set their own filters. |
|
380 |
* |
|
381 |
*/ |
|
382 |
final static class Global implements ObjectInputFilter { |
|
383 |
/** |
|
384 |
* The pattern used to create the filter. |
|
385 |
*/ |
|
386 |
private final String pattern; |
|
387 |
/** |
|
388 |
* The list of class filters. |
|
389 |
*/ |
|
390 |
private final List<Function<Class<?>, Status>> filters; |
|
391 |
/** |
|
392 |
* Maximum allowed bytes in the stream. |
|
393 |
*/ |
|
394 |
private long maxStreamBytes; |
|
395 |
/** |
|
396 |
* Maximum depth of the graph allowed. |
|
397 |
*/ |
|
398 |
private long maxDepth; |
|
399 |
/** |
|
400 |
* Maximum number of references in a graph. |
|
401 |
*/ |
|
402 |
private long maxReferences; |
|
403 |
/** |
|
404 |
* Maximum length of any array. |
|
405 |
*/ |
|
406 |
private long maxArrayLength; |
|
407 |
||
408 |
/** |
|
409 |
* Returns an ObjectInputFilter from a string of patterns. |
|
410 |
* |
|
411 |
* @param pattern the pattern string to parse |
|
42446
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
412 |
* @return a filter to check a class being deserialized; |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
413 |
* {@code null} if no patterns |
41230 | 414 |
* @throws IllegalArgumentException if the parameter is malformed |
415 |
* if the pattern is missing the name, the long value |
|
416 |
* is not a number or is negative. |
|
417 |
*/ |
|
418 |
static ObjectInputFilter createFilter(String pattern) { |
|
42446
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
419 |
try { |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
420 |
return new Global(pattern); |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
421 |
} catch (UnsupportedOperationException uoe) { |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
422 |
// no non-empty patterns |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
423 |
return null; |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
424 |
} |
41230 | 425 |
} |
426 |
||
427 |
/** |
|
428 |
* Construct a new filter from the pattern String. |
|
429 |
* |
|
430 |
* @param pattern a pattern string of filters |
|
431 |
* @throws IllegalArgumentException if the pattern is malformed |
|
42446
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
432 |
* @throws UnsupportedOperationException if there are no non-empty patterns |
41230 | 433 |
*/ |
434 |
private Global(String pattern) { |
|
42446
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
435 |
boolean hasLimits = false; |
41230 | 436 |
this.pattern = pattern; |
437 |
||
438 |
maxArrayLength = Long.MAX_VALUE; // Default values are unlimited |
|
439 |
maxDepth = Long.MAX_VALUE; |
|
440 |
maxReferences = Long.MAX_VALUE; |
|
441 |
maxStreamBytes = Long.MAX_VALUE; |
|
442 |
||
443 |
String[] patterns = pattern.split(";"); |
|
444 |
filters = new ArrayList<>(patterns.length); |
|
445 |
for (int i = 0; i < patterns.length; i++) { |
|
446 |
String p = patterns[i]; |
|
447 |
int nameLen = p.length(); |
|
448 |
if (nameLen == 0) { |
|
449 |
continue; |
|
450 |
} |
|
451 |
if (parseLimit(p)) { |
|
452 |
// If the pattern contained a limit setting, i.e. type=value |
|
42446
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
453 |
hasLimits = true; |
41230 | 454 |
continue; |
455 |
} |
|
456 |
boolean negate = p.charAt(0) == '!'; |
|
457 |
int poffset = negate ? 1 : 0; |
|
458 |
||
459 |
// isolate module name, if any |
|
460 |
int slash = p.indexOf('/', poffset); |
|
461 |
if (slash == poffset) { |
|
462 |
throw new IllegalArgumentException("module name is missing in: \"" + pattern + "\""); |
|
463 |
} |
|
464 |
final String moduleName = (slash >= 0) ? p.substring(poffset, slash) : null; |
|
465 |
poffset = (slash >= 0) ? slash + 1 : poffset; |
|
466 |
||
467 |
final Function<Class<?>, Status> patternFilter; |
|
468 |
if (p.endsWith("*")) { |
|
469 |
// Wildcard cases |
|
470 |
if (p.endsWith(".*")) { |
|
471 |
// Pattern is a package name with a wildcard |
|
472 |
final String pkg = p.substring(poffset, nameLen - 1); |
|
473 |
if (pkg.length() < 2) { |
|
474 |
throw new IllegalArgumentException("package missing in: \"" + pattern + "\""); |
|
475 |
} |
|
476 |
if (negate) { |
|
477 |
// A Function that fails if the class starts with the pattern, otherwise don't care |
|
478 |
patternFilter = c -> matchesPackage(c, pkg) ? Status.REJECTED : Status.UNDECIDED; |
|
479 |
} else { |
|
480 |
// A Function that succeeds if the class starts with the pattern, otherwise don't care |
|
481 |
patternFilter = c -> matchesPackage(c, pkg) ? Status.ALLOWED : Status.UNDECIDED; |
|
482 |
} |
|
483 |
} else if (p.endsWith(".**")) { |
|
484 |
// Pattern is a package prefix with a double wildcard |
|
485 |
final String pkgs = p.substring(poffset, nameLen - 2); |
|
486 |
if (pkgs.length() < 2) { |
|
487 |
throw new IllegalArgumentException("package missing in: \"" + pattern + "\""); |
|
488 |
} |
|
489 |
if (negate) { |
|
490 |
// A Function that fails if the class starts with the pattern, otherwise don't care |
|
491 |
patternFilter = c -> c.getName().startsWith(pkgs) ? Status.REJECTED : Status.UNDECIDED; |
|
492 |
} else { |
|
493 |
// A Function that succeeds if the class starts with the pattern, otherwise don't care |
|
494 |
patternFilter = c -> c.getName().startsWith(pkgs) ? Status.ALLOWED : Status.UNDECIDED; |
|
495 |
} |
|
496 |
} else { |
|
497 |
// Pattern is a classname (possibly empty) with a trailing wildcard |
|
498 |
final String className = p.substring(poffset, nameLen - 1); |
|
499 |
if (negate) { |
|
500 |
// A Function that fails if the class starts with the pattern, otherwise don't care |
|
501 |
patternFilter = c -> c.getName().startsWith(className) ? Status.REJECTED : Status.UNDECIDED; |
|
502 |
} else { |
|
503 |
// A Function that succeeds if the class starts with the pattern, otherwise don't care |
|
504 |
patternFilter = c -> c.getName().startsWith(className) ? Status.ALLOWED : Status.UNDECIDED; |
|
505 |
} |
|
506 |
} |
|
507 |
} else { |
|
508 |
final String name = p.substring(poffset); |
|
509 |
if (name.isEmpty()) { |
|
510 |
throw new IllegalArgumentException("class or package missing in: \"" + pattern + "\""); |
|
511 |
} |
|
512 |
// Pattern is a class name |
|
513 |
if (negate) { |
|
514 |
// A Function that fails if the class equals the pattern, otherwise don't care |
|
515 |
patternFilter = c -> c.getName().equals(name) ? Status.REJECTED : Status.UNDECIDED; |
|
516 |
} else { |
|
517 |
// A Function that succeeds if the class equals the pattern, otherwise don't care |
|
518 |
patternFilter = c -> c.getName().equals(name) ? Status.ALLOWED : Status.UNDECIDED; |
|
519 |
} |
|
520 |
} |
|
521 |
// If there is a moduleName, combine the module name check with the package/class check |
|
522 |
if (moduleName == null) { |
|
523 |
filters.add(patternFilter); |
|
524 |
} else { |
|
525 |
filters.add(c -> moduleName.equals(c.getModule().getName()) ? patternFilter.apply(c) : Status.UNDECIDED); |
|
526 |
} |
|
527 |
} |
|
42446
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
528 |
if (filters.isEmpty() && !hasLimits) { |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
529 |
throw new UnsupportedOperationException("no non-empty patterns"); |
397681315009
8170291: Unpredictable results of j.i.ObjectInputFilter::createFilter
rriggs
parents:
42223
diff
changeset
|
530 |
} |
41230 | 531 |
} |
532 |
||
533 |
/** |
|
534 |
* Parse out a limit for one of maxarray, maxdepth, maxbytes, maxreferences. |
|
535 |
* |
|
536 |
* @param pattern a string with a type name, '=' and a value |
|
537 |
* @return {@code true} if a limit was parsed, else {@code false} |
|
538 |
* @throws IllegalArgumentException if the pattern is missing |
|
539 |
* the name, the Long value is not a number or is negative. |
|
540 |
*/ |
|
541 |
private boolean parseLimit(String pattern) { |
|
542 |
int eqNdx = pattern.indexOf('='); |
|
543 |
if (eqNdx < 0) { |
|
544 |
// not a limit pattern |
|
545 |
return false; |
|
546 |
} |
|
547 |
String valueString = pattern.substring(eqNdx + 1); |
|
548 |
if (pattern.startsWith("maxdepth=")) { |
|
549 |
maxDepth = parseValue(valueString); |
|
550 |
} else if (pattern.startsWith("maxarray=")) { |
|
551 |
maxArrayLength = parseValue(valueString); |
|
552 |
} else if (pattern.startsWith("maxrefs=")) { |
|
553 |
maxReferences = parseValue(valueString); |
|
554 |
} else if (pattern.startsWith("maxbytes=")) { |
|
555 |
maxStreamBytes = parseValue(valueString); |
|
556 |
} else { |
|
557 |
throw new IllegalArgumentException("unknown limit: " + pattern.substring(0, eqNdx)); |
|
558 |
} |
|
559 |
return true; |
|
560 |
} |
|
561 |
||
562 |
/** |
|
563 |
* Parse the value of a limit and check that it is non-negative. |
|
564 |
* @param string inputstring |
|
565 |
* @return the parsed value |
|
566 |
* @throws IllegalArgumentException if parsing the value fails or the value is negative |
|
567 |
*/ |
|
568 |
private static long parseValue(String string) throws IllegalArgumentException { |
|
569 |
// Parse a Long from after the '=' to the end |
|
570 |
long value = Long.parseLong(string); |
|
571 |
if (value < 0) { |
|
572 |
throw new IllegalArgumentException("negative limit: " + string); |
|
573 |
} |
|
574 |
return value; |
|
575 |
} |
|
576 |
||
577 |
/** |
|
578 |
* {@inheritDoc} |
|
579 |
*/ |
|
580 |
@Override |
|
581 |
public Status checkInput(FilterInfo filterInfo) { |
|
582 |
if (filterInfo.references() < 0 |
|
583 |
|| filterInfo.depth() < 0 |
|
584 |
|| filterInfo.streamBytes() < 0 |
|
585 |
|| filterInfo.references() > maxReferences |
|
586 |
|| filterInfo.depth() > maxDepth |
|
587 |
|| filterInfo.streamBytes() > maxStreamBytes) { |
|
588 |
return Status.REJECTED; |
|
589 |
} |
|
590 |
||
591 |
Class<?> clazz = filterInfo.serialClass(); |
|
592 |
if (clazz != null) { |
|
593 |
if (clazz.isArray()) { |
|
594 |
if (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > maxArrayLength) { |
|
595 |
// array length is too big |
|
596 |
return Status.REJECTED; |
|
597 |
} |
|
598 |
do { |
|
599 |
// Arrays are decided based on the component type |
|
600 |
clazz = clazz.getComponentType(); |
|
601 |
} while (clazz.isArray()); |
|
602 |
} |
|
603 |
||
604 |
if (clazz.isPrimitive()) { |
|
605 |
// Primitive types are undecided; let someone else decide |
|
606 |
return Status.UNDECIDED; |
|
607 |
} else { |
|
608 |
// Find any filter that allowed or rejected the class |
|
609 |
final Class<?> cl = clazz; |
|
610 |
Optional<Status> status = filters.stream() |
|
611 |
.map(f -> f.apply(cl)) |
|
612 |
.filter(p -> p != Status.UNDECIDED) |
|
613 |
.findFirst(); |
|
614 |
return status.orElse(Status.UNDECIDED); |
|
615 |
} |
|
616 |
} |
|
617 |
return Status.UNDECIDED; |
|
618 |
} |
|
619 |
||
620 |
/** |
|
621 |
* Returns {@code true} if the class is in the package. |
|
622 |
* |
|
623 |
* @param c a class |
|
624 |
* @param pkg a package name (including the trailing ".") |
|
625 |
* @return {@code true} if the class is in the package, |
|
626 |
* otherwise {@code false} |
|
627 |
*/ |
|
628 |
private static boolean matchesPackage(Class<?> c, String pkg) { |
|
629 |
String n = c.getName(); |
|
630 |
return n.startsWith(pkg) && n.lastIndexOf('.') == pkg.length() - 1; |
|
631 |
} |
|
632 |
||
633 |
/** |
|
634 |
* Returns the pattern used to create this filter. |
|
635 |
* @return the pattern used to create this filter |
|
636 |
*/ |
|
637 |
@Override |
|
638 |
public String toString() { |
|
639 |
return pattern; |
|
640 |
} |
|
641 |
} |
|
642 |
} |
|
643 |
} |