@@ -99,19 +99,31 @@ function patchTargetLib(targetLib) {
99
99
100
100
const buildVerificationCallback = ( realCallbackAddr ) => {
101
101
if ( ! verificationCallbackCache [ realCallbackAddr ] ) {
102
- const realCallback = realCallbackAddr
102
+ const realCallback = ( ! realCallbackAddr || realCallbackAddr . isNull ( ) )
103
103
? new NativeFunction ( realCallbackAddr , 'int' , [ 'pointer' , 'pointer' ] )
104
104
: ( ) => SSL_VERIFY_INVALID ; // Callback can be null - treat as invalid (=our validation only)
105
105
106
+ let pendingCheckThreads = new Set ( ) ;
107
+
106
108
const hookedCallback = new NativeCallback ( function ( ssl , out_alert ) {
107
- let realResult = false ;
109
+ let realResult = false ; // False = not yet called, 0/1 = call result
110
+
111
+ const threadId = Process . getCurrentThreadId ( ) ;
112
+ const alreadyHaveLock = pendingCheckThreads . has ( threadId ) ;
113
+
114
+ // We try to have only one thread running these checks at a time, as parallel calls
115
+ // here on the same underlying callback seem to crash in some specific scenarios
116
+ while ( pendingCheckThreads . size > 0 && ! alreadyHaveLock ) {
117
+ Thread . sleep ( 0.01 ) ;
118
+ }
119
+ pendingCheckThreads . add ( threadId ) ;
108
120
109
121
if ( targetLib !== 'libboringssl.dylib' ) {
110
122
// Cronet assumes its callback is always called, and crashes if not. iOS's BoringSSL
111
123
// meanwhile seems to use some negative checks in its callback, and rejects the
112
124
// connection independently of the return value here if it's called with a bad cert.
113
125
// End result: we *only sometimes* proactively call the callback.
114
- realResult = realCallback ( ssl , out_alert )
126
+ realResult = realCallback ( ssl , out_alert ) ;
115
127
}
116
128
117
129
// Extremely dumb certificate validation: we accept any chain where the *exact* CA cert
@@ -138,16 +150,18 @@ function patchTargetLib(targetLib) {
138
150
const certData = new Uint8Array ( certPointer . readByteArray ( certDataLength ) ) ;
139
151
140
152
if ( certData . every ( ( byte , j ) => CERT_DER [ j ] === byte ) ) {
153
+ if ( ! alreadyHaveLock ) pendingCheckThreads . delete ( threadId ) ;
141
154
return SSL_VERIFY_OK ;
142
155
}
143
156
}
144
157
145
158
// No matched peer - fallback to the provided callback instead:
146
- if ( realResult !== false ) {
147
- return realResult ;
148
- } else {
149
- return realCallback ( ssl , out_alert ) ;
159
+ if ( realResult === false ) { // Haven't called it yet
160
+ realResult = realCallback ( ssl , out_alert ) ;
150
161
}
162
+
163
+ if ( ! alreadyHaveLock ) pendingCheckThreads . delete ( threadId ) ;
164
+ return realResult ;
151
165
} , 'int' , [ 'pointer' , 'pointer' ] ) ;
152
166
153
167
verificationCallbackCache [ realCallbackAddr ] = hookedCallback ;
0 commit comments