Skip to content

[crypto] Hold OpenSSL objects in an owning handle in the OpenSSL P256 PAL + double-free fix - #73455

Open
Alami-Amine wants to merge 1 commit into
project-chip:masterfrom
Alami-Amine:crypto-openssl-pal-scoped-handles
Open

[crypto] Hold OpenSSL objects in an owning handle in the OpenSSL P256 PAL + double-free fix#73455
Alami-Amine wants to merge 1 commit into
project-chip:masterfrom
Alami-Amine:crypto-openssl-pal-scoped-handles

Conversation

@Alami-Amine

@Alami-Amine Alami-Amine commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • This change was motivated by a double-free in the helper's error path; the RAII conversion is how it is fixed

  • Replaces the goto exit cleanup blocks in _create_evp_key_from_binary_p256_key and P256Keypair::ECDH_derive_secret with an owning handle.

Problem
  • Both functions released several raw OpenSSL pointers in a trailing exit: block, putting correctness in code that ordinary tests never execute.
  • The helper's block had two defects. It assigned out_evp_pkey = nullptr — the local parameter rather than *out_evp_pkey — so if EVP_PKEY_set1_EC_KEY() failed after EVP_PKEY_new() succeeded, the caller kept a pointer the helper had already released and released it again: a double free. It also used *out_evp_pkey != nullptr as a proxy for "the helper allocated this", which is the condition its own precondition rejects, so a caller passing a non-empty out-parameter received CHIP_ERROR_INVALID_ARGUMENT and had its object released.
  • Neither is reachable from the public key bytes: a malformed or off-curve point fails at EC_POINT_oct2point, before the EVP_PKEY exists.
Solution
  • Added ScopedOpenSSLObject<T, Free> to CHIPCryptoPALOpenSSL.h: std::unique_ptr with a stateless deleter and a private base, aliased per (type, free function) pair. Free is a template parameter rather than derived from T because types such as EC_POINT have both a plain and a zeroizing free, and the correct one depends on whether the object held secret material.
  • The helper takes the handle by reference, so its out-parameter is written once on success and the null precondition is no longer needed.
  • ECDH_derive_secret holds all four of its OpenSSL objects in handles. Its exit: label remains only because SSLErrorLog() runs on every path.
Caveats
  • The helper no longer returns CHIP_ERROR_INVALID_ARGUMENT; its only caller did not distinguish it.
  • ScopedOpenSSLObject can only name a void (T *) cleanup, so sk_X509_free and sk_<TYPE>_pop_free elsewhere in CHIPCryptoPALOpenSSL.cpp are unchanged.

Testing

  • Added TestECDH_RemotePublicKeyNotOnCurve in src/crypto/tests/TestChipCryptoPAL.cpp: a correctly sized SEC1 uncompressed point that is not on P-256 must return an error with out_secret length still zero. This is the suite's first negative ECDH case.

… PAL

_create_evp_key_from_binary_p256_key and P256Keypair::ECDH_derive_secret
released their OpenSSL objects through trailing goto-exit blocks. Add
ScopedOpenSSLObject to CHIPCryptoPALOpenSSL.h and use it in both, which
removes the cleanup blocks and makes the helper assign its out-parameter
once, on success.

The helper's out-parameter becomes a reference to the handle. Assigning
to a handle releases whatever it held, so the null-out-parameter
precondition and its CHIP_ERROR_INVALID_ARGUMENT path are no longer
needed. ECDH_derive_secret keeps its exit label because SSLErrorLog()
runs on every path, but the block is now only that call and the return.

Add TestECDH_RemotePublicKeyNotOnCurve, the suite's first negative ECDH
case, covering the remote-key decode failure path.
@Alami-Amine Alami-Amine changed the title [crypto] Hold OpenSSL objects in an owning handle in the OpenSSL P256 PAL [crypto] Hold OpenSSL objects in an owning handle in the OpenSSL P256 PAL + double-free fix Aug 6, 2026
@Alami-Amine
Alami-Amine requested a lite review from Copilot August 6, 2026 18:13

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR strengthens OpenSSL-backed P-256 ECDH handling by adopting RAII wrappers for OpenSSL heap objects and adding a regression test that exercises the failure path for an invalid remote public key (not on the curve), ensuring no secret is produced and allocations are cleaned up.

Changes:

  • Added a new unit test covering ECDH with a remote public key that decodes to a point not on the P-256 curve.
  • Refactored OpenSSL object lifetime management in ECDH/key decoding to use scoped RAII wrappers.
  • Introduced generic scoped OpenSSL handle types in the OpenSSL crypto PAL header.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/crypto/tests/TestChipCryptoPAL.cpp Adds a negative ECDH test to validate cleanup/no-secret on invalid remote public key input.
