You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by bu...@apache.org on 2014/09/17 17:40:59 UTC

[Bug 56989] New: Classes being loaded from wrong web app.

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.martinez@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-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


[Bug 56989] Classes being loaded from wrong web app.

Posted by bu...@apache.org.
https://issues.apache.org/bugzilla/show_bug.cgi?id=56989

--- Comment #3 from Mel Martinez <m....@ll.mit.edu> ---
Sigh.  Some typos.  I wish Bugzilla allowed us to edit comments.
----- should be----

The issue is that

a) the SecurityContextHolder class has a static reference to a 'strategy'
object.
b) When a request goes to war1 the SecurityContextHolder class is loaded from
war1, initialized and the strategy object is created out of the classes in
war1.
c) When a request next goes to war2, the SecurityContextHolder class for war2
is loaded from the war2 (correctly) but it's static field STILL points to the
strategy object created in (b), from war1!
d) Because the strategy object was loaded from war1, when it's
"createEmptyContext()" method is called, it creates a SecurityContextImpl
instance using the class for that from war1.
e) Finally, this will "fail" when you attempt to use this object within war2
because assigning it to a reference of type SecurityContext will throw a
ClassCastException.

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

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


[Bug 56989] Classes being loaded from wrong web app.

Posted by bu...@apache.org.
https://issues.apache.org/bugzilla/show_bug.cgi?id=56989

Mark Thomas <ma...@apache.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Severity|critical                    |normal

--- Comment #5 from Mark Thomas <ma...@apache.org> ---
It has been almost a week without any response to Konstantin's request for
information so I am dropping the severity to normal since it clearly isn't that
important to the OP or else they would have responded already.

Like Konstantin, I suspect that additional copies of key classes are present
elsewhere in the class loader hierarchy.

Without the requested information, this issue is going to get closed as
INVALID.

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

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


[Bug 56989] Classes being loaded from wrong web app.

Posted by bu...@apache.org.
https://issues.apache.org/bugzilla/show_bug.cgi?id=56989

Mark Thomas <ma...@apache.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|NEEDINFO                    |RESOLVED
         Resolution|---                         |INVALID

--- Comment #6 from Mark Thomas <ma...@apache.org> ---
A week has passed since my last comment with no response.

Since no further information has been provided, this issue is being closed as
invalid on the basis that the most likely cause is duplicate classes present
int he class loader hierarchy.

If you are able to repeat this issue on a clean install of the latest stable
Tomcat 7 (or later) release then feel free to re-open this issue and provide
the steps to reproduce it.

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

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


[Bug 56989] Classes being loaded from wrong web app.

Posted by bu...@apache.org.
https://issues.apache.org/bugzilla/show_bug.cgi?id=56989

--- Comment #2 from Mel Martinez <m....@ll.mit.edu> ---
Hi Konstantin,

Thanks for your quick response on this topic.

I will try to find the time to throw together a pair of sample web apps that
will reproduce the problem (I cannot provide our actual project code).  That
may not happen for a day or two.

I am not expecting support at all and I would hope that would be clear from the
details I have provided. I have a workaround for the problem already as
indicated in my report.  I am posting this bug report so that the Tomcat
committers are aware that they have a very serious bug in the class loader
implementation.

The use of a ThreadLocal by the Spring code is not the issue.  The ThreadLocal
is being properly cleared at the termination of each request during the
execution of the finally clause in the SecurityContextPersistenceFilter.  This
is a class loader isolation issue.

The issue is that

a) the SecurityContextHolder class has a static reference to a 'strategy'
object.
b) When a request goes to war1 the SecurityContext class is loaded from war1,
initialized and the strategy object is created out of the classes in war1.
c) When a request next goes to war2, the SecurityContext class for war2 is
loaded from the war2 (correctly) but it's static field STILL points to the
strategy object created in (b), from war1!
d) Because the strategy object was loaded from war1, when it's
"createEmptyContext()" method is called, it creates a SecurityContextImpl
instance using the class for that from war1.
e) Finally, this will "fail" when you attempt to use this object within war2
because assigning it to a reference of type SecurityContext will throw a
ClassCastException.

It will throw the ClassCastException because the object implements the
SecurityContext interface from war1, not war2.

The core problem is in step "c" :  Why is the static field for the
SecurityContextHolder class in war2 pointing to the strategy object from war1?

A static field should belong to a class.  The SecurityContextHolder class in
each war are, as we both agree, supposed to be separate classes.  So they are
supposed to have completely separate static fields.

Thanks again for your attention to this.

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

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


[Bug 56989] Classes being loaded from wrong web app.

Posted by bu...@apache.org.
https://issues.apache.org/bugzilla/show_bug.cgi?id=56989

--- Comment #4 from Konstantin Kolinko <kn...@gmail.com> ---
You may try running with a debugger
http://wiki.apache.org/tomcat/FAQ/Developing#Debugging

With a breakpoint in static method SecurityContextHolder.initialize(). It may
be interesting to know what stack trace will be at that point and what class
loaders will be invoked.

> c) When a request next goes to war2, the SecurityContextHolder class for
> war2 is loaded from the war2 (correctly) but it's static field STILL
> points to the strategy object created in (b), from war1!

That would mean that the interface (the class used to declare the static field)
were also loaded from "war1".

There is no reflection API in use here (neither when declaring field, nor when
creating strategy instance - it is created with Java "new" operator), so it
will be loaded from the same class loader that loaded the calling class. So how
can it be loaded from different war? What jars are in this classloader's class
path?

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

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


[Bug 56989] Classes being loaded from wrong web app.

Posted by bu...@apache.org.
https://issues.apache.org/bugzilla/show_bug.cgi?id=56989

Konstantin Kolinko <kn...@gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|NEW                         |NEEDINFO

--- Comment #1 from Konstantin Kolinko <kn...@gmail.com> ---
Please provide a small sample web application that would reproduce the
behaviour that you are observing on a clean Tomcat installation,  so that
another person could reproduce and debug the issue.

Remember that Bugzilla is not a support forum. If you need help, you would
better ask on the users mailing list. Its audience is wider and it is more
likely to find people familiar with your configuration.


Even if both war1 and war2 create a ThreadLocal, they are different thread
locals. As far as I know, when ThreadLocals are stored in a Thread, they are
keyed by Class instance.

As war1 and war2 have different class instances, those are different
ThreadLocals, and cannot interfere with each other, nor can they store a class
loaded from different web application (it cannot be class-casted to
SecurityContext interface loaded from different web application).

> the above code will fail

The word "fail" can mean a lot of different things. What is the actual
behaviour that is observed? (The actual messages, stack traces etc.)

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

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


[Bug 56989] Classes being loaded from wrong web app.

Posted by bu...@apache.org.
https://issues.apache.org/bugzilla/show_bug.cgi?id=56989

Mel Martinez <m....@ll.mit.edu> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |m.martinez@ll.mit.edu

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

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