Skip to content

Retain shared certificate and key buffers across CTX reloads - #12062

Open
ageprocpp wants to merge 2 commits into
wolfSSL:masterfrom
ageprocpp:fix/fenrir-16791
Open

ageprocpp wants to merge 2 commits into
wolfSSL:masterfrom
ageprocpp:fix/fenrir-16791

Conversation

@ageprocpp

Copy link
Copy Markdown
Contributor

Description

Fixes Fenrir #16791

When certificate or key copying is disabled, a WOLFSSL object can share DER buffers with its WOLFSSL_CTX. Replacing a certificate chain or private key on the context after wolfSSL_new() can release a buffer that the existing connection still needs and cause use-after-free.

This change adds reference counting to DerBuffer so that a context and its connections can retain the same certificate, chain, or private key buffer and make the buffer freed only when its last reference is released.

Background

This revisits the reference-counting approach proposed in #11109, which superseded #10905 and was closed without being merged. The approach in #11191 instead blocks certain context changes during callbacks regarding handshake.

The case addressed here does not require a context change inside a callback: the context can be always reloaded after wolfSSL_new() and before the connection uses its certificate or key and that is the condition of the case.

Copilot AI balanced review requested due to automatic review settings November 9, 2026 06:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

gcc-arm-cortex-m0plus

  • FLASH: .text +64 B (+0.1%, 70,867 B / 262,144 B, total: 27% used)

gcc-arm-cortex-m3

  • FLASH: .text +392 B (+0.3%, 132,153 B / 262,144 B, total: 50% used)

gcc-arm-cortex-m4

  • FLASH: .text +384 B (+0.2%, 209,420 B / 262,144 B, total: 80% used)

gcc-arm-cortex-m4-baremetal

  • FLASH: .text +64 B (+0.1%, 73,591 B / 262,144 B, total: 28% used)

gcc-arm-cortex-m4-crypto-only

  • FLASH: .text +64 B (+0.0%, 181,779 B / 262,144 B, total: 69% used)

gcc-arm-cortex-m4-dtls13

  • FLASH: .text +384 B (+0.2%, 196,376 B / 1,048,576 B, total: 19% used)

gcc-arm-cortex-m4-min-ecc

  • FLASH: .text +64 B (+0.1%, 68,185 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .text +512 B (+0.1%, 801,700 B / 1,048,576 B, total: 76% used)

gcc-arm-cortex-m4-pq

  • FLASH: .text +384 B (+0.1%, 313,232 B / 1,048,576 B, total: 30% used)

gcc-arm-cortex-m4-rsa-only

  • FLASH: .text +384 B (+0.1%, 344,080 B / 1,048,576 B, total: 33% used)

gcc-arm-cortex-m4-sp-math

  • FLASH: .text +64 B (+0.1%, 68,185 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m4-tls12

  • FLASH: .text +384 B (+0.3%, 132,945 B / 262,144 B, total: 51% used)

gcc-arm-cortex-m4-tls13

  • FLASH: .text +384 B (+0.2%, 250,018 B / 262,144 B, total: 95% used)

gcc-arm-cortex-m7

  • FLASH: .text +448 B (+0.2%, 209,484 B / 262,144 B, total: 80% used)

gcc-arm-cortex-m7-pq

  • FLASH: .text +384 B (+0.1%, 314,064 B / 1,048,576 B, total: 30% used)

gcc-arm-cortex-m7-tls13

  • FLASH: .text +384 B (+0.2%, 250,018 B / 262,144 B, total: 95% used)

zephyr-frdm_rw612

  • FLASH: log_const_area -8 B, rodata -8 B, text +1,664 B (+0.6%, 284,112 B / 67,108,864 B, total: 0% used)

No memory changes detected for:


Catch memory regressions in CI with MemBrowse. Free for open source.

@ageprocpp
ageprocpp force-pushed the fix/fenrir-16791 branch 3 times, most recently from e069aa8 to 142c6f6 Compare November 9, 2026 08:02

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #12062

Scan targets checked: wolfcrypt-src, wolfcrypt-bugs, wolfcrypt-port-bugs, wolfssl-src, wolfssl-bugs
Not reviewed (no scan target covers them): doc/dox_comments/header_files/asn_public.h
Coverage: 10 of 12 in-scope changed file(s) opened by the reviewer; not opened: wolfssl/internal.h, wolfssl/wolfcrypt/asn.h

Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Reported findings require changes before merge.

Review tier: Max

Comment thread src/tls13.c
ERROR_OUT(BAD_MUTEX_E, exit_scv);
}
FreeDer(&ssl->buffers.keyMask);
ssl->buffers.keyMask = ssl->buffers.altKeyMask;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BLIND alt-key swap leaves altKeyMask stale after the exit re-blind · Logic errors

After the swap, key/altKey share one buffer. On success, wolfssl_priv_der_blind() at exit_scv re-blinds that buffer under a new keyMask, but altKeyMask keeps the old mask. A later ALTERNATIVE CertificateVerify (e.g. TLS 1.3 post-handshake auth) then unblinds with the wrong mask and signs with a corrupted key.

Suggested fix: In WOLFSSL_BLIND_PRIVATE_KEY builds, give ssl->buffers.key its own unblinded copy of the alt key instead of aliasing altKey and altKeyMask.
Basis: wolfssl_priv_der_blind() (src/internal.c:333) frees and regenerates only the mask pointer passed to it and XORs the new mask into the key buffer, so any other pointer to the previous mask is left stale.

Related known findings (similar but distinct; listed for context, not part of this finding)

  • F-13391: that one is about the CTX key ID/label setters keeping a stale mask; this one is the TLS 1.3 alt-key aliasing in SendTls13CertificateVerify, where the mask goes out of sync after the exit re-blind.

Comment thread tests/api/test_ocsp.c
* expected. Inspect the request sent to the responder instead. */
(void)test_memio_do_handshake(ssl_c, ssl_s, 10, NULL);
ExpectIntEQ(test_ocsp_ctx_request_cache_cb_cnt, 4);
ExpectIntEQ(test_ocsp_ctx_request_cache_differs, 1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reload OCSP test cannot detect reuse of the stale cached request · Weak or missing assertions

differs is only compared with the first server1 request. The cached request's serial was already XOR-corrupted at i == 1, so reusing that stale cached request also gives differs == 1. The new reload block passes whether or not the CTX cache is bypassed after the certificate reload.

Suggested fix: Undo the serial corruption before the reload step, or capture the post-reload request and assert it carries server2's serial.

@ageprocpp ageprocpp assigned ageprocpp and unassigned wolfSSL-Bot Nov 9, 2026
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.

4 participants