Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Keycloak-10162] Usage of ObjectInputStream without checking the object types #7053

Closed
wants to merge 3 commits into from

Conversation

douglaspalmer
Copy link
Contributor

I have an issue with this PR which is why it is currently a draft PR.

This code is JDK 8 specific. JDK 9 introduced java.io.ObjectInputFilter. The filter was then backported to JDK 8 as sun.misc.ObjectInputFilter. How can we maintain JDK compatibility when the package name is different between versions?

@douglaspalmer
Copy link
Contributor Author

With newer JDKs we would also need to swap calls to ObjectInputFilter.Config.setObjectInputFilter(in, filter) with in.setObjectInputFilter(filter)
and
ObjectInputFilter.Config.getObjectInputFilter(in) with in.getObjectInputFilter()

@thomasdarimont
Copy link
Contributor

thomasdarimont commented May 9, 2020

@douglaspalmer how about this:

package demo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

public class SerialFilterDemo {

    public static void main(String[] args) throws Exception {

        Logger.getLogger(DelegatingSerializationFilter.class.getName()).setLevel(Level.FINEST);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(out);

        oos.writeObject("hello");

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));

        JavaSerializationFilter serializationFilter = new DelegatingSerializationFilter();
        serializationFilter.setFilter(ois, "org.keycloak.KeycloakSecurityContext;!*");

        // Java 8
        // System.out.println(sun.misc.ObjectInputFilter.Config.getObjectInputFilter(ois));

        // Java 9+
        // System.out.println(ois.getObjectInputFilter());
    }

    interface JavaSerializationFilter {
        void setFilter(ObjectInputStream ois, String filterPattern);
    }

    static class DelegatingSerializationFilter implements JavaSerializationFilter {

        private static final Logger LOG = Logger.getLogger(DelegatingSerializationFilter.class.getName());

        private static final SerializationFilterAdapter serializationFilterAdapter = isJava8() ? new OnJava8() : new OnJavaAfter8();

        private static boolean isJava8() {
            return "1.8".equals(System.getProperty("java.specification.version"));
        }

        @Override
        public void setFilter(ObjectInputStream ois, String filterPattern) {

            LOG.info("Using: " + serializationFilterAdapter.getClass().getSimpleName());

            if (serializationFilterAdapter.getObjectInputFilter(ois) == null) {
                serializationFilterAdapter.setObjectInputFilter(ois, filterPattern);
            }
        }

        interface SerializationFilterAdapter {

            Object getObjectInputFilter(ObjectInputStream ois);

            void setObjectInputFilter(ObjectInputStream ois, String filterPattern);
        }

        // If codebase stays on Java 8 for a while you could use Java 8 classes directly without reflection
        static class OnJava8 implements SerializationFilterAdapter {

            private static final Method getObjectInputFilterMethod;
            private static final Method setObjectInputFilterMethod;
            private static final Method createFilterMethod;

            static {

                Method getObjectInputFilter;
                Method setObjectInputFilter;
                Method createFilter;

                try {
                    ClassLoader cl = Thread.currentThread().getContextClassLoader();
                    Class<?> objectInputFilterClass = cl.loadClass("sun.misc.ObjectInputFilter");
                    Class<?> objectInputFilterConfigClass = cl.loadClass("sun.misc.ObjectInputFilter$Config");
                    getObjectInputFilter = objectInputFilterConfigClass.getDeclaredMethod("getObjectInputFilter", ObjectInputStream.class);
                    setObjectInputFilter = objectInputFilterConfigClass.getDeclaredMethod("setObjectInputFilter", ObjectInputStream.class, objectInputFilterClass);
                    createFilter = objectInputFilterConfigClass.getDeclaredMethod("createFilter", String.class);
                } catch (ClassNotFoundException | NoSuchMethodException e) {
                    LOG.warning("Could not configure SerializationFilterAdapter: " + e.getMessage());
                    getObjectInputFilter = null;
                    setObjectInputFilter = null;
                    createFilter = null;
                }

                getObjectInputFilterMethod = getObjectInputFilter;
                setObjectInputFilterMethod = setObjectInputFilter;
                createFilterMethod = createFilter;
            }

            public Object getObjectInputFilter(ObjectInputStream ois) {
                try {
                    return getObjectInputFilterMethod.invoke(null, ois);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    LOG.warning("Could not read ObjectFilter from ObjectInputStream: " + e.getMessage());
                    return null;
                }
            }

            public void setObjectInputFilter(ObjectInputStream ois, String filterPattern) {
                try {
                    Object objectFilter = createFilterMethod.invoke(null, filterPattern);
                    setObjectInputFilterMethod.invoke(null, ois, objectFilter);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    LOG.warning("Could not set ObjectFilter: " + e.getMessage());
                }
            }
        }

        // If codebase moves to Java 9+ could use Java 9+ classes directly without reflection and keep the old variant with reflection
        static class OnJavaAfter8 implements SerializationFilterAdapter {

            private static final Method getObjectInputFilterMethod;
            private static final Method setObjectInputFilterMethod;
            private static final Method createFilterMethod;

            static {

                Method getObjectInputFilter;
                Method setObjectInputFilter;
                Method createFilter;

                try {
                    ClassLoader cl = Thread.currentThread().getContextClassLoader();
                    Class<?> objectInputFilterClass = cl.loadClass("java.io.ObjectInputFilter");
                    Class<?> objectInputFilterConfigClass = cl.loadClass("java.io.ObjectInputFilter$Config");
                    Class<?> objectInputStreamClass = cl.loadClass("java.io.ObjectInputStream");
                    getObjectInputFilter = objectInputStreamClass.getDeclaredMethod("getObjectInputFilter");
                    setObjectInputFilter = objectInputStreamClass.getDeclaredMethod("setObjectInputFilter", objectInputFilterClass);
                    createFilter = objectInputFilterConfigClass.getDeclaredMethod("createFilter", String.class);
                } catch (ClassNotFoundException | NoSuchMethodException e) {
                    LOG.warning("Could not configure SerializationFilterAdapter: " + e.getMessage());
                    getObjectInputFilter = null;
                    setObjectInputFilter = null;
                    createFilter = null;
                }

                getObjectInputFilterMethod = getObjectInputFilter;
                setObjectInputFilterMethod = setObjectInputFilter;
                createFilterMethod = createFilter;
            }

            public Object getObjectInputFilter(ObjectInputStream ois) {
                try {
                    return getObjectInputFilterMethod.invoke(ois);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    LOG.warning("Could not read ObjectFilter from ObjectInputStream: " + e.getMessage());
                    return null;
                }
            }

            public void setObjectInputFilter(ObjectInputStream ois, String filterPattern) {
                try {
                    Object objectFilter = createFilterMethod.invoke(ois, filterPattern);
                    setObjectInputFilterMethod.invoke(ois, objectFilter);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    LOG.warning("Could not set ObjectFilter: " + e.getMessage());
                }
            }
        }
    }
}

@douglaspalmer
Copy link
Contributor Author

Thanks @thomasdarimont, I've incorporated your code into my PR.

@douglaspalmer douglaspalmer marked this pull request as ready for review May 12, 2020 16:05
@abstractj abstractj self-assigned this May 12, 2020
@mposolda
Copy link
Contributor

@douglaspalmer Thanks for the PR. I am fine with the codebase itself. However as I pointed, it will be good to run the pipeline to make sure there are not regressions. I have some concerns especially about:

  • Various adapter tests, with various JDK versions (EG. EAP6 and JBoss7 tests which use JDK 7 etc)
  • Server and adapter tests with IBM JDK. I have some fear about IBM JDK due the usage of the internal classes "sun.misc.ObjectInputFilter" etc.

@abstractj
Copy link
Contributor

@douglaspalmer did you manage to talk with @tkyjovsk? As we approach the next release, I believe we need to wrap this up next week.

@thomasdarimont
Copy link
Contributor

In case this is not fixable for ancient JDKs, one could print a big warning on startup, with a hint for upgrading the used Java Version, if an old JVM is detected.

@tkyjovsk
Copy link
Contributor

tkyjovsk commented May 27, 2020

@douglaspalmer Hi, I've finished testing the PR. I didn't find any problem with the server but there is an issue with EAP6 client adapter and JDK 7 and 8 (independent on OS and JDK vendor). See details in the JIRA.

The POMs in this PR are still on 9.0.4-SNAPSHOT. Should be updated to 11.0.0 to match the fix version.

@mposolda
Copy link
Contributor

mposolda commented Jun 3, 2020

FYI. I've sent another PR from this branch #7138 , which adds some more test fixes ( OIDCAdapterClusterTest with various JDKs)

@abstractj
Copy link
Contributor

@douglaspalmer I believe this needs a rebase.

@abstractj
Copy link
Contributor

@douglaspalmer it needs a rebase.

@mposolda
Copy link
Contributor

mposolda commented Jun 8, 2020

@abstractj Closing as this was super-seded by other PR #7138 . See the email for more details

@zywj
Copy link
Contributor

zywj commented Sep 30, 2020

@douglaspalmer
I'm using Spring Session to store the keycloak session. But reporting many WARN messages.
Each request are slower than before using Spring Session.
After I degrading the keycloak dependency version from 11.0.2 to 10.0.2, everything is ok.

2020-09-30 14:40:34.900  INFO 13212 --- [nio-8007-exec-1] o.k.c.u.DelegatingSerializationFilter    : Using OnJavaAfter8 serialization filter adapter
2020-09-30 14:40:34.901  WARN 13212 --- [nio-8007-exec-1] o.k.c.u.DelegatingSerializationFilter    : Could not set ObjectFilter: null
2020-09-30 14:40:34.903  WARN 13212 --- [nio-8007-exec-1] o.k.c.u.DelegatingSerializationFilter    : Could not set ObjectFilter: null
2020-09-30 14:40:35.288  WARN 13212 --- [nio-8007-exec-1] o.k.c.u.DelegatingSerializationFilter    : Could not set ObjectFilter: null
2020-09-30 14:40:35.288  WARN 13212 --- [nio-8007-exec-1] o.k.c.u.DelegatingSerializationFilter    : Could not set ObjectFilter: null
2020-09-30 14:40:35.295  WARN 13212 --- [nio-8007-exec-1] o.k.c.u.DelegatingSerializationFilter    : Could not set ObjectFilter: null
2020-09-30 14:40:35.295  WARN 13212 --- [nio-8007-exec-1] o.k.c.u.DelegatingSerializationFilter    : Could not set ObjectFilter: null
2020-09-30 14:40:35.322  WARN 13212 --- [nio-8007-exec-3] o.k.c.u.DelegatingSerializationFilter    : Could not set ObjectFilter: null
2020-09-30 14:40:35.322  WARN 13212 --- [nio-8007-exec-2] o.k.c.u.DelegatingSerializationFilter    : Could not set ObjectFilter: null
2020-09-30 14:40:35.322  WARN 13212 --- [nio-8007-exec-3] o.k.c.u.DelegatingSerializationFilter    : Could not set ObjectFilter: null
2020-09-30 14:40:35.322  WARN 13212 --- [nio-8007-exec-4] o.k.c.u.DelegatingSerializationFilter    : Could not set ObjectFilter: null
2020-09-30 14:40:35.322  WARN 13212 --- [nio-8007-exec-2] o.k.c.u.DelegatingSerializationFilter    : Could not set ObjectFilter: null


@thomasdarimont
Copy link
Contributor

@zywj which JDK / version are you using?

@zywj
Copy link
Contributor

zywj commented Sep 30, 2020

@thomasdarimont I have tried OracleJDK 8/11 and OpenJDK 8-hotspot , OpenJDK 8-openj9.

@thomasdarimont
Copy link
Contributor

@zywj can you check with a debugger which exception is thrown in
org.keycloak.common.util.DelegatingSerializationFilter.OnJavaAfter8#setObjectInputFilter ?

@zywj
Copy link
Contributor

zywj commented Oct 2, 2020

@thomasdarimont
result of jdk 11:
image

result of jdk 8:
image

@mposolda
Copy link
Contributor

mposolda commented Oct 2, 2020

@zywj There is some work in progress on the issue related to this. We may have more updates regarding this next week...

@zywj
Copy link
Contributor

zywj commented Oct 2, 2020

@mposolda thanks!

@thomasdarimont
Copy link
Contributor

@zywj it seems that the line with the totalObjectRefs check in ObjectInputStream.setObjectFilter (on Java 9 - 11) respective ObjectInputStream.setInternalObjectInputFilter (in Java 8) causes the exception:
This is check is not present in JDK12+.

    public final void setObjectInputFilter(ObjectInputFilter filter) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(ObjectStreamConstants.SERIAL_FILTER_PERMISSION);
        }
        // Allow replacement of the process-wide filter if not already set
        if (serialFilter != null &&
                serialFilter != ObjectInputFilter.Config.getSerialFilter()) {
            throw new IllegalStateException("filter can not be set more than once");
        }
        if (totalObjectRefs > 0 && !Caches.SET_FILTER_AFTER_READ) {
            throw new IllegalStateException(
                    "filter can not be set after an object has been read");
        }
        this.serialFilter = filter;
    }

Could you try running your app with a JDK greater than 11?

@thomasdarimont
Copy link
Contributor

Close but no cigar... the check was present in Java 11, then gone in 12,13,14 and was reintroduced in 15.

@thomasdarimont
Copy link
Contributor

thomasdarimont commented Oct 2, 2020

There is the System property jdk.serialSetFilterAfterRead that you can set to true to explicitly allow setting the ObjectFilter after the Object has been read.

-Djdk.serialSetFilterAfterRead=true

A potential workaround for the problem could be checking the internal state of the ObjectInputStream for preceding reads and avoid setting the filter in that case that would trigger the exception. See below.

import org.jboss.logging.Logger;

import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class DelegatingSerializationFilter {
    private static final Logger LOG = Logger.getLogger(DelegatingSerializationFilter.class.getName());

    private static final boolean JDK_SERIAL_SET_FILTER_AFTER_READ_ALLOWED = Boolean.getBoolean("jdk.serialSetFilterAfterRead");

    private static final SerializationFilterAdapter serializationFilterAdapter = isJava6To8() ? createOnJava6To8Adapter() : createOnJavaAfter8Adapter();

    private static boolean isJava6To8() {
        List<String> olderVersions = Arrays.asList("1.6", "1.7", "1.8");
        return olderVersions.contains(System.getProperty("java.specification.version"));
    }

    private DelegatingSerializationFilter() {
    }

    public static DelegatingSerializationFilter.FilterPatternBuilder builder() {
        return new DelegatingSerializationFilter.FilterPatternBuilder();
    }

    private void setFilter(ObjectInputStream ois, String filterPattern) {
        LOG.debugf("Using: %s", serializationFilterAdapter.getClass().getSimpleName());

        if (serializationFilterAdapter.getObjectInputFilter(ois) == null) {
            serializationFilterAdapter.setObjectInputFilter(ois, filterPattern);
        }
    }

    interface SerializationFilterAdapter {

        Object getObjectInputFilter(ObjectInputStream ois);

        void setObjectInputFilter(ObjectInputStream ois, String filterPattern);
    }

    private static SerializationFilterAdapter createOnJava6To8Adapter() {
        try {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            Class<?> objectInputFilterClass = cl.loadClass("sun.misc.ObjectInputFilter");
            Class<?> objectInputFilterConfigClass = cl.loadClass("sun.misc.ObjectInputFilter$Config");
            Method getObjectInputFilter = objectInputFilterConfigClass.getDeclaredMethod("getObjectInputFilter", ObjectInputStream.class);
            Method setObjectInputFilter = objectInputFilterConfigClass.getDeclaredMethod("setObjectInputFilter", ObjectInputStream.class, objectInputFilterClass);
            Method createFilter = objectInputFilterConfigClass.getDeclaredMethod("createFilter", String.class);

            LOG.info("Using OnJava6To8 serialization filter adapter");
            if (JDK_SERIAL_SET_FILTER_AFTER_READ_ALLOWED) {
                return new OnJava6To8(getObjectInputFilter, setObjectInputFilter, createFilter, null);
            } else {
                // Before setting the ObjectInputFilter, we need to check the internal state of the ObjectInputStream to avoid exceptions if the stream was already used before.
                Field totalObjectRefsField = null;
                try {
                    // this field is checked in the setInternalObjectFilterMethod
                    // lookup totalObjectRefsField to guard ObjectInputFilter configuration
                    totalObjectRefsField = ObjectInputStream.class.getDeclaredField("totalObjectRefs");
                    makeAccessible(totalObjectRefsField);
                } catch (NoSuchFieldException e) {
                    LOG.debug("Could not find totalObjectRefs field in ObjectInputStream", e);
                }
                return new OnJava6To8(getObjectInputFilter, setObjectInputFilter, createFilter, totalObjectRefsField);
            }
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            // This can happen for older JDK updates.
            LOG.warn("Could not configure SerializationFilterAdapter. For better security, it is highly recommended to upgrade to newer JDK version update!");
            LOG.warn("For the Java 7, the recommended update is at least 131 (1.7.0_131 or newer). For the Java 8, the recommended update is at least 121 (1.8.0_121 or newer).");
            LOG.warn("Error details", e);
            return new EmptyFilterAdapter();
        }
    }

    private static SerializationFilterAdapter createOnJavaAfter8Adapter() {
        try {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            Class<?> objectInputFilterClass = cl.loadClass("java.io.ObjectInputFilter");
            Class<?> objectInputFilterConfigClass = cl.loadClass("java.io.ObjectInputFilter$Config");
            Class<?> objectInputStreamClass = cl.loadClass("java.io.ObjectInputStream");
            Method getObjectInputFilter = objectInputStreamClass.getDeclaredMethod("getObjectInputFilter");
            Method setObjectInputFilter = objectInputStreamClass.getDeclaredMethod("setObjectInputFilter", objectInputFilterClass);
            Method createFilter = objectInputFilterConfigClass.getDeclaredMethod("createFilter", String.class);

            LOG.info("Using OnJavaAfter8 serialization filter adapter");
            if (JDK_SERIAL_SET_FILTER_AFTER_READ_ALLOWED) {
                return new OnJavaAfter8(getObjectInputFilter, setObjectInputFilter, createFilter, null);
            } else {
                // Before setting the ObjectInputFilter, we need to check the internal state of the ObjectInputStream to avoid exceptions if the stream was already used before.
                Field totalObjectRefsField = null;
                try {
                    // this field is checked in the setObjectFilterMethod in JDK 9-11 and 15-16+? but not in 12-14
                    // lookup totalObjectRefsField to guard ObjectInputFilter configuration
                    totalObjectRefsField = ObjectInputStream.class.getDeclaredField("totalObjectRefs");
                    makeAccessible(totalObjectRefsField);
                } catch (NoSuchFieldException e) {
                    LOG.debug("Could not find totalObjectRefs field in ObjectInputStream", e);
                }
                return new OnJavaAfter8(getObjectInputFilter, setObjectInputFilter, createFilter, totalObjectRefsField);
            }
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            // This can happen for older JDK updates.
            LOG.warn("Could not configure SerializationFilterAdapter. For better security, it is highly recommended to upgrade to newer JDK version update!");
            LOG.warn("Error details", e);
            return new EmptyFilterAdapter();
        }
    }

    private static void makeAccessible(Field field) {
        if ((!Modifier.isPublic(field.getModifiers()) ||
                !Modifier.isPublic(field.getDeclaringClass().getModifiers()) ||
                Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) {
            field.setAccessible(true);
        }
    }

    static class EmptyFilterAdapter implements SerializationFilterAdapter {

        @Override
        public Object getObjectInputFilter(ObjectInputStream ois) {
            return null;
        }

        @Override
        public void setObjectInputFilter(ObjectInputStream ois, String filterPattern) {
            // NOOP
        }
    }

    // If codebase stays on Java 8 for a while you could use Java 8 classes directly without reflection
    static class OnJava6To8 implements SerializationFilterAdapter {

        private final Method getObjectInputFilterMethod;
        private final Method setObjectInputFilterMethod;
        private final Method createFilterMethod;
        private final Field totalObjectRefsField;

        private OnJava6To8(Method getObjectInputFilterMethod, Method setObjectInputFilterMethod, Method createFilterMethod, Field totalObjectRefsField) {
            this.getObjectInputFilterMethod = getObjectInputFilterMethod;
            this.setObjectInputFilterMethod = setObjectInputFilterMethod;
            this.createFilterMethod = createFilterMethod;
            this.totalObjectRefsField = totalObjectRefsField;
        }

        public Object getObjectInputFilter(ObjectInputStream ois) {
            try {
                return getObjectInputFilterMethod.invoke(null, ois);
            } catch (IllegalAccessException | InvocationTargetException e) {
                LOG.warnf("Could not read ObjectFilter from ObjectInputStream: %s", e.getMessage());
                return null;
            }
        }

        public void setObjectInputFilter(ObjectInputStream ois, String filterPattern) {
            try {
                // Avoid causing an exception when setting the filter to an ObjectInputStream which has already been read from.
                // workaround for bug "filter cannot be set after an object has been read"
                // https://github.com/keycloak/keycloak/pull/7053#issuecomment-701193325
                if (totalObjectRefsField != null) {
                    Long totalObjectRefs = (Long) totalObjectRefsField.get(ois);
                    if (totalObjectRefs != null && totalObjectRefs > 0L) {
                        LOG.debugf("Skip configuring ObjectInputFilter for ObjectInputStream to avoid Exception after object has been read");
                        return;
                    }
                }

                Object objectFilter = createFilterMethod.invoke(null, filterPattern);
                setObjectInputFilterMethod.invoke(null, ois, objectFilter);
            } catch (IllegalAccessException | InvocationTargetException e) {
                LOG.warnf("Could not set ObjectFilter: %s", e.getMessage());
            }
        }
    }

    // If codebase moves to Java 9+ could use Java 9+ classes directly without reflection and keep the old variant with reflection
    static class OnJavaAfter8 implements SerializationFilterAdapter {

        private final Method getObjectInputFilterMethod;
        private final Method setObjectInputFilterMethod;
        private final Method createFilterMethod;
        private final Field totalObjectRefsField;

        private OnJavaAfter8(Method getObjectInputFilterMethod, Method setObjectInputFilterMethod, Method createFilterMethod, Field totalObjectRefsField) {
            this.getObjectInputFilterMethod = getObjectInputFilterMethod;
            this.setObjectInputFilterMethod = setObjectInputFilterMethod;
            this.createFilterMethod = createFilterMethod;
            this.totalObjectRefsField = totalObjectRefsField;
        }

        public Object getObjectInputFilter(ObjectInputStream ois) {
            try {
                return getObjectInputFilterMethod.invoke(ois);
            } catch (IllegalAccessException | InvocationTargetException e) {
                LOG.warnf("Could not read ObjectFilter from ObjectInputStream: %s", e.getMessage());
                return null;
            }
        }

        public void setObjectInputFilter(ObjectInputStream ois, String filterPattern) {
            try {
                // Avoid causing an exception when setting the filter to an ObjectInputStream which has already been read from.
                // workaround for bug "filter cannot be set after an object has been read"
                // This happens in Java 9-11 and from 15-16
                // https://github.com/keycloak/keycloak/pull/7053#issuecomment-701193325
                if (totalObjectRefsField != null) {
                    Long totalObjectRefs = (Long) totalObjectRefsField.get(ois);
                    if (totalObjectRefs != null && totalObjectRefs > 0L) {
                        LOG.debugf("Skip configuring ObjectInputFilter for ObjectInputStream to avoid Exception after object has been read");
                        return;
                    }
                }

                Object objectFilter = createFilterMethod.invoke(ois, filterPattern);
                setObjectInputFilterMethod.invoke(ois, objectFilter);
            } catch (IllegalAccessException | InvocationTargetException e) {
                LOG.warnf("Could not set ObjectFilter: %s", e.getMessage());
            }
        }
    }

    public static class FilterPatternBuilder {

        private Set<Class> classes = new HashSet<>();
        private Set<String> patterns = new HashSet<>();

        public FilterPatternBuilder() {
            // Add "java.util" package by default (contains all the basic collections)
            addAllowedPattern("java.util.*");
        }

        /**
         * This is used when the caller of this method can't use the {@link #addAllowedClass(Class)}. For example because the
         * particular is private or it is not available at the compile time. Or when adding the whole package like "java.util.*"
         *
         * @param pattern
         * @return
         */
        public FilterPatternBuilder addAllowedPattern(String pattern) {
            this.patterns.add(pattern);
            return this;
        }

        public FilterPatternBuilder addAllowedClass(Class javaClass) {
            this.classes.add(javaClass);
            return this;
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();

            for (Class javaClass : classes) {
                builder.append(javaClass.getName()).append(";");
            }
            for (String pattern : patterns) {
                builder.append(pattern).append(";");
            }

            builder.append("!*");

            return builder.toString();
        }

        public void setFilter(ObjectInputStream ois) {
            DelegatingSerializationFilter filter = new DelegatingSerializationFilter();
            String filterPattern = this.toString();
            filter.setFilter(ois, filterPattern);
        }
    }
}

This can be tested with the following program which works for JDK 8 - JDK 16:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class KeycloakDelegatingSerializationBug {

    public static void main(String[] args) throws Exception{

//        System.setSecurityManager(new SecurityManager());

        transport();
        transport();
    }

    private static void transport() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);

        oos.writeObject(new Data());
        oos.writeObject(new Data());


        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));

        Object result = ois.readObject();
        System.out.println(result);

        DelegatingSerializationFilter.builder().addAllowedPattern("*").setFilter(ois);

        result = ois.readObject();
        System.out.println(result);
    }

    public static class Data implements Serializable {
        String s = "foo";
        int i = 42;

        public Data() {
        }

        @Override
        public String toString() {
            return "Data{" +
                    "s='" + s + '\'' +
                    ", i=" + i +
                    '}';
        }
    }
}

