JEP415: FilterInThread Example

Roger Riggs roger.riggs at oracle.com
Mon Feb 6 04:55:10 UTC 2023


Hi Heinz,

Indeed, this example is not intuitive and does not do what was intended 
and could use a better explanation.

The interaction of three filters gets complicated and their combination 
depends on the ordering and intention of each filter and the particular 
filter factory goal. The FilterInThread example provides only one of 
many possible functions.

The FilterInThread example uses the JVM-wide filter, thread filter, and 
stream filter for different purposes.

The JVM-wide filter has a particular role in that it can be set on the 
command line via a system property.
It is typically used as a backstop after the application is deployed to 
patch in additional rejection of classes.
The property value syntax allows for either allowing or rejecting 
classes, and an otherwise unmentioned class is left UNDECIDED, possibly 
with some risk exposure.

The thread filter is used to more focus de-serialization on a group of 
classes, either to narrow it or expand it.

The FilterInThread example takes the position that any UNDECIDED in the 
thread's filter and the JVM-wide filter should be rejected even if the 
pattern does not explicitly do so.  This keeps an oversight in filter 
construction from becoming a risk.

The stream filter is included mostly for backward compatibility, 
introduced in JDK 9 via JEP 290. The stream filter is set by the code 
creating the ObjectInputStream and part of its design and purpose. In 
the FilterInThread example, if it returns UNDECIDED, the result is 
determined by a merge of the other two filters and further rejecting 
UNDECIDED.

The bug in the example, that individually rejects UNDECIDED in the 
JVM-wide and thread filters respectively, should instead reject only if 
both return UNDECIDED.

The revised example is:

*// Returns a composite filter of the static JVM-wide filter, a 
thread-specific filter, *// and the stream-specific filter. *public 
ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) 
{ *if (curr == null) { *// Called from the OIS constructor or perhaps 
OIS.setObjectInputFilter with no current filter *var filter = 
filterThreadLocal.get(); *if (filter != null) { *// Merge to invoke the 
thread local filter and then the JVM-wide filter (if any) *filter = 
ObjectInputFilter.merge(filter, next); *return 
ObjectInputFilter.rejectUndecidedClass(filter); *} *return (next == 
null) ? null : ObjectInputFilter.rejectUndecidedClass(next); *} else { 
*// Called from OIS.setObjectInputFilter with a current filter and a 
stream-specific filter. *// The curr filter already incorporates the 
thread filter and static JVM-wide filter *// and rejection of undecided 
classes *// If there is a stream-specific filter merge to invoke it and 
then the current filter. *if (next != null) { *return 
ObjectInputFilter.merge(next, curr); *} *return curr; *} *}


The filters are evaluated as:
merge(restrictLargeArrays,rejectUndecidedClass(merge(allowInteger,allowArrayList)))

The first call to the factory returns a filter:  `var f1 = 
rejectUndecidedClass(merge(allowInteger,allowArrayList))`
The second call to the factory returns filter: `var f2 = 
merge(restrictLargeArrays, f1)`

The filters are evaluated in order, until an accept or reject is returned:
   1) restrictLargeArrays  (stream)
   2) allowInteger            (thread filter)
   3) allowArrayList         (JVM-wide filter)

This has the same value as your ideal but without an extra 
RejectUndecidedClass.

Note that in this composition, the choice by a filter to accept or 
reject can not be overridden by a subsequent filter.

Thank you for the comments and suggestions, Roger

On 2/3/23 1:20 PM, Dr Heinz M. Kabutz wrote:
> I was trying to get my head around the FilterInThread example in JEP 
> 415 (https://openjdk.org/jeps/415) and the JavaDoc for the 
> ObjectInputFilter 
> (https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/ObjectInputFilter.html)
>
> For example, let's assume we have three filters. The first allow 
> ArrayList, the second allows Integer, the third restricts arrays to 
> not be larger than 1000.
>
>     ObjectInputFilter allowArrayList = ObjectInputFilter.allowFilter(
>             Set.of(ArrayList.class, Object.class)::contains, UNDECIDED
>     );
>     ObjectInputFilter allowInteger = ObjectInputFilter.allowFilter(
>             Set.of(Number.class, Integer.class)::contains, UNDECIDED
>     );
>     ObjectInputFilter restrictLargeArrays =
> ObjectInputFilter.Config.createFilter("maxarray=1000");
>
> Let's say that we create a FilterInThread instance and install that as 
> our factory. Furthermore, we set the allowArrayList as the global 
> serial filter. When we call filterInThread.doWithSerialFilter() we 
> pass in the allowInteger filter. Lastly, during the actual 
> deserialization, we call setObjectInputFilter() on the 
> ObjectInputStream with the restrictLargeArrays filter. Ideally, I 
> would want the final filter to look like this:
>
> rejectUndecidedClass(merge(restrictLargeArrays,merge(allowInteger,allowArrayList))) 
>
>
> However, in the FilterInThread example, we add the 
> rejectUndecidedClass() wrapper around each of the steps. Thus we would 
> get something like:
>
> rejectUndecidedClass(merge(restrictLargeArrays,rejectUndecidedClass(merge(allowInteger,rejectUndecidedClass(allowArrayList))))) 
>
>
> Thus we could never allow any classes except for ArrayList.
>
>
> Regards
>
> Heinz
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/core-libs-dev/attachments/20230205/473a6346/attachment-0001.htm>


More information about the core-libs-dev mailing list