Bug 57091 - Websockets cannot be used in Windows applet plugin environments based on Oracle Java7
Websockets cannot be used in Windows applet plugin environments based on Orac...
Status: RESOLVED FIXED
Product: Tomcat 8
Classification: Unclassified
Component: WebSocket
8.0.14
PC All
: P2 critical (vote)
: ----
Assigned To: Tomcat Developers Mailing List
:
Depends on:
Blocks:
  Show dependency tree
 
Reported: 2014-10-14 13:27 UTC by Niklas Hallqvist
Modified: 2014-10-22 21:29 UTC (History)
0 users



Attachments
A simple websocket client (1.90 KB, text/plain)
2014-10-16 14:45 UTC, Niklas Hallqvist
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Niklas Hallqvist 2014-10-14 13:27:57 UTC
When using the Tomcat8 Websockets implementation in a Windows JRE 1.7.0_67 applet environment an AccessControlException occurs even though a policy allowing everything is in charge.

Exception in thread "anInnocuousThread" java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "setContextClassLoader")
	at java.security.AccessControlContext.checkPermission(Unknown Source)
	at java.security.AccessController.checkPermission(Unknown Source)
	at java.lang.SecurityManager.checkPermission(Unknown Source)
	at sun.plugin2.applet.AWTAppletSecurityManager.checkPermission(Unknown Source)
	at java.lang.Thread.setContextClassLoader(Unknown Source)
	at org.apache.tomcat.websocket.AsyncChannelGroupUtil$AsyncIOThreadFactory.newThread(AsyncChannelGroupUtil.java:112)
	at java.util.concurrent.ThreadPoolExecutor$Worker.<init>(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.addWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:161)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:141)
	at sun.nio.ch.AsynchronousChannelGroupImpl.executeOnPooledThread(Unknown Source)
	at sun.nio.ch.Invoker.invokeIndirectly(Unknown Source)
	at sun.nio.ch.Invoker.invoke(Unknown Source)
	at sun.nio.ch.Invoker.invoke(Unknown Source)
	at sun.nio.ch.WindowsAsynchronousSocketChannelImpl$ReadTask.completed(Unknown Source)
	at sun.nio.ch.Iocp$EventHandlerTask.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)
	at sun.misc.InnocuousThread.run(Unknown Source)

The problem is actually in the JVM which installs a null protection domain which fails every access checked operation.  Apparantly Oracle won't fix this, according to https://issues.apache.org/jira/browse/SSHD-332, but there is a workaround which is fairly easy.  It is modelled after the fix to the SSHD one found in the link above.

Index: /d/sd0h/h/niklas/java/workspace-1/Tomcat8/java/org/apache/tomcat/websocket/AsyncChannelGroupUtil.java
===================================================================
--- /d/sd0h/h/niklas/java/workspace-1/Tomcat8/java/org/apache/tomcat/websocket/AsyncChannelGroupUtil.java	(revision 1630809)
+++ /d/sd0h/h/niklas/java/workspace-1/Tomcat8/java/org/apache/tomcat/websocket/AsyncChannelGroupUtil.java	(working copy)
@@ -18,6 +18,8 @@
 
 import java.io.IOException;
 import java.nio.channels.AsynchronousChannelGroup;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.ThreadFactory;
@@ -106,12 +108,16 @@
         private AtomicInteger count = new AtomicInteger(0);
 
         @Override
-        public Thread newThread(Runnable r) {
-            Thread t = new Thread(r);
-            t.setName("WebSocketClient-AsyncIO-" + count.incrementAndGet());
-            t.setContextClassLoader(this.getClass().getClassLoader());
-            t.setDaemon(true);
-            return t;
+        public Thread newThread(final Runnable r) {
+            return (Thread)AccessController.doPrivileged(new PrivilegedAction<Object>() {
+        	public Object run() {
+        	    Thread t = new Thread(r);
+        	    t.setName("WebSocketClient-AsyncIO-" + count.incrementAndGet());
+        	    t.setContextClassLoader(this.getClass().getClassLoader());
+        	    t.setDaemon(true);
+        	    return t;
+        	}
+            });
         }
     }
 }
