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     */
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>
196     * The filter is configured during the initialization of the {@code ObjectInputFilter.Config}
197     * class. For example, by calling {@link #getSerialFilter() Config.getSerialFilter}.
198     * If the system property {@code jdk.serialFilter} is defined, it is used
199     * to configure the filter.
200     * If the system property is not defined, and the {@link java.security.Security}
201     * property {@code jdk.serialFilter} is defined then it is used to configure the filter.
202     * Otherwise, the filter is not configured during initialization.
203     * The syntax for each property is the same as for the
204     * {@link #createFilter(String) createFilter} method.
205     * If a filter is not configured, it can be set with
206     * {@link #setSerialFilter(ObjectInputFilter) Config.setSerialFilter}.
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
325         * {@link Module#getName() class.getModule().getName()}.
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.
352         * If the limits are not exceeded and no pattern matches the class,
353         * the result is {@link Status#UNDECIDED Status.UNDECIDED}.
354         *
355         * @param pattern the pattern string to parse; not null
356         * @return a filter to check a class being deserialized;
357         *          {@code null} if no patterns
358         * @throws IllegalArgumentException if the pattern string is illegal or
359         *         malformed and cannot be parsed.
360         *         In particular, if any of the following is true:
361         * <ul>
362         * <li>   if a limit is missing the name or the name is not one of
363         *        "maxdepth", "maxrefs", "maxbytes", or "maxarray"
364         * <li>   if the value of the limit can not be parsed by
365         *        {@link Long#parseLong Long.parseLong} or is negative
366         * <li>   if the pattern contains "/" and the module name is missing
367         *        or the remaining pattern is empty
368         * <li>   if the package is missing for ".*" and ".**"
369         * </ul>
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
412             * @return a filter to check a class being deserialized;
413             *          {@code null} if no patterns
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) {
419                try {
420                    return new Global(pattern);
421                } catch (UnsupportedOperationException uoe) {
422                    // no non-empty patterns
423                    return null;
424                }
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
432             * @throws UnsupportedOperationException if there are no non-empty patterns
433             */
434            private Global(String pattern) {
435                boolean hasLimits = false;
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
453                        hasLimits = true;
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                }
528                if (filters.isEmpty() && !hasLimits) {
529                    throw new UnsupportedOperationException("no non-empty patterns");
530                }
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}
644