Author: amiloslavskiy
Date: Thu Oct 15 10:16:39 2020
New Revision: 1882523

URL: http://svn.apache.org/viewvc?rev=1882523&view=rev
Log:
JavaHL: Fix crash when TunnelAgent continues reading after being closed

The problem here is that when 'RemoteSession' is destroyed, it also
does 'apr_pool_destroy()' which frees memory behind 'apr_file_t', which
is represented by 'TunnelChannel.nativeChannel' in Java.
'TunnelChannel' runs on a different thread and is unaware that
'apr_file_t' pointer is now invalid.

Fix this by informing 'TunnelChannel' before 'apr_file_t' is freed.

This crash is demonstrated by the following JavaHL tests:
* testCrash_RequestChannel_nativeRead_AfterException
* testCrash_RequestChannel_nativeRead_AfterSvnError

This commit alone does not fix all problems in these tests, see
subsequent commits as well.

[in subversion/bindings/javahl]
* native/OperationContext.cpp
  (close_TunnelChannel): New function to inform Java side.
  (openTunnel): Keep references to Java tunnel objects.
  (closeTunnel): Inform Java side when tunnel is closed.
* src/org/apache/subversion/javahl/util/RequestChannel.java
* src/org/apache/subversion/javahl/util/ResponseChannel.java
  Add 'synchronized' to avoid error described in 'syncClose()'
* src/org/apache/subversion/javahl/util/Tunnel.java
  A new function to clean up. I decided not to change old '.close()'
  because the new function can lead to a deadlock if used incorrectly.

Modified:
    
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/native/OperationContext.cpp
    
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java
    
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java
    
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java

Modified: 
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/native/OperationContext.cpp
URL: 
http://svn.apache.org/viewvc/subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/native/OperationContext.cpp?rev=1882523&r1=1882522&r2=1882523&view=diff
==============================================================================
--- 
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/native/OperationContext.cpp
 (original)
+++ 
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/native/OperationContext.cpp
 Thu Oct 15 10:16:39 2020
@@ -492,6 +492,8 @@ public:
       request_out(NULL),
       response_in(NULL),
       response_out(NULL),