Comment 1 Mark Thomas 2014-10-14 19:26:50 UTC
Does this still work if you narrow the doPrivileged() block to just the setContextClassLoader() call? If so provide an updated patch and I'll apply it. If not, why not?
Comment 2 Niklas Hallqvist 2014-10-14 22:16:33 UTC
(In reply to Mark Thomas from comment #1)
> Does this still work if you narrow the doPrivileged() block to just the
> setContextClassLoader() call? If so provide an updated patch and I'll apply
> it. If not, why not?

Actually I haven't tested that, I may do that toorrow localtime (UTC+2).
But... I know for a fact that the AccessController failed other operations before that, inside the Thread constructor, but the exceptions were masked by inner try-clauses.
In a normal AccessController setup they wouldn't have failed, which is why I covered the whole thing in the doPrivileged.

As the problem is really that the method is called with the wrong AccessController setup established for all of the duraton, I thought it most safe to actually cover all of the code, even if not strictly required at this moment.

But as I said, I may do the tests tomorrow.

I have a testcase btw, but in order to run it you need to sign the code and trust the ceritifcate in the browser which will run the test.  If you want it I can make a package.
Comment 3 Mark Thomas 2014-10-15 12:46:37 UTC
A test case would be helpful, thank you.
Comment 4 Niklas Hallqvist 2014-10-16 14:45:47 UTC
Created attachment 32117 [details]
A simple websocket client

A simple websocket client test.
Name it se/appli/test/WebsocketClientTest.java
Compile, put in a jar, sign, call it test-signed.jar.
Install it somewhere in a web-catalog along with an html-file like:

<html>
  <head><title>WebsocketClientTest</title></head>
  <body>
    <object type="application/x-java-applet" width="800" height="600">
      <param name="code" value="se.appli.test.WebsocketClientTest"/>
      <param name="archive" value="test-signed.jar,websocket-api.jar,tomcat-websocket.jar,tomcat-util.jar,tomcat-juli.jar"/>
      <param name="uri" value="ws://tomcat8.appli.se:8080/websocket/test"/>
    </object>
  </body>
</html>

Alter the uri-parameter to somewhere a websocket will reply.
Any websocket service that provides a greeting (e.g. an hello world websocket test) will suffice.

Also install the foloowing jar files in there:

websocket-api.jar
tomcat-websocket.jar
tomcat-util.jar
tomcat-juli.jar

Fire up a browser on a MS Windows box with a JRE7 installed, whcih is trusting the key you signed the applet with.
Load the HTML file.  Watch the AccessControlException come up in the console.
Comment 5 Mark Thomas 2014-10-22 16:06:51 UTC
A note for folks trying to reproduce this, the Tomcat JARs need to be signed as well.
Comment 6 Mark Thomas 2014-10-22 19:33:35 UTC
Thanks for the test case. Very helpful.

I can repeat this issue with Java 8 and Java 7 and I have applied your suggested patch to 8.0.x for 8.0.15 onwards and 7.0.x for 7.0.57 onwards.

On a related topic, would it be helpful at all if the Tomcat JARs were signed by the ASF (we recently started to use Symantec's code signing service). If this would be useful, please open a new enhancement request and we'll figure out how to fit JSAR signing into the build process.
Comment 7 Niklas Hallqvist 2014-10-22 21:29:26 UTC
(In reply to Mark Thomas from comment #6)
> Thanks for the test case. Very helpful.
> 
> I can repeat this issue with Java 8 and Java 7 and I have applied your
> suggested patch to 8.0.x for 8.0.15 onwards and 7.0.x for 7.0.57 onwards.
> 
> On a related topic, would it be helpful at all if the Tomcat JARs were
> signed by the ASF (we recently started to use Symantec's code signing
> service). If this would be useful, please open a new enhancement request and
> we'll figure out how to fit JSAR signing into the build process.

Sorry I forgot to tell the tomcat jars needed to be signed as well.

I am not sure it will be helpful, maybe it will now, but at least at some point in time, mixed signers were not working too well in our applet setups, so we decided sign all jars ourselves, 3rd party or not, with our own certificate as a standard practice these days.  Of course we would have the potential to verify the jars' origin if they were signed, but we would still overwrite the signature with our own before deployment.

Thanks for including the fix, this way I don't need to have a customized tomcat version for our needs.