1 /*
2 * Copyright 2026 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.ssl;
17
18 import java.lang.ref.WeakReference;
19 import java.util.Map;
20 import java.util.concurrent.ConcurrentHashMap;
21
22 /**
23 * Maps a native {@code SSL*} pointer to its {@link ReferenceCountedOpenSslEngine} so native OpenSSL callbacks
24 * (certificate verification, private-key operations, certificate (de)compression) can recover the engine from the
25 * raw pointer they are handed.
26 * <p>
27 * Engines are held weakly so a leaked engine is not pinned by the long-lived parent
28 * {@link ReferenceCountedOpenSslContext} (a live engine is always strongly reachable via its {@link SslHandler}, and
29 * on the stack during a callback, so weak retention never collects a usable one). For {@link SslProvider#OPENSSL}
30 * this lets {@link OpenSslEngine#finalize()} reclaim the native {@code SSL*} without waiting for the whole context to
31 * be collected; a leaked {@link SslProvider#OPENSSL_REFCNT} engine has no finalizer so its native memory still leaks,
32 * but it becomes collectable and so is reported by the {@code ResourceLeakDetector} rather than pinned silently.
33 * <p>
34 * Entries are removed in {@link ReferenceCountedOpenSslEngine#shutdown()}, so the map does not grow in steady state.
35 * A cleared {@link WeakReference} lingers only for a leaked {@code OPENSSL_REFCNT} engine (whose {@code SSL*} is never
36 * freed, hence never reused); such a husk is tiny, dwarfed by the native memory it marks, and is deliberately left as
37 * a heap-inspectable leak signal. Do not reap it (e.g. via a {@code ReferenceQueue}): {@link #get(long)} already
38 * yields {@code null} for a cleared reference, so reaping would only erase that signal.
39 */
40 final class OpenSslEngineMap {
41
42 private final Map<Long, WeakReference<ReferenceCountedOpenSslEngine>> engines =
43 new ConcurrentHashMap<Long, WeakReference<ReferenceCountedOpenSslEngine>>();
44
45 void add(long ssl, ReferenceCountedOpenSslEngine engine) {
46 // A fresh SSL_new() pointer maps to nothing yet: an SSL* is reused only after shutdown() removed its entry
47 // (remove-before-freeSSL), and a husk survives only for a never-freed, never-reused SSL*.
48 WeakReference<ReferenceCountedOpenSslEngine> prev =
49 engines.put(ssl, new WeakReference<ReferenceCountedOpenSslEngine>(engine));
50 assert prev == null : "OpenSslEngineMap already had an entry for SSL* 0x" + Long.toHexString(ssl);
51 }
52
53 void remove(long ssl) {
54 engines.remove(ssl);
55 }
56
57 ReferenceCountedOpenSslEngine get(long ssl) {
58 WeakReference<ReferenceCountedOpenSslEngine> ref = engines.get(ssl);
59 return ref == null ? null : ref.get();
60 }
61 }