• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef CONSCRYPT_APPDATA_H_
18 #define CONSCRYPT_APPDATA_H_
19 
20 #include "NetFd.h"
21 #include "NetworkUtil.h"
22 #include "Trace.h"
23 #include "compat.h"
24 
25 #include <jni.h>
26 #include <mutex>
27 
28 namespace conscrypt {
29 
30 /**
31  * Our additional application data needed for getting synchronization right.
32  * This maybe warrants a bit of lengthy prose:
33  *
34  * (1) We use a flag to reflect whether we consider the SSL connection alive.
35  * Any read or write attempt loops will be cancelled once this flag becomes 0.
36  *
37  * (2) We use an int to count the number of threads that are blocked by the
38  * underlying socket. This may be at most two (one reader and one writer), since
39  * the Java layer ensures that no more threads will enter the native code at the
40  * same time.
41  *
42  * (3) The pipe is used primarily as a means of cancelling a blocking select()
43  * when we want to close the connection (aka "emergency button"). It is also
44  * necessary for dealing with a possible race condition situation: There might
45  * be cases where both threads see an SSL_ERROR_WANT_READ or
46  * SSL_ERROR_WANT_WRITE. Both will enter a select() with the proper argument.
47  * If one leaves the select() successfully before the other enters it, the
48  * "success" event is already consumed and the second thread will be blocked,
49  * possibly forever (depending on network conditions).
50  *
51  * The idea for solving the problem looks like this: Whenever a thread is
52  * successful in moving around data on the network, and it knows there is
53  * another thread stuck in a select(), it will write a byte to the pipe, waking
54  * up the other thread. A thread that returned from select(), on the other hand,
55  * knows whether it's been woken up by the pipe. If so, it will consume the
56  * byte, and the original state of affairs has been restored.
57  *
58  * The pipe may seem like a bit of overhead, but it fits in nicely with the
59  * other file descriptors of the select(), so there's only one condition to wait
60  * for.
61  *
62  * (4) Finally, a mutex is needed to make sure that at most one thread is in
63  * either SSL_read() or SSL_write() at any given time. This is an OpenSSL
64  * requirement. We use the same mutex to guard the field for counting the
65  * waiting threads.
66  *
67  * Note: The current implementation assumes that we don't have to deal with
68  * problems induced by multiple cores or processors and their respective
69  * memory caches. One possible problem is that of inconsistent views on the
70  * "aliveAndKicking" field. This could be worked around by also enclosing all
71  * accesses to that field inside a lock/unlock sequence of our mutex, but
72  * currently this seems a bit like overkill. Marking volatile at the very least.
73  *
74  * During handshaking, additional fields are used to up-call into
75  * Java to perform certificate verification and handshake
76  * completion. These are also used in any renegotiation.
77  *
78  * (5) the JNIEnv so we can invoke the Java callback
79  *
80  * (6) a NativeCrypto.SSLHandshakeCallbacks instance for callbacks from native to Java
81  *
82  * (7) a java.io.FileDescriptor wrapper to check for socket close
83  *
84  * We store the ALPN protocols list so we can either send it (from the server) or
85  * select a protocol (on the client). We eagerly acquire a pointer to the array
86  * data so the callback doesn't need to acquire resources that it cannot
87  * release.
88  *
89  * Because renegotiation can be requested by the peer at any time,
90  * care should be taken to maintain an appropriate JNIEnv on any
91  * downcall to openssl since it could result in an upcall to Java. The
92  * current code does try to cover these cases by conditionally setting
93  * the JNIEnv on calls that can read and write to the SSL such as
94  * SSL_do_handshake, SSL_read, SSL_write, and SSL_shutdown.
95  */
96 class AppData {
97 public:
98     volatile int aliveAndKicking;
99     int waitingThreads;
100 #ifdef _WIN32
101     HANDLE interruptEvent;
102 #else
103     int fdsEmergency[2];
104 #endif
105     std::mutex mutex;
106     JNIEnv* env;
107     jobject sslHandshakeCallbacks;
108     char* alpnProtocolsData;
109     size_t alpnProtocolsLength;
110 
111     /**
112      * Creates the application data context for the SSL*.
113      */
114 public:
create()115     static AppData* create() {
116         std::unique_ptr<AppData> appData(new AppData());
117 #ifdef _WIN32
118         HANDLE interruptEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
119         if (interruptEvent == nullptr) {
120             JNI_TRACE("AppData::create WSACreateEvent failed: %d", WSAGetLastError());
121             return nullptr;
122         }
123         appData.get()->interruptEvent = interruptEvent;
124 #else
125         if (pipe(appData.get()->fdsEmergency) == -1) {
126             ALOGE("AppData::create pipe(2) failed: %s", strerror(errno));
127             return nullptr;
128         }
129         if (!NetworkUtil::setBlocking(appData.get()->fdsEmergency[0], false)) {
130             ALOGE("AppData::create fcntl(2) failed: %s", strerror(errno));
131             return nullptr;
132         }
133 #endif
134         return appData.release();
135     }
136 
~AppData()137     ~AppData() {
138         aliveAndKicking = 0;
139 #ifdef _WIN32
140         if (interruptEvent != nullptr) {
141             CloseHandle(interruptEvent);
142         }
143 #else
144         if (fdsEmergency[0] != -1) {
145             close(fdsEmergency[0]);
146         }
147         if (fdsEmergency[1] != -1) {
148             close(fdsEmergency[1]);
149         }
150 #endif
151         clearCallbackState();
152         clearAlpnCallbackState();
153     }
154 
155 private:
AppData()156     AppData()
157         : aliveAndKicking(1),
158           waitingThreads(0),
159           env(nullptr),
160           sslHandshakeCallbacks(nullptr),
161           alpnProtocolsData(nullptr),
162           alpnProtocolsLength(static_cast<size_t>(-1)) {
163 #ifdef _WIN32
164         interruptEvent = nullptr;
165 #else
166         fdsEmergency[0] = -1;
167         fdsEmergency[1] = -1;
168 #endif
169     }
170 
171 public:
172     /**
173      * Sets the callback data for ALPN negotiation. Only called in server-mode.
174      *
175      * @param env The JNIEnv
176      * @param alpnProtocols ALPN protocols so that they may be advertised (by the
177      *                     server) or selected (by the client). Passing
178      *                     non-null enables ALPN. This array is copied so that no
179      *                     global reference to the Java byte array is maintained.
180      */
setAlpnCallbackState(JNIEnv * e,jbyteArray alpnProtocolsJava)181     bool setAlpnCallbackState(JNIEnv* e, jbyteArray alpnProtocolsJava) {
182         clearAlpnCallbackState();
183         if (alpnProtocolsJava != nullptr) {
184             jbyte* alpnProtocols = e->GetByteArrayElements(alpnProtocolsJava, nullptr);
185             if (alpnProtocols == nullptr) {
186                 clearCallbackState();
187                 JNI_TRACE("appData=%p setAlpnCallbackState => alpnProtocols == null", this);
188                 return false;
189             }
190             alpnProtocolsLength = static_cast<size_t>(e->GetArrayLength(alpnProtocolsJava));
191             alpnProtocolsData = new char[alpnProtocolsLength];
192             memcpy(alpnProtocolsData, alpnProtocols, alpnProtocolsLength);
193             e->ReleaseByteArrayElements(alpnProtocolsJava, alpnProtocols, JNI_ABORT);
194         }
195         return true;
196     }
197 
clearAlpnCallbackState()198     void clearAlpnCallbackState() {
199         if (alpnProtocolsData != nullptr) {
200             delete alpnProtocolsData;
201             alpnProtocolsData = nullptr;
202             alpnProtocolsLength = static_cast<size_t>(-1);
203         }
204     }
205 
206     /**
207      * Used to set the SSL-to-Java callback state before each SSL_*
208      * call that may result in a callback. It should be cleared after
209      * the operation returns with clearCallbackState.
210      *
211      * @param env The JNIEnv
212      * @param shc The SSLHandshakeCallbacks
213      * @param fd The FileDescriptor
214      */
setCallbackState(JNIEnv * e,jobject shc,jobject fd)215     bool setCallbackState(JNIEnv* e, jobject shc, jobject fd) {
216         std::unique_ptr<NetFd> netFd;
217         if (fd != nullptr) {
218             netFd.reset(new NetFd(e, fd));
219             if (netFd->isClosed()) {
220                 JNI_TRACE("appData=%p setCallbackState => netFd->isClosed() == true", this);
221                 return false;
222             }
223         }
224         env = e;
225         sslHandshakeCallbacks = shc;
226         return true;
227     }
228 
clearCallbackState()229     void clearCallbackState() {
230         sslHandshakeCallbacks = nullptr;
231         env = nullptr;
232     }
233 };
234 
235 }  // namespace conscrypt
236 
237 #endif  // CONSCRYPT_APPDATA_H_
238