+      jrequest(NULL),
+      jresponse(NULL),
       jclosecb(NULL)
     {
       status = apr_file_pipe_create_ex(&request_in, &request_out,
@@ -512,6 +514,8 @@ public:
   apr_file_t *response_in;
   apr_file_t *response_out;
   apr_status_t status;
+  jobject jrequest;
+  jobject jresponse;
   jobject jclosecb;
 };
 
@@ -523,7 +527,10 @@ jobject create_Channel(const char *class
   jmethodID ctor = env->GetMethodID(cls, "<init>", "(J)V");
   if (JNIUtil::isJavaExceptionThrown())
     return NULL;
-  return env->NewObject(cls, ctor, reinterpret_cast<jlong>(fd));
+  jobject channel = env->NewObject(cls, ctor, reinterpret_cast<jlong>(fd));
+  if (JNIUtil::isJavaExceptionThrown())
+    return NULL;
+  return env->NewGlobalRef(channel);
 }
 
 jobject create_RequestChannel(JNIEnv *env, apr_file_t *fd)
@@ -534,6 +541,24 @@ jobject create_ResponseChannel(JNIEnv *e
 {
   return create_Channel(JAVAHL_CLASS("/util/ResponseChannel"), env, fd);
 }
+void close_TunnelChannel(JNIEnv* env, jobject channel)
+{
+  // Usually after this function, the memory will be freed behind
+  // 'TunnelChannel.nativeChannel'. Ask Java side to forget it. This is the
+  // only way to avoid a JVM crash when 'TunnelAgent' tries to read/write,
+  // not knowing that 'TunnelChannel' is already closed in native side.
+  static jmethodID mid = 0;
+  if (0 == mid)
+    {
+      jclass cls;
+      SVN_JNI_CATCH_VOID(
+        cls = env->FindClass(JAVAHL_CLASS("/util/TunnelChannel")));
+      SVN_JNI_CATCH_VOID(mid = env->GetMethodID(cls, "syncClose", "()V"));
+    }
+
+  SVN_JNI_CATCH_VOID(env->CallVoidMethod(channel, mid));
+  env->DeleteGlobalRef(channel);
+}
 } // anonymous namespace
 
 svn_boolean_t
@@ -590,10 +615,10 @@ OperationContext::openTunnel(svn_stream_
 
   JNIEnv *env = JNIUtil::getEnv();
 
-  jobject jrequest = create_RequestChannel(env, tc->request_in);
+  tc->jrequest = create_RequestChannel(env, tc->request_in);
   SVN_JNI_CATCH(, SVN_ERR_BASE);
 
-  jobject jresponse = create_ResponseChannel(env, tc->response_out);
+  tc->jresponse = create_ResponseChannel(env, tc->response_out);
   SVN_JNI_CATCH(, SVN_ERR_BASE);
 
   jstring jtunnel_name = JNIUtil::makeJString(tunnel_name);
@@ -625,7 +650,7 @@ OperationContext::openTunnel(svn_stream_
   jobject jtunnelcb = jobject(tunnel_baton);
   SVN_JNI_CATCH(
       tc->jclosecb = env->CallObjectMethod(
-          jtunnelcb, mid, jrequest, jresponse,
+          jtunnelcb, mid, tc->jrequest, tc->jresponse,
           jtunnel_name, juser, jhostname, jint(port)),
       SVN_ERR_BASE);
 
@@ -657,7 +682,12 @@ void
 OperationContext::closeTunnel(void *tunnel_context, void *)
 {
   TunnelContext* tc = static_cast<TunnelContext*>(tunnel_context);
+  jobject jrequest = tc->jrequest;
+  jobject jresponse = tc->jresponse;
   jobject jclosecb = tc->jclosecb;
+
+  // Note that this closes other end of the pipe, which cancels and
+  // prevents further read/writes in 'TunnelAgent'
   delete tc;
 
   JNIEnv *env = JNIUtil::getEnv();
@@ -666,4 +696,14 @@ OperationContext::closeTunnel(void *tunn
 
   if (jclosecb)
     callCloseTunnelCallback(env, jclosecb);
+
+  if (jrequest)
+    {
+      close_TunnelChannel(env, jrequest);
+    }
+
+  if (jresponse)
+    {
+      close_TunnelChannel(env, jresponse);
+    }
 }

Modified: 
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java
URL: 
http://svn.apache.org/viewvc/subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java?rev=1882523&r1=1882522&r2=1882523&view=diff
==============================================================================
--- 
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java
 (original)
+++ 
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java
 Thu Oct 15 10:16:39 2020
@@ -42,15 +42,18 @@ class RequestChannel
 
     public int read(ByteBuffer dst) throws IOException
     {
-        long channel = nativeChannel.get();
-        if (channel != 0)
-            try {
-                return nativeRead(channel, dst);
-            } catch (IOException ex) {
-                nativeChannel.set(0); // Close the channel
-                throw ex;
-            }
-        throw new ClosedChannelException();
+        synchronized (nativeChannel)
+        {
+            long channel = nativeChannel.get();
+            if (channel != 0)
+                try {
+                    return nativeRead(channel, dst);
+                } catch (IOException ex) {
+                    nativeChannel.set(0); // Close the channel
+                    throw ex;
+                }
+            throw new ClosedChannelException();
+        }
     }
 
     private static native int nativeRead(long nativeChannel, ByteBuffer dst)

Modified: 
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java
URL: 
http://svn.apache.org/viewvc/subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java?rev=1882523&r1=1882522&r2=1882523&view=diff
==============================================================================
--- 
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java
 (original)
+++ 
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java
 Thu Oct 15 10:16:39 2020
@@ -42,15 +42,18 @@ class ResponseChannel
 
     public int write(ByteBuffer src) throws IOException
     {
-        long channel = this.nativeChannel.get();
-        if (channel != 0)
-            try {
-                return nativeWrite(channel, src);
-            } catch (IOException ex) {
-                nativeChannel.set(0); // Close the channel
-                throw ex;
-            }
-        throw new ClosedChannelException();
+        synchronized (nativeChannel)
+        {
+            long channel = this.nativeChannel.get();
+            if (channel != 0)
+                try {
+                    return nativeWrite(channel, src);
+                } catch (IOException ex) {
+                    nativeChannel.set(0); // Close the channel
+                    throw ex;
+                }
+            throw new ClosedChannelException();
+        }
     }
 
     private static native int nativeWrite(long nativeChannel, ByteBuffer src)

Modified: 
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java
URL: 
http://svn.apache.org/viewvc/subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java?rev=1882523&r1=1882522&r2=1882523&view=diff
==============================================================================
--- 
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java
 (original)
+++ 
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java
 Thu Oct 15 10:16:39 2020
@@ -50,6 +50,39 @@ abstract class TunnelChannel implements
             nativeClose(channel);
     }
 
+    /**
+     * Wait for current read/write to complete, then close() channel.
+     * Compared to close(), it has the following differences:
+     * <ul>
+     *   <li>
+     *     Prevents race condition where read/write could use incorrect file :
+     *     <ol>
+     *       <li>Writer thread extracts OS file descriptor from 
nativeChannel.</li>
+     *       <li>Writer thread calls OS API to write to file, passing file 
descriptor.</li>
+     *       <li>Writer thread is interrupted.</li>
+     *       <li>Closer thread closes OS file descriptor. The file descriptor 
number is now free.</li>
+     *       <li>Unrelated thread opens a new file. OS reuses the old file 
descriptor (currently free).</li>
+     *       <li>Writer thread resumes inside OS API to write to file.</li>
+     *       <li>Writer thread writes to unrelated file, corrupting it with 
unexpected data.</li>
+     *     </ol>
+     *   </li>
+     *   <li>
+     *     It can no longer cancel a read/write operation already in progress.
+     *     The native implementation closes the other end of the pipe, 
breaking the pipe,
+     *     which prevents the risk of never-completing read/write.
+     *   </li>
+     * <ul/>
+     *
+     * @throws IOException
+     */
+    public void syncClose() throws IOException
+    {
+        synchronized (nativeChannel)
+        {
+            close();
+        }
+    }
+
     private native static void nativeClose(long nativeChannel)
         throws IOException;
 


Reply via email to