Skip to content

Fix resource leaks and session-pool transition races - #665

Merged
mtrojnar merged 13 commits into
OpenSC:masterfrom
mtrojnar:fix/resource-session-handling
Jul 31, 2026
Merged

Fix resource leaks and session-pool transition races#665
mtrojnar merged 13 commits into
OpenSC:masterfrom
mtrojnar:fix/resource-session-handling

Conversation

@mtrojnar

Copy link
Copy Markdown
Member

Pull Request Type

  • Bug fix
  • New feature
  • Code style / formatting / renaming
  • Refactoring (no functional or API changes)
  • Build / CI related changes
  • Documentation
  • Other (please describe):

Related Issue

Issue number: N/A

Current Behavior

Several key and session-pool paths mishandle resources or synchronization:

  • pkcs11_ec_keygen() leaks the allocated DER-encoded EC parameters if the second i2d_ASN1_OBJECT() call fails.
  • pkcs11_get_rsa() leaks attributes in the temporary template used to find a matching public key when a private key does not expose CKA_PUBLIC_EXPONENT.
  • Key generation can unlock slot->lock twice after switching the pool to R/W mode, causing undefined mutex behavior. Its mode change, login, and session acquisition can also interleave with another transition.
  • A pool mode change calls C_CloseAllSessions() even when other threads still hold checked-out sessions, invalidating handles used by concurrent operations.

New Behavior

Error paths release their temporary allocations, and session-pool mode changes are serialized safely. A transition blocks new checkouts, waits for active users to return their sessions, and only then closes the Cryptoki sessions and changes mode.

Key generation keeps its mode change, login, and session acquisition serialized against other transitions. Condition-variable broadcasts wake both transition and consumer waiters on POSIX and Windows.

Internal helpers are named explicitly for pool acquisition, release, and mode changes. The public PKCS11_open_session() API remains unchanged.

Scope of Changes

  • Centralize EC key-generation failure cleanup and free the DER parameter buffer.
  • Clear the RSA public-key search template immediately after its final use.
  • Add a per-slot transition mutex and checkout gate for changes between R/O and R/W modes.
  • Drain checked-out sessions before calling C_CloseAllSessions().
  • Add Windows pthread_cond_broadcast() compatibility.
  • Rename internal session helpers to pkcs11_session_pool_* names without changing the public API or ABI.

Testing

  • Existing tests
  • New tests added
  • Manual testing

Passed:

make -j"$(nproc)"
make -C tests check TESTS='rsa-keygen.softhsm ec-keygen.softhsm'

Both focused SoftHSM tests passed. The modified C translation units also compile cleanly with -Wall -Wextra; the Windows pthread compatibility wrapper was checked with MinGW; and concurrent EC key generation passed a 10-round stress run.

Additional Notes

  • No public API or ABI changes are introduced.
  • A full make check attempt reported 37 passes, 2 skips, and 9 test-environment failures because existing tests/output.* directories were not writable (Permission denied while creating certificate files).

License Declaration

  • I hereby agree to license my contribution under the project's license.

@mtrojnar
mtrojnar requested a review from olszomal July 24, 2026 11:57
@mtrojnar
mtrojnar force-pushed the fix/resource-session-handling branch 3 times, most recently from a56b4aa to 70532f2 Compare July 27, 2026 13:08
mtrojnar added 4 commits July 27, 2026 15:10
The second i2d_ASN1_OBJECT() call can fail after ec_params has been
allocated. That error path returned the pooled session but leaked the
DER buffer.

Route curve lookup, DER encoding, and allocation failures through one
cleanup path that releases the session and frees ec_params.
When a private RSA object omits CKA_PUBLIC_EXPONENT, pkcs11_get_rsa()
builds a temporary template to find the matching public key. Attributes
allocated for that template were never freed.

Clear the template immediately after the object lookup, its final use,
so every lookup outcome releases the allocated attributes.
Key generation dropped slot->lock before switching to R/W mode and then
unlocked it again unconditionally. Mode changes could also call
C_CloseAllSessions() while another thread still used a checked-out handle.

Serialize mode changes with transition_active under the slot lock. Track
checked-out sessions explicitly, block normal acquisition for the entire
drain, mode switch, relogin, and key-generation checkout, and close
sessions only after every lease is returned. Use condition-variable
broadcasts so transition and consumer waiters all recheck their predicates.

Acquire and relogin the key-generation session without re-entering the
normal gated path. Restore pool accounting on every error, and reject
invalid releases defensively.

