forked from wolfSSL/wolfssljni
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathWolfSSLEngineMemoryLeakTest.java
More file actions
273 lines (227 loc) · 9.62 KB
/
WolfSSLEngineMemoryLeakTest.java
File metadata and controls
273 lines (227 loc) · 9.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
/* WolfSSLEngineMemoryLeakTest.java
*
* Copyright (C) 2006-2026 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
package com.wolfssl.provider.jsse.test;
import org.junit.Test;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.Timeout;
import static org.junit.Assert.*;
import java.util.concurrent.TimeUnit;
import java.nio.ByteBuffer;
import java.security.Security;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import com.wolfssl.provider.jsse.WolfSSLProvider;
/**
* Memory leak regression test for WolfSSLEngine.
*
* Tests that JNI global references are properly cleaned up when SSLEngine
* instances are abandoned (e.g., due to exceptions during handshake).
*
* This test verifies fixes for memory leak caused by:
* - I/O callback references (setIOWriteCtx, setIOReadCtx)
* - Session ticket callback references (setSessionTicketCb)
* - Verify callback references (WolfSSLInternalVerifyCb.callingEngine)
*/
public class WolfSSLEngineMemoryLeakTest {
/**
* Global timeout for all tests in this class. The 500-engine test
* plus multiple GC/finalization rounds can run 60+ seconds on slower
* platforms (notably Windows with FIPS enabled), so 180s leaves
* comfortable headroom while still catching genuine hangs.
*/
@Rule
public Timeout globalTimeout = new Timeout(180, TimeUnit.SECONDS);
@BeforeClass
public static void setupProvider() {
System.out.println("WolfSSLEngineMemoryLeakTest");
Security.addProvider(new WolfSSLProvider());
}
private void pass(String msg) {
WolfSSLTestFactory.pass(msg);
}
private void error(String msg) {
WolfSSLTestFactory.fail(msg);
}
/**
* Test that abandoned SSLEngine instances can be garbage collected.
*
* This test creates many SSLEngine instances that fail during handshake
* (simulating real-world scenarios where connections are dropped), then
* verifies that memory is properly released after garbage collection.
*
* The test will fail if JNI global references prevent garbage collection.
*/
@Test
public void testEngineMemoryLeakWithAbandonedEngines() throws Exception {
/* Skip on Android due to performance and timeout issues */
if (WolfSSLTestFactory.isAndroid()) {
System.out.println("\tmem leak test\t\t\t... skipped (Android)");
return;
}
/* Number of engines to create. Use a smaller number for unit tests
* to keep test time reasonable (few seconds). */
final int numEngines = 500;
/* Threshold for acceptable memory growth (in MB).
* I/O and session ticket callbacks are immediately released. Verify
* callbacks may be retained until finalization.
* JDK 21+ has larger object overhead than earlier JDKs.
* Acceptable: ~20-65 MB for 500 engines depending on JDK version.
* Before fixes, growth would be ~230+ MB for 500 engines.
* We use a conservative threshold that detects major leaks while
* accounting for JVM differences. */
final double maxAcceptableGrowthMB = 80.0;
String javaVersion = System.getProperty("java.version");
System.out.print("\tmem leak test with " + numEngines + " engines");
/* Measure baseline memory - use aggressive GC */
for (int i = 0; i < 3; i++) {
System.gc();
System.runFinalization();
}
Thread.sleep(200);
long baselineMemory = getUsedMemoryBytes();
/* Create and abandon many SSLEngine instances */
for (int i = 0; i < numEngines; i++) {
createAndAbandonSSLEngine();
}
/* Force aggressive garbage collection and finalization.
* Multiple rounds help ensure finalizers run for abandoned engines.
* This may be important for JDK 21+ which seems to have different
* GC timing characteristics. */
for (int i = 0; i < 5; i++) {
System.gc();
System.runFinalization();
}
Thread.sleep(300);
/* Measure final memory */
long finalMemory = getUsedMemoryBytes();
long memoryGrowthBytes = finalMemory - baselineMemory;
double memoryGrowthMB = memoryGrowthBytes / (1024.0 * 1024.0);
/* Verify memory growth is within acceptable limits */
String message = String.format(
"Memory leak detected: created %d engines, " +
"memory grew by %.2f MB (max acceptable: %.2f MB). " +
"JNI global references may not be properly cleaned up.",
numEngines, memoryGrowthMB, maxAcceptableGrowthMB);
if (memoryGrowthMB > maxAcceptableGrowthMB) {
error("\t... failed");
fail(message);
}
pass("\t... passed");
}
/**
* Test that SSLEngine instances that complete handshake successfully
* and are properly closed do not leak memory.
*/
@Test
public void testEngineMemoryLeakWithProperClose() throws Exception {
final int numEngines = 100;
final double maxAcceptableGrowthMB = 20.0;
System.gc();
System.gc();
Thread.sleep(100);
long baselineMemory = getUsedMemoryBytes();
System.out.print("\tmem leak test closed engines");
/* Create engines and explicitly close them */
for (int i = 0; i < numEngines; i++) {
SSLContext sslContext =
SSLContext.getInstance("TLS", "wolfJSSE");
sslContext.init(null, null, null);
SSLEngine engine =
sslContext.createSSLEngine("example.com", 443);
engine.setUseClientMode(true);
/* Explicitly close the engine */
engine.closeOutbound();
/* For inbound, we need to handle the exception if not connected */
try {
engine.closeInbound();
} catch (SSLException e) {
/* Expected - not a real connection */
}
}
System.gc();
System.gc();
Thread.sleep(100);
long finalMemory = getUsedMemoryBytes();
long memoryGrowthBytes = finalMemory - baselineMemory;
double memoryGrowthMB = memoryGrowthBytes / (1024.0 * 1024.0);
String message = String.format(
"Memory leak detected with proper close: created %d engines, " +
"memory grew by %.2f MB (max acceptable: %.2f MB).",
numEngines, memoryGrowthMB, maxAcceptableGrowthMB);
if (memoryGrowthMB > maxAcceptableGrowthMB) {
error("\t... failed");
fail(message);
}
pass("\t... passed");
}
/**
* Creates an SSLEngine, initializes it (which creates JNI global
* references), attempts a wrap operation that will fail, then abandons
* the engine without proper cleanup.
*
* This simulates real-world scenarios where:
* - Connections are dropped mid-handshake
* - Exceptions occur during handshake
* - Applications don't properly close engines
*/
private void createAndAbandonSSLEngine() throws Exception {
/* Create SSLContext using WolfSSL provider */
SSLContext sslContext = SSLContext.getInstance("TLS", "wolfJSSE");
/* Initialize with null (will use default trust/key managers) */
sslContext.init(null, null, null);
/* Create SSLEngine in client mode */
SSLEngine engine = sslContext.createSSLEngine("wolfssl.com", 443);
engine.setUseClientMode(true);
/* Begin handshake - this triggers initialization of callbacks
* and creates JNI global references */
engine.beginHandshake();
/* Allocate buffers for handshake */
ByteBuffer netBuffer = ByteBuffer.allocate(
engine.getSession().getPacketBufferSize());
ByteBuffer appBuffer = ByteBuffer.allocate(
engine.getSession().getApplicationBufferSize());
try {
/* Attempt a wrap operation - this will create initial ClientHello
* and set up all the JNI global references. The operation will
* fail because there's no peer to connect to. */
engine.wrap(appBuffer, netBuffer);
} catch (SSLException e) {
/* Expected - we don't have a real peer to connect to */
}
/* IMPORTANT: We intentionally do NOT call:
* - engine.closeOutbound()
* - engine.closeInbound()
* - Any final wrap()/unwrap() operations
*
* This simulates scenarios where connections are abruptly dropped
* or applications don't properly close engines. Without the fix,
* JNI global references will keep the engine in memory forever. */
}
/**
* Gets the current used memory in bytes.
*/
private long getUsedMemoryBytes() {
Runtime runtime = Runtime.getRuntime();
return runtime.totalMemory() - runtime.freeMemory();
}
}