ObjectInputFilter.java revision 16220:47e0091b0fbe
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
26package java.io;
27
28import java.security.AccessController;
29import java.security.PrivilegedAction;
30import java.security.Security;
31import java.util.ArrayList;
32import java.util.List;
33import java.util.Objects;
34import java.util.Optional;
35import 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
93public 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     * @since 9
108     */
109    Status checkInput(FilterInfo filterInfo);
110
111    /**
112     * FilterInfo provides access to information about the current object
113     * being deserialized and the status of the {@link ObjectInputStream}.
114     * @since 9
115     */
116    interface FilterInfo {
117        /**
118         * The class of an object being deserialized.
119         * For arrays, it is the array type.
120         * For example, the array class name of a 2 dimensional array of strings is
121         * "{@code [[Ljava.lang.String;}".
122         * To check the array's element type, iteratively use
123         * {@link Class#getComponentType() Class.getComponentType} while the result
124         * is an array and then check the class.
125         * The {@code serialClass is null} in the case where a new object is not being
126         * created and to give the filter a chance to check the depth, number of
127         * references to existing objects, and the stream size.
128         *
129         * @return class of an object being deserialized; may be null
130         */
131        Class<?> serialClass();
132
133        /**
134         * The number of array elements when deserializing an array of the class.
135         *
136         * @return the non-negative number of array elements when deserializing
137         * an array of the class, otherwise -1
138         */
139        long arrayLength();
140
141        /**
142         * The current depth.
143         * The depth starts at {@code 1} and increases for each nested object and
144         * decrements when each nested object returns.
145         *
146         * @return the current depth
147         */
148        long depth();
149
150        /**
151         * The current number of object references.
152         *
153         * @return the non-negative current number of object references
154         */
155        long references();
156
157        /**
158         * The current number of bytes consumed.
159         * @implSpec  {@code streamBytes} is implementation specific
160         * and may not be directly related to the object in the stream
161         * that caused the callback.
162         *
163         * @return the non-negative current number of bytes consumed
164         */
165        long streamBytes();
166    }
167
168    /**
169     * The status of a check on the class, array length, number of references,
170     * depth, and stream size.
171     *
172     * @since 9
173     */
174    enum Status {
175        /**
176         * The status is undecided, not allowed and not rejected.
177         */
178        UNDECIDED,
179        /**
180         * The status is allowed.
181         */
182        ALLOWED,
183        /**
184         * The status is rejected.
185         */
186        REJECTED;
187    }
188
189    /**
190     * A utility class to set and get the process-wide filter or create a filter
191     * from a pattern string. If a process-wide filter is set, it will be
192     * used for each {@link ObjectInputStream} that does not set its own filter.
193     * <p>
194     * When setting the filter, it should be stateless and idempotent,
195     * reporting the same result when passed the same arguments.
196     * <p>
197     * The filter is configured during the initialization of the {@code ObjectInputFilter.Config}
198     * class. For example, by calling {@link #getSerialFilter() Config.getSerialFilter}.
199     * If the system property {@code jdk.serialFilter} is defined, it is used
200     * to configure the filter.
201     * If the system property is not defined, and the {@link java.security.Security}
202     * property {@code jdk.serialFilter} is defined then it is used to configure the filter.
203     * Otherwise, the filter is not configured during initialization.
204     * The syntax for each property is the same as for the
205     * {@link #createFilter(String) createFilter} method.
206     * If a filter is not configured, it can be set with
207     * {@link #setSerialFilter(ObjectInputFilter) Config.setSerialFilter}.
208     *
209     * @since 9
210     */
211    final class Config {
212        /* No instances. */
213        private Config() {}
214
215        /**
216         * Lock object for process-wide filter.
217         */
218        private final static Object serialFilterLock = new Object();
219
220        /**
221         * Debug: Logger
222         */
223        private final static System.Logger configLog;
224
225        /**
226         * Logger for debugging.
227         */
228        static void filterLog(System.Logger.Level level, String msg, Object... args) {
229            if (configLog != null) {
230                configLog.log(level, msg, args);
231            }
232        }
233
234        /**
235         * The name for the process-wide deserialization filter.
236         * Used as a system property and a java.security.Security property.
237         */
238        private final static String SERIAL_FILTER_PROPNAME = "jdk.serialFilter";
239
240        /**
241         * The process-wide filter; may be null.
242         * Lookup the filter in java.security.Security or
243         * the system property.
244         */
245        private final static ObjectInputFilter configuredFilter;
246
247        static {
248            configuredFilter = AccessController
249                    .doPrivileged((PrivilegedAction<ObjectInputFilter>) () -> {
250                        String props = System.getProperty(SERIAL_FILTER_PROPNAME);
251                        if (props == null) {
252                            props = Security.getProperty(SERIAL_FILTER_PROPNAME);
253                        }
254                        if (props != null) {
255                            System.Logger log =
256                                    System.getLogger("java.io.serialization");
257                            log.log(System.Logger.Level.INFO,
258                                    "Creating serialization filter from {0}", props);
259                            try {
260                                return createFilter(props);
261                            } catch (RuntimeException re) {
262                                log.log(System.Logger.Level.ERROR,
263                                        "Error configuring filter: {0}", re);
264                            }
265                        }
266                        return null;
267                    });
268            configLog = (configuredFilter != null) ? System.getLogger("java.io.serialization") : null;
269        }
270
271        /**
272         * Current configured filter.
273         */
274        private static ObjectInputFilter serialFilter = configuredFilter;
275
276        /**
277         * Returns the process-wide serialization filter or {@code null} if not configured.
278         *
279         * @return the process-wide serialization filter or {@code null} if not configured
280         */
281        public static ObjectInputFilter getSerialFilter() {
282            synchronized (serialFilterLock) {
283                return serialFilter;
284            }
285        }
286
287        /**
288         * Set the process-wide filter if it has not already been configured or set.
289         *
290         * @param filter the serialization filter to set as the process-wide filter; not null
291         * @throws SecurityException if there is security manager and the
292         *       {@code SerializablePermission("serialFilter")} is not granted
293         * @throws IllegalStateException if the filter has already been set {@code non-null}
294         */
295        public static void setSerialFilter(ObjectInputFilter filter) {
296            Objects.requireNonNull(filter, "filter");
297            SecurityManager sm = System.getSecurityManager();
298            if (sm != null) {
299                sm.checkPermission(ObjectStreamConstants.SERIAL_FILTER_PERMISSION);
300            }
301            synchronized (serialFilterLock) {
302                if (serialFilter != null) {
303                    throw new IllegalStateException("Serial filter can only be set once");
304                }
305                serialFilter = filter;
306            }
307        }
308
309        /**
310         * Returns an ObjectInputFilter from a string of patterns.
311         * <p>
312         * Patterns are separated by ";" (semicolon). Whitespace is significant and
313         * is considered part of the pattern.
314         * If a pattern includes an equals assignment, "{@code =}" it sets a limit.
315         * If a limit appears more than once the last value is used.
316         * <ul>
317         *     <li>maxdepth={@code value} - the maximum depth of a graph</li>
318         *     <li>maxrefs={@code value}  - the maximum number of internal references</li>
319         *     <li>maxbytes={@code value} - the maximum number of bytes in the input stream</li>
320         *     <li>maxarray={@code value} - the maximum array length allowed</li>
321         * </ul>
322         * <p>
323         * Other patterns match or reject class or package name
324         * as returned from {@link Class#getName() Class.getName()} and
325         * if an optional module name is present
326         * {@link java.lang.reflect.Module#getName() class.getModule().getName()}.
327         * Note that for arrays the element type is used in the pattern,
328         * not the array type.
329         * <ul>
330         * <li>If the pattern starts with "!", the class is rejected if the remaining pattern is matched;
331         *     otherwise the class is allowed if the pattern matches.
332         * <li>If the pattern contains "/", the non-empty prefix up to the "/" is the module name;
333         *     if the module name matches the module name of the class then
334         *     the remaining pattern is matched with the class name.
335         *     If there is no "/", the module name is not compared.
336         * <li>If the pattern ends with ".**" it matches any class in the package and all subpackages.
337         * <li>If the pattern ends with ".*" it matches any class in the package.
338         * <li>If the pattern ends with "*", it matches any class with the pattern as a prefix.
339         * <li>If the pattern is equal to the class name, it matches.
340         * <li>Otherwise, the pattern is not matched.
341         * </ul>
342         * <p>
343         * The resulting filter performs the limit checks and then
344         * tries to match the class, if any. If any of the limits are exceeded,
345         * the filter returns {@link Status#REJECTED Status.REJECTED}.
346         * If the class is an array type, the class to be matched is the element type.
347         * Arrays of any number of dimensions are treated the same as the element type.
348         * For example, a pattern of "{@code !example.Foo}",
349         * rejects creation of any instance or array of {@code example.Foo}.
350         * The first pattern that matches, working from left to right, determines
351         * the {@link Status#ALLOWED Status.ALLOWED}
352         * or {@link Status#REJECTED Status.REJECTED} result.
353         * If the limits are not exceeded and no pattern matches the class,
354         * the result is {@link Status#UNDECIDED Status.UNDECIDED}.
355         *
356         * @param pattern the pattern string to parse; not null
357         * @return a filter to check a class being deserialized;
358         *          {@code null} if no patterns
359         * @throws IllegalArgumentException if the pattern string is illegal or
360         *         malformed and cannot be parsed.
361         *         In particular, if any of the following is true:
362         * <ul>
363         * <li>   if a limit is missing the name or the name is not one of
364         *        "maxdepth", "maxrefs", "maxbytes", or "maxarray"
365         * <li>   if the value of the limit can not be parsed by
366         *        {@link Long#parseLong Long.parseLong} or is negative
367         * <li>   if the pattern contains "/" and the module name is missing
368         *        or the remaining pattern is empty
369         * <li>   if the package is missing for ".*" and ".**"
370         * </ul>
371         */
372        public static ObjectInputFilter createFilter(String pattern) {
373            Objects.requireNonNull(pattern, "pattern");
374            return Global.createFilter(pattern);
375        }
376
377        /**
378         * Implementation of ObjectInputFilter that performs the checks of
379         * the process-wide serialization filter. If configured, it will be
380         * used for all ObjectInputStreams that do not set their own filters.
381         *
382         */
383        final static class Global implements ObjectInputFilter {
384            /**
385             * The pattern used to create the filter.
386             */
387            private final String pattern;
388            /**
389             * The list of class filters.
390             */
391            private final List<Function<Class<?>, Status>> filters;
392            /**
393             * Maximum allowed bytes in the stream.
394             */
395            private long maxStreamBytes;
396            /**
397             * Maximum depth of the graph allowed.
398             */
399            private long maxDepth;
400            /**
401             * Maximum number of references in a graph.
402             */
403            private long maxReferences;
404            /**
405             * Maximum length of any array.
406             */
407            private long maxArrayLength;
408
409            /**
410             * Returns an ObjectInputFilter from a string of patterns.
411             *
412             * @param pattern the pattern string to parse
413             * @return a filter to check a class being deserialized;
414             *          {@code null} if no patterns
415             * @throws IllegalArgumentException if the parameter is malformed
416             *                if the pattern is missing the name, the long value
417             *                is not a number or is negative.
418             */
419            static ObjectInputFilter createFilter(String pattern) {
420                try {
421                    return new Global(pattern);
422                } catch (UnsupportedOperationException uoe) {
423                    // no non-empty patterns
424                    return null;
425                }
426            }
427
428            /**
429             * Construct a new filter from the pattern String.
430             *
431             * @param pattern a pattern string of filters
432             * @throws IllegalArgumentException if the pattern is malformed
433             * @throws UnsupportedOperationException if there are no non-empty patterns
434             */
435            private Global(String pattern) {
436                boolean hasLimits = false;
437                this.pattern = pattern;
438
439                maxArrayLength = Long.MAX_VALUE; // Default values are unlimited
440                maxDepth = Long.MAX_VALUE;
441                maxReferences = Long.MAX_VALUE;
442                maxStreamBytes = Long.MAX_VALUE;
443
444                String[] patterns = pattern.split(";");
445                filters = new ArrayList<>(patterns.length);
446                for (int i = 0; i < patterns.length; i++) {
447                    String p = patterns[i];
448                    int nameLen = p.length();
449                    if (nameLen == 0) {
450                        continue;
451                    }
452                    if (parseLimit(p)) {
453                        // If the pattern contained a limit setting, i.e. type=value
454                        hasLimits = true;
455                        continue;
456                    }
457                    boolean negate = p.charAt(0) == '!';
458                    int poffset = negate ? 1 : 0;
459
460                    // isolate module name, if any
461                    int slash = p.indexOf('/', poffset);
462                    if (slash == poffset) {
463                        throw new IllegalArgumentException("module name is missing in: \"" + pattern + "\"");
464                    }
465                    final String moduleName = (slash >= 0) ? p.substring(poffset, slash) : null;
466                    poffset = (slash >= 0) ? slash + 1 : poffset;
467
468                    final Function<Class<?>, Status> patternFilter;
469                    if (p.endsWith("*")) {
470                        // Wildcard cases
471                        if (p.endsWith(".*")) {
472                            // Pattern is a package name with a wildcard
473                            final String pkg = p.substring(poffset, nameLen - 1);
474                            if (pkg.length() < 2) {
475                                throw new IllegalArgumentException("package missing in: \"" + pattern + "\"");
476                            }
477                            if (negate) {
478                                // A Function that fails if the class starts with the pattern, otherwise don't care
479                                patternFilter = c -> matchesPackage(c, pkg) ? Status.REJECTED : Status.UNDECIDED;
480                            } else {
481                                // A Function that succeeds if the class starts with the pattern, otherwise don't care
482                                patternFilter = c -> matchesPackage(c, pkg) ? Status.ALLOWED : Status.UNDECIDED;
483                            }
484                        } else if (p.endsWith(".**")) {
485                            // Pattern is a package prefix with a double wildcard
486                            final String pkgs = p.substring(poffset, nameLen - 2);
487                            if (pkgs.length() < 2) {
488                                throw new IllegalArgumentException("package missing in: \"" + pattern + "\"");
489                            }
490                            if (negate) {
491                                // A Function that fails if the class starts with the pattern, otherwise don't care
492                                patternFilter = c -> c.getName().startsWith(pkgs) ? Status.REJECTED : Status.UNDECIDED;
493                            } else {
494                                // A Function that succeeds if the class starts with the pattern, otherwise don't care
495                                patternFilter = c -> c.getName().startsWith(pkgs) ? Status.ALLOWED : Status.UNDECIDED;
496                            }
497                        } else {
498                            // Pattern is a classname (possibly empty) with a trailing wildcard
499                            final String className = p.substring(poffset, nameLen - 1);
500                            if (negate) {
501                                // A Function that fails if the class starts with the pattern, otherwise don't care
502                                patternFilter = c -> c.getName().startsWith(className) ? Status.REJECTED : Status.UNDECIDED;
503                            } else {
504                                // A Function that succeeds if the class starts with the pattern, otherwise don't care
505                                patternFilter = c -> c.getName().startsWith(className) ? Status.ALLOWED : Status.UNDECIDED;
506                            }
507                        }
508                    } else {
509                        final String name = p.substring(poffset);
510                        if (name.isEmpty()) {
511                            throw new IllegalArgumentException("class or package missing in: \"" + pattern + "\"");
512                        }
513                        // Pattern is a class name
514                        if (negate) {
515                            // A Function that fails if the class equals the pattern, otherwise don't care
516                            patternFilter = c -> c.getName().equals(name) ? Status.REJECTED : Status.UNDECIDED;
517                        } else {
518                            // A Function that succeeds if the class equals the pattern, otherwise don't care
519                            patternFilter = c -> c.getName().equals(name) ? Status.ALLOWED : Status.UNDECIDED;
520                        }
521                    }
522                    // If there is a moduleName, combine the module name check with the package/class check
523                    if (moduleName == null) {
524                        filters.add(patternFilter);
525                    } else {
526                        filters.add(c -> moduleName.equals(c.getModule().getName()) ? patternFilter.apply(c) : Status.UNDECIDED);
527                    }
528                }
529                if (filters.isEmpty() && !hasLimits) {
530                    throw new UnsupportedOperationException("no non-empty patterns");
531                }
532            }
533
534            /**
535             * Parse out a limit for one of maxarray, maxdepth, maxbytes, maxreferences.
536             *
537             * @param pattern a string with a type name, '=' and a value
538             * @return {@code true} if a limit was parsed, else {@code false}
539             * @throws IllegalArgumentException if the pattern is missing
540             *                the name, the Long value is not a number or is negative.
541             */
542            private boolean parseLimit(String pattern) {
543                int eqNdx = pattern.indexOf('=');
544                if (eqNdx < 0) {
545                    // not a limit pattern
546                    return false;
547                }
548                String valueString = pattern.substring(eqNdx + 1);
549                if (pattern.startsWith("maxdepth=")) {
550                    maxDepth = parseValue(valueString);
551                } else if (pattern.startsWith("maxarray=")) {
552                    maxArrayLength = parseValue(valueString);
553                } else if (pattern.startsWith("maxrefs=")) {
554                    maxReferences = parseValue(valueString);
555                } else if (pattern.startsWith("maxbytes=")) {
556                    maxStreamBytes = parseValue(valueString);
557                } else {
558                    throw new IllegalArgumentException("unknown limit: " + pattern.substring(0, eqNdx));
559                }
560                return true;
561            }
562
563            /**
564             * Parse the value of a limit and check that it is non-negative.
565             * @param string inputstring
566             * @return the parsed value
567             * @throws IllegalArgumentException if parsing the value fails or the value is negative
568             */
569            private static long parseValue(String string) throws IllegalArgumentException {
570                // Parse a Long from after the '=' to the end
571                long value = Long.parseLong(string);
572                if (value < 0) {
573                    throw new IllegalArgumentException("negative limit: " + string);
574                }
575                return value;
576            }
577
578            /**
579             * {@inheritDoc}
580             */
581            @Override
582            public Status checkInput(FilterInfo filterInfo) {
583                if (filterInfo.references() < 0
584                        || filterInfo.depth() < 0
585                        || filterInfo.streamBytes() < 0
586                        || filterInfo.references() > maxReferences
587                        || filterInfo.depth() > maxDepth
588                        || filterInfo.streamBytes() > maxStreamBytes) {
589                    return Status.REJECTED;
590                }
591
592                Class<?> clazz = filterInfo.serialClass();
593                if (clazz != null) {
594                    if (clazz.isArray()) {
595                        if (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > maxArrayLength) {
596                            // array length is too big
597                            return Status.REJECTED;
598                        }
599                        do {
600                            // Arrays are decided based on the component type
601                            clazz = clazz.getComponentType();
602                        } while (clazz.isArray());
603                    }
604
605                    if (clazz.isPrimitive())  {
606                        // Primitive types are undecided; let someone else decide
607                        return Status.UNDECIDED;
608                    } else {
609                        // Find any filter that allowed or rejected the class
610                        final Class<?> cl = clazz;
611                        Optional<Status> status = filters.stream()
612                                .map(f -> f.apply(cl))
613                                .filter(p -> p != Status.UNDECIDED)
614                                .findFirst();
615                        return status.orElse(Status.UNDECIDED);
616                    }
617                }
618                return Status.UNDECIDED;
619            }
620
621            /**
622             * Returns {@code true} if the class is in the package.
623             *
624             * @param c   a class
625             * @param pkg a package name (including the trailing ".")
626             * @return {@code true} if the class is in the package,
627             * otherwise {@code false}
628             */
629            private static boolean matchesPackage(Class<?> c, String pkg) {
630                String n = c.getName();
631                return n.startsWith(pkg) && n.lastIndexOf('.') == pkg.length() - 1;
632            }
633
634            /**
635             * Returns the pattern used to create this filter.
636             * @return the pattern used to create this filter
637             */
638            @Override
639            public String toString() {
640                return pattern;
641            }
642        }
643    }
644}
645