Reset transition and pool accounting during slot reload so a child cannot
inherit state owned by a vanished parent thread. Keeping all transition
state under the existing slot lock avoids a separate mutex that could be
inherited locked.
The get, put, and open helper names obscured that these functions manage
libp11's per-slot pool rather than direct Cryptoki session ownership.

Rename the helpers and every internal caller to describe pool acquisition,
release, and mode changes explicitly. Keep PKCS11_open_session() unchanged
to preserve the public API and ABI.
@mtrojnar
mtrojnar force-pushed the fix/resource-session-handling branch from 70532f2 to 47a80f7 Compare July 27, 2026 13:11
mtrojnar added 8 commits July 28, 2026 17:52
Add deterministic tests around a fake PKCS#11 module for transition
waiter wakeups, acquisition gating during key-generation relogin,
recovery after a failed relogin, and stale transition state after fork
reload.

Add a bounded SoftHSM stress test that runs signing, key generation, and
pool mode changes concurrently. A watchdog turns deadlocks into explicit
test failures.

Run the focused unit helper through a shell wrapper that selects the
configured OpenSSL. Select the same runtime paths for the stress helper
after SoftHSM setup, so custom builds load the matching libcrypto and
the locally built libp11 without affecting pkcs11-tool.

Call OPENSSL_cleanup() at the end of the stress helper to make leak
checks deterministic. Guard the terminal cleanup for OpenSSL 1.0.x and
LibreSSL versions that do not provide it.

Wire both tests into Automake, include their scripts in distributions,
and skip the threaded helpers when pthread support is unavailable.
Store a referenced PKCS11_OBJECT_private directly in EVP_PKEY ex-data
instead of following a PKCS11_KEY back-pointer. The back-pointer becomes
stale when key enumeration reallocates the cached key array, causing
concurrent signing and key generation to crash.

Remove the now-obsolete back-pointer from the private object structure.
The EVP_PKEY cached on a private EdDSA or XDH object only borrows
its legacy ex-data pointer. Returned EVP_PKEY objects acquire their
owning object reference through the callback-backed generic ex-data
installed by pkcs11_get_key().

Taking another reference while constructing each cached key therefore
had no matching release. It kept the object, slot, and associated PIN
data alive after ENGINE teardown. Remove those redundant references
and document the ownership split.
The Ed448 key path registered pkcs11_ed25519_method_free() with
atexit(), leaving the Ed448 EVP_PKEY method allocated and invoking
cleanup for the wrong method family.

Pair pkcs11_ed448_method_new() with pkcs11_ed448_method_free().
PKCS#11-backed EVP_PKEY objects retain engine-managed key and slot
state. Several ENGINE tests called ENGINE_finish() before freeing
those keys, so teardown could no longer release the object graph.
Leak checks consequently reported the objects, slots, and PIN data.

Free every EVP_PKEY before dropping the functional ENGINE reference.
Centralize cleanup in the check/copy tests and track whether
ENGINE_init() succeeded, so error paths release either the functional
or structural reference as appropriate.
OSSL_PARAM_BLD_push_utf8_string() retains the supplied pointer until
OSSL_PARAM_BLD_to_param() materializes the parameter array. The EC
group-name buffer was scoped to the switch case, so its lifetime ended
before the common conversion after the switch. ASan consequently
reported a stack-use-after-scope.

Move the buffer to function scope so it remains valid through
parameter construction.
ERR_load_PKCS11_strings() registers both the P11 and CKR error tables
when a context is created. Remove both registrations when the final
context releases libp11's process-global state so OpenSSL can reclaim
the associated hash entries.

CKR reason strings are still registered on OpenSSL 3; only legacy
function codes are ignored there.
Private RSA objects store a PKCS11_OBJECT_private pointer in RSA ex-data,
and pkcs11_rsa_free_method() releases that reference. The matching
pkcs11_object_ref() was incorrectly inside the OpenSSL 3 version guard.
On OpenSSL 1.x, freeing the cached EVP_PKEY therefore released the
cache's sole reference, and pkcs11_destroy_keys() then accessed the
already freed object.

Acquire the ex-data-owned reference on every supported OpenSSL version,
matching the ownership used for private EC keys.
@mtrojnar
mtrojnar force-pushed the fix/resource-session-handling branch from 7eefaf5 to 86e639c Compare July 28, 2026 15:53
@mtrojnar
mtrojnar merged commit 3408349 into OpenSC:master Jul 31, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant