View Javadoc
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  }