src/crypto/P256KeyPairOpenSSL.cpp Reworks ECDH and key decoding to use scoped OpenSSL handles and reduce manual free paths.
src/crypto/CHIPCryptoPALOpenSSL.h Introduces reusable scoped RAII handle templates/aliases for OpenSSL types.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +86 to +88
VerifyOrReturnError(EC_POINT_oct2point(group.get(), point.get(), Uint8::to_const_uchar(key), key.Length(), nullptr) == 1,
CHIP_ERROR_INTERNAL);
VerifyOrReturnError(EC_KEY_set_public_key(ec_key.get(), point.get()) == 1, CHIP_ERROR_INTERNAL);
Comment on lines +80 to +84
ScopedEcGroup group(EC_GROUP_new_by_curve_name(nid));
VerifyOrReturnError(group, CHIP_ERROR_INTERNAL);

*out_evp_pkey = EVP_PKEY_new();
VerifyOrExit(*out_evp_pkey != nullptr, error = CHIP_ERROR_INTERNAL);
ScopedEcPoint point(EC_POINT_new(group.get()));
VerifyOrReturnError(point, CHIP_ERROR_INTERNAL);
@github-actions github-actions Bot added the crypto label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

PR #73455: Size comparison from cad0b20 to ee524fc

Full report (23 builds for bl602, bl702, bl702l, cc13x4_26x4, cc32xx, efr32, esp32, nrfconnect, psoc6, qpg, realtek, stm32)
platform target config section cad0b20 ee524fc change % change
bl602 lighting-app bl602+mfd+littlefs+rpc FLASH 1099276 1099276 0 0.0
RAM 133546 133546 0 0.0
bl702 lighting-app bl702+eth FLASH 1086002 1086002 0 0.0
RAM 109157 109157 0 0.0
bl702l contact-sensor-app bl702l+mfd+littlefs FLASH 884340 884340 0 0.0
RAM 108628 108628 0 0.0
cc13x4_26x4 lighting-app LP_EM_CC1354P10_6 FLASH 778524 778524 0 0.0
RAM 103524 103524 0 0.0
lock-ftd LP_EM_CC1354P10_6 FLASH 790664 790664 0 0.0
RAM 108708 108708 0 0.0
pump-app LP_EM_CC1354P10_6 FLASH 739628 739628 0 0.0
RAM 97636 97636 0 0.0
pump-controller-app LP_EM_CC1354P10_6 FLASH 719800 719800 0 0.0
RAM 97668 97668 0 0.0
cc32xx air-purifier CC3235SF_LAUNCHXL FLASH 569910 569910 0 0.0
RAM 205136 205136 0 0.0
lock CC3235SF_LAUNCHXL FLASH 597278 597278 0 0.0
RAM 205296 205296 0 0.0
efr32 lighting-app BRD4187C FLASH 1121852 1121852 0 0.0
RAM 135548 135548 0 0.0
lock-app BRD4187C FLASH 1012588 1012588 0 0.0
RAM 131640 131640 0 0.0
BRD4338a FLASH 812709 812709 0 0.0
RAM 235804 235804 0 0.0
esp32 all-clusters-app c3devkit DRAM 99708 99708 0 0.0
FLASH 1629570 1629570 0 0.0
IRAM 94776 94776 0 0.0
nrfconnect all-clusters-app nrf52840dk_nrf52840 FLASH 847240 847240 0 0.0
RAM 158065 158065 0 0.0
psoc6 all-clusters cy8ckit_062s2_43012 FLASH 1759292 1759292 0 0.0
RAM 215796 215796 0 0.0
all-clusters-minimal cy8ckit_062s2_43012 FLASH 1631380 1631380 0 0.0
RAM 211708 211708 0 0.0
light cy8ckit_062s2_43012 FLASH 1471804 1471804 0 0.0
RAM 197556 197556 0 0.0
lock cy8ckit_062s2_43012 FLASH 1504604 1504604 0 0.0
RAM 225292 225292 0 0.0
qpg lighting-app qpg6200+debug FLASH 843672 843672 0 0.0
RAM 127988 127988 0 0.0
lock-app qpg6200+debug FLASH 783208 783208 0 0.0
RAM 118928 118928 0 0.0
realtek light-switch-app rtl8777g FLASH 690136 690136 0 0.0
RAM 101896 101896 0 0.0
lighting-app rtl8777g FLASH 731064 731064 0 0.0
RAM 102176 102176 0 0.0
stm32 light STM32WB5MM-DK FLASH 484076 484076 0 0.0
RAM 128184 128184 0 0.0

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.16%. Comparing base (879a9c6) to head (ee524fc).
⚠️ Report is 38 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #73455      +/-   ##
==========================================
+ Coverage   56.11%   56.16%   +0.04%     
==========================================
  Files        1817     1818       +1     
  Lines      117720   117858     +138     
  Branches    13885    13898      +13     
==========================================
+ Hits        66064    66194     +130     
- Misses      51656    51664       +8     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants