https://issues.apache.org/bugzilla/show_bug.cgi?id=56989

            Bug ID: 56989
           Summary: Classes being loaded from wrong web app.
           Product: Tomcat 7
           Version: 7.0.55
          Hardware: All
                OS: All
            Status: NEW
          Severity: critical
          Priority: P2
         Component: Catalina
          Assignee: dev@tomcat.apache.org
          Reporter: m.marti...@ll.mit.edu

Actually a more correct description might be "Classes NOT being reloaded from
correct web app.   The following scenario results in classes loaded from one
web app showing up in another web app, in place of the correct classes that
should have been loaded from the latter.  The example uses Spring Security to
demonstrate the problem, but after examining the Spring code I don't believe it
is the problem at all and the problem can be produced in other code.

Scenario:

Two web apps deployed into the same Tomcat container, "war1" and "war2".  The
server.xml is configured to use mutual SSL on the connector so requests must
have an acceptable x509 certificate to get through.

Inside each war is a copy of the Spring security framework (though this can be
produced with other classes, this is easy to reproduce).  The exact version
probably doesn't matter, but in this case it was:

..tomcat/webapps/war1/WEB-INF/lib/spring-security-core-3.1.4.RELEASE.jar
..tomcat/webapps/war1/WEB-INF/lib/spring-security-web-3.1.4.RELEASE.jar

and

..tomcat/webapps/war2/WEB-INF/lib/spring-security-core-3.1.4.RELEASE.jar
..tomcat/webapps/war2/WEB-INF/lib/spring-security-web-3.1.4.RELEASE.jar

(along with associated dependencies in each case).

There are no other copies of the Spring libraries anywhere.

Both web apps declare a Spring FilterChain which, naturally, starts with the 
org.springframework.security.web.context.SecurityContextPersistenceFilter
class.

   <bean id="filterChainProxy"
         class="org.springframework.security.web.FilterChainProxy">
      <constructor-arg>
         <list>
         <security:filter-chain pattern="/resources/**" filters="none" />
         <security:filter-chain pattern="/login**" filters="none"/>
         <!--  All authenticated requests must go through this chain -->
         <security:filter-chain request-matcher-ref="httpsRequestMatcher"
                   filters=      
                          "securityContextPersistenceFilter,  
                           securityContextHolderAwareRequestFilter,  
                           myAuthenticationFilter"  
                           />  

         </list>
      </constructor-arg>
   </bean>

The "securityContextPersistenceFilter" is fairly simple, if you have not looked
at their source code. All it is doing is asserting that a Spring
SecurityContext object exists at the start of the request, putting it into a
ThreadLocal and then, at the end of the request, clearing it from the
ThreadLocal and saving the object in the session.

The "securityContextHolderAwareRequestFilter" is another standard Spring filter
that simply wraps the request in a wrapper that knows how to get some standard
security pieces out of the SecurityContext, if it exists and is setup.

The "myAuthenticationFilter" bean is a our own Filter that simply confirms
whether the DN of the user certificate is acceptable and if so, creates an
appropriate "org.springframework.security.core.Authentication" object that the
following servlet application will understand and places it into the
SecurityContextHolder.  Pretty standard stuff:

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) 
throws IOException, ServletException{
    //...
    Authentication myAuth = //... create authentication.
    SecurityContext context = SecurityContextHolder.getContext();
    context.setAuthentication(myAuth);
    //...
    chain.doFilter(request,response);   
}

Pretty simple.

When either of the web apps is deployed alone, this works perfect.

However, when both are deployed into the same tomcat, it only works perfect for
the first web app you access with a request.

Upon sending a request to the second war, the above code will fail because the
SecurityContext object is built from classes loaded from the first war1!

Upon debugging this problem, and very carefully and using the
Class.getProtectionDomain().getCodeSource() API to printout the exact source of
each of the relevant classes involved, the following are true:

 When making the first request against war1 all classes properly load from
war1.

 When making the next request against war2:

    SecurityContextHolder -> properly loaded from war2
    Authentication -> properly loaded from war2
    ThreadLocalSecurityContextHolderStrategy --> LOADED FROM WAR1!!!
    SecurityContextImpl -> LOADED FROM WAR1!


This is bizarre and seems like an explicit violation of the specified class
loader violation.

If you flip the problem around and access war2 first after startup, then the
problem instead shows up when you subsequently try to access war2.

The way the Spring code is structured is pretty simple.

The SecurityContextPersistenceFilter makes use of static calls on the
SecurityContextHolder class to create and 'hold' the SecurityContext object.

The SecurityContextHolder has a static field called 'strategy'.  It has a
static initializer that instantiates the
ThreadLocalSecurityContextHolderStrategy and assigns it to the field.  You can
get to it with getContextHolderStrategy() accessor.

The strategy object itself maintains a ThreadLocal that is used to hold the
SecurityContext object.  It also provides the 'createEmptyContext()' method
which is used during the persistence Filter phase to create the original new
SecurityContextImpl object.   The SecurityContextPersistenceFilter always makes
sure to clear ThreadLocal upon request termination via a 'finally' clause.  All
those steps seem to execute correctly when I step through the code.

The problem seems to be that when the second web app is being invoked and the
SecurityContextHolder class is loaded from war2, it's static fields are not
properly isolated.   

I created an alternative 'SecurityContextHolderStrategy' implementation for the
SecurityContextHolder to use that printed out debug statements (including
identifying the code source) in it's constructor.  I can see that it gets
properly instantiated for each web app when each is loaded.  But when I check
the object that is actually attached to the SecurityContextHolder.strategy
field in the second war, it points to the strategy object from the first war.

I consider this to a pretty serious bug, obviously.

I have a workaround for this particular occurrence of the issue.   

In "MyAuthenticationFilter", the very first time we get a request from an
unauthenticated user, the SecurityContext object is by definition empty and the
way Spring implements it, the hash code for an empty SecurityContextImpl object
will always be "-1".   

So I can detect that it is a new, empty object without having to cast it to a
SecurityContext object (which would fail because that interface class is loaded
from the current web app).  I also can check the class loader against the class
loader used to load the SecurityContextHolder and if they are not equal that
indicates the problem (since they are _supposed_ to be loaded from the same
jar).

Once I detect the problem, I can then simply instantiate a new
SecurityContextImpl object (using the class from the current, correct class
loader) and setting that new object into the SecurityContextHolder.   

This has to be done with reflection to avoid the class cast exceptions:

Class contextHolderClass = SecurityContextHolder.class;
Method getContext = contextHolderClass.getMethod("getContext",(Class[])null);
Object context = getContext.invoke(null,(Object[])null);
if(context.hashCode()==-1){
    //this indicates it is empty.
    if(context.getClass().getClassLoader() !=
contextHolderCls.getClassLoader()){
        //it was not loaded by the correct classloader (amazingly - this can
happen!)
        //create a new one
        String clsName = context.getClass().getName();
        Class cls = contextHolderCls.getClassLoader().loadClass(clsName);
        context = cls.newInstance();
        //now, set this object back into the context holder
        Method[] methods = contextHolderCls.getMethods();
        Method setContext = null;
        for(Method m : methods){
            if("setContext".equals(m.getName())){
                setContext = m;
                break;
            }
        }
        setContext.invoke(null, context);
    }
}

The use of the contextHolder.getClassLoader().loadClass() method and
cls.newInstance() to create the new SecurityContextImpl is probably
anal-retentive caution.  It should work by simply invoking "new
SecurityContextImpl()".

This works around this particular instance of the problem and now both web apps
are able to co-exist perfectly.

Notes:

We replicated this problem with the following platform variations:

Tomcat v7.0.55 & v7.0.42
OS: Linux (two flavors of Centos 6.5), Windows 7 and Mac OS 10.8.5
Java:  Java 7 v1.7.0_65 & v1.7.0_40

Finally - I think it is important to emphasize that while the above workaround
helps fix the occurrence relative to the Spring SecurityContext API, I do not
consider this a 'fix' at all for the core problem, which must be somewhere in
the web app class loader implementation.  The problem can be reproduced with
any java code that follows the same pattern as these Spring classes, which are
not doing anything unusual.

I hope this is helpful!

-- 
You are receiving this mail because:
You are the assignee for the bug.

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to