@thomasdarimont
Copy link
Contributor

@zywj could you try to run your app with:

-Djdk.serialSetFilterAfterRead=true

@zywj
Copy link
Contributor

zywj commented Oct 2, 2020

@thomasdarimont thanks. i'll try it tomorrow

@zywj
Copy link
Contributor

zywj commented Oct 3, 2020

@thomasdarimont
After setting up the jdk.serialSetFilterAfterRead=true, it reports the spring session data redis error.
My JDK/Version is OracleJDK 11

2020-10-03 20:50:12.943  INFO 23012 --- [nio-8007-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-10-03 20:50:12.943  INFO 23012 --- [nio-8007-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-10-03 20:50:12.966  INFO 23012 --- [nio-8007-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 23 ms
2020-10-03 20:50:12.994  INFO 23012 --- [nio-8007-exec-1] o.k.c.u.DelegatingSerializationFilter    : Using OnJavaAfter8 serialization filter adapter
2020-10-03 20:50:13.001 ERROR 23012 --- [nio-8007-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

org.springframework.data.redis.serializer.SerializationException: Cannot deserialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.InvalidClassException: filter status: REJECTED
	at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.deserialize(JdkSerializationRedisSerializer.java:84) ~[spring-data-redis-2.3.4.RELEASE.jar:2.3.4.RELEASE]
	at org.springframework.data.redis.core.AbstractOperations.deserializeHashValue(AbstractOperations.java:355) ~[spring-data-redis-2.3.4.RELEASE.jar:2.3.4.RELEASE]
	at org.springframework.data.redis.core.AbstractOperations.deserializeHashMap(AbstractOperations.java:299) ~[spring-data-redis-2.3.4.RELEASE.jar:2.3.4.RELEASE]
	at org.springframework.data.redis.core.DefaultHashOperations.entries(DefaultHashOperations.java:247) ~[spring-data-redis-2.3.4.RELEASE.jar:2.3.4.RELEASE]
	at org.springframework.data.redis.core.DefaultBoundHashOperations.entries(DefaultBoundHashOperations.java:183) ~[spring-data-redis-2.3.4.RELEASE.jar:2.3.4.RELEASE]
	at org.springframework.session.data.redis.RedisIndexedSessionRepository.getSession(RedisIndexedSessionRepository.java:440) ~[spring-session-data-redis-2.3.1.RELEASE.jar:2.3.1.RELEASE]
	at org.springframework.session.data.redis.RedisIndexedSessionRepository.findById(RedisIndexedSessionRepository.java:412) ~[spring-session-data-redis-2.3.1.RELEASE.jar:2.3.1.RELEASE]
	at org.springframework.session.data.redis.RedisIndexedSessionRepository.findById(RedisIndexedSessionRepository.java:249) ~[spring-session-data-redis-2.3.1.RELEASE.jar:2.3.1.RELEASE]
	at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getRequestedSession(SessionRepositoryFilter.java:351) ~[spring-session-core-2.3.1.RELEASE.jar:2.3.1.RELEASE]
	at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:289) ~[spring-session-core-2.3.1.RELEASE.jar:2.3.1.RELEASE]
	at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:192) ~[spring-session-core-2.3.1.RELEASE.jar:2.3.1.RELEASE]
	at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:244) ~[tomcat-embed-core-9.0.38.jar:4.0.FR]
	at org.springframework.security.web.context.HttpSessionSecurityContextRepository.loadContext(HttpSessionSecurityContextRepository.java:111) ~[spring-security-web-5.3.4.RELEASE.jar:5.3.4.RELEASE]
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:100) ~[spring-security-web-5.3.4.RELEASE.jar:5.3.4.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.4.RELEASE.jar:5.3.4.RELEASE]
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.3.4.RELEASE.jar:5.3.4.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.4.RELEASE.jar:5.3.4.RELEASE]
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.3.4.RELEASE.jar:5.3.4.RELEASE]
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.3.4.RELEASE.jar:5.3.4.RELEASE]
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:141) ~[spring-session-core-2.3.1.RELEASE.jar:2.3.1.RELEASE]
	at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82) ~[spring-session-core-2.3.1.RELEASE.jar:2.3.1.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.keycloak.adapters.tomcat.AbstractAuthenticatedActionsValve.invoke(AbstractAuthenticatedActionsValve.java:67) ~[spring-boot-container-bundle-11.0.0.jar:11.0.0]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve.invoke(AbstractKeycloakAuthenticatorValve.java:181) ~[spring-boot-container-bundle-11.0.0.jar:11.0.0]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
	at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Caused by: org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.InvalidClassException: filter status: REJECTED
	at org.springframework.core.serializer.support.DeserializingConverter.convert(DeserializingConverter.java:78) ~[spring-core-5.2.9.RELEASE.jar:5.2.9.RELEASE]
	at org.springframework.core.serializer.support.DeserializingConverter.convert(DeserializingConverter.java:36) ~[spring-core-5.2.9.RELEASE.jar:5.2.9.RELEASE]
	at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.deserialize(JdkSerializationRedisSerializer.java:82) ~[spring-data-redis-2.3.4.RELEASE.jar:2.3.4.RELEASE]
	... 57 common frames omitted
Caused by: java.io.InvalidClassException: filter status: REJECTED
	at java.base/java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1343) ~[na:na]
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1975) ~[na:na]
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1851) ~[na:na]
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2139) ~[na:na]
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1668) ~[na:na]
	at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2434) ~[na:na]
	at java.base/java.io.ObjectInputStream.defaultReadObject(ObjectInputStream.java:618) ~[na:na]
	at org.keycloak.KeycloakPrincipal.readObject(KeycloakPrincipal.java:77) ~[keycloak-core-11.0.0.jar:11.0.0]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
	at java.base/java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1175) ~[na:na]
	at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2295) ~[na:na]
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2166) ~[na:na]
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1668) ~[na:na]
	at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2434) ~[na:na]
	at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2328) ~[na:na]
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2166) ~[na:na]
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1668) ~[na:na]
	at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2434) ~[na:na]
	at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2328) ~[na:na]
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2166) ~[na:na]
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1668) ~[na:na]
	at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2434) ~[na:na]
	at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2328) ~[na:na]
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2166) ~[na:na]
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1668) ~[na:na]
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:482) ~[na:na]
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:440) ~[na:na]
	at org.springframework.core.serializer.DefaultDeserializer.deserialize(DefaultDeserializer.java:72) ~[spring-core-5.2.9.RELEASE.jar:5.2.9.RELEASE]
	at org.springframework.core.serializer.support.DeserializingConverter.convert(DeserializingConverter.java:73) ~[spring-core-5.2.9.RELEASE.jar:5.2.9.RELEASE]
	... 59 common frames omitted

@thomasdarimont
Copy link
Contributor

thomasdarimont commented Oct 4, 2020

This exception indicates that you are trying to deserialize types, which are not allowed by the class / package patterns configured in the FilterPatternBuilder.
I think the problem is triggered here: https://github.com/keycloak/keycloak/blob/master/core/src/main/java/org/keycloak/KeycloakPrincipal.java#L77

Could you find out what is missing from the allowed classes, package patterns and tell us?
To do that you can set a breakpoint here: java.io.ObjectInputStream#filterCheck after serialFilter.checkInput(...)).

If you find the missing class you could try to shadow the class org.keycloak.KeycloakPrincipal with an appropriate readObject method by compiling it along your src/main/java source folder.

I think we need a way to configure the DelegatingSerializationFilter via a system property

@mhajas
Copy link
Contributor

mhajas commented Oct 6, 2020

@zywj @thomasdarimont It seems the later issue is already reported here: https://issues.redhat.com/browse/KEYCLOAK-14871.

@zywj Could you please check this: #7471 solved the InvalidClassException: filter status: REJECTED issue?

@mhajas
Copy link
Contributor

mhajas commented Oct 6, 2020

@thomasdarimont Could you please open a new PR with your solution for the Could not set ObjectFilter: null issue? We can continue with the discussion there. This way it is hard for us to see what you have changed.

@thomasdarimont
Copy link
Contributor

@mhajas I could create a new PR, but I think my solution is more of a hack than a real solution...
One of the problems mentioned above was, that the ObjectInputStream implementations across various JDKs
throw an IllegalStateException("filter can not be set after an object has been read") if one tries to configure an ObjectInputFilter on an ObjectInputStream which was already used, e.g. read from.
In my example, I avoid the IllegalStateException by peeking into the internal state of ObjectInputStream to detect whether
the ObjectInputStream was already used. In that case, I simply skip setting the filter and log a debug message - which leaves the ObjectInputStream vulnerable without a filter.

I think the following "fix" would be better:

  • Add a note to the documentation for consumers of the Keycloak API about how to avoid the IllegalStateException("filter can not be set after an object has been read") when deserializing Keycloak data structures:
    Either ensure that the ObjectInputStreams used to deserialize Keycloak data structures are "unused" or set the system property jdk.serialSetFilterAfterRead=true.

For the record here are the changes I did to the DelegatingSerilizationFilter: https://github.com/keycloak/keycloak/compare/master...thomasdarimont:issue/KEYCLOAK-XXX-More-Robust-ObjectInputFilterConfig?expand=1
Apart from my hack I think some elements from the branch could be used, e.g. the revised logging parameters.

@zywj
Copy link
Contributor

zywj commented Oct 7, 2020

@mhajas Sorry, I don't know how to test your code. Do I need to compile keycloak source code?

Close but no cigar... the check was present in Java 11, then gone in 12,13,14 and was reintroduced in 15.

@thomasdarimont Yes. It can be reproduced in 15. So I think the keycloak might has a bug.

@mhajas
Copy link
Contributor

mhajas commented Oct 7, 2020

@mhajas I could create a new PR, but I think my solution is more of a hack than a real solution...
One of the problems mentioned above was, that the ObjectInputStream implementations across various JDKs
throw an IllegalStateException("filter can not be set after an object has been read") if one tries to configure an ObjectInputFilter on an ObjectInputStream which was already used, e.g. read from.
In my example, I avoid the IllegalStateException by peeking into the internal state of ObjectInputStream to detect whether
the ObjectInputStream was already used. In that case, I simply skip setting the filter and log a debug message - which leaves the ObjectInputStream vulnerable without a filter.

I think the following "fix" would be better:

  • Add a note to the documentation for consumers of the Keycloak API about how to avoid the IllegalStateException("filter can not be set after an object has been read") when deserializing Keycloak data structures:
    Either ensure that the ObjectInputStreams used to deserialize Keycloak data structures are "unused" or set the system property jdk.serialSetFilterAfterRead=true.

For the record here are the changes I did to the DelegatingSerilizationFilter: https://github.com/keycloak/keycloak/compare/master...thomasdarimont:issue/KEYCLOAK-XXX-More-Robust-ObjectInputFilterConfig?expand=1
Apart from my hack I think some elements from the branch could be used, e.g. the revised logging parameters.

@thomasdarimont I think we could do both. Add the note to the documentation. And add your workaround to the DelegatingSerilizationFilter, however, I would change the debug log to warning for the Skip configuring ObjectInputFilter... message, so that from the log it can be seen that jdk.serialSetFilterAfterRead should be set to true. @mposolda WDYT?

@mhajas Sorry, I don't know how to test your code. Do I need to compile keycloak source code?

Sorry, it is my fault. Can you try with this keycloak-core jar file? I uploaded updated jar file here: https://issues.redhat.com/browse/KEYCLOAK-14871.

@thomasdarimont
Copy link
Contributor

@mhajas I think documenting this and mentioning the option with the System property is the only sensible thing we can do. Peeking into the internals of ObjectInputStream to avoid an exception is quite nasty especially if a consequence of this is that "no" ObjectInputFilter is applied at all.

I recommend to "fix" this with a section in the Keycloak documentation (and perhaps a reference in the release notes).

@mhajas
Copy link
Contributor

mhajas commented Oct 9, 2020

@thomasdarimont If I understand correctly, currently the exception is ignored as well right? There is just this message Could not set ObjectFilter: null. So I just wanted to make the message meaningful, so that when it pops out, everybody knows what is the problem, and what they should do to avoid it. I created a Jira for this issue: https://issues.redhat.com/browse/KEYCLOAK-15901. If you have something more to add to this issue, please add a comment there.

@hoangdinhnarvar
Copy link

Adding weblogic.oif.serialFilter=sun.misc.ObjectInputFilter also works for me.

@JayAhn2
Copy link

JayAhn2 commented Aug 31, 2022

It happens after introducing spring-session. Using Keycloak 18.0.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants