Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix hash_test() memory leak in wolfcrypt/test/test.c #8506

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

gojimmypi
Copy link
Contributor

@gojimmypi gojimmypi commented Feb 26, 2025

Description

This PR fixes a memory leak in the hash_test function of wolfcrypt/test/test.c and adds some Espressif-specific heap checking features.

Background

The memory leak was first brought to my attention by the Devin automated #8471.

I've reviewed the Espressif code. I was unable to find a memory leak.

I also had ChatGPT review the code. There were some false positives, but after I explained the multi-threaded nature of the library, ChatGPT was also unable to find any memory leak candidates.

From what I can see, there is no memory leak detected in any of the core wolfcrypt hash functions, only the test code around current line 6082 (no line as the file is too big to view online):

WOLFSSL_TEST_SUBROUTINE wc_test_ret_t hash_test(void)

The problem is manifested when #define WOLFSSL_SMALL_STACK is found in the user_settings.h.

Generally the test has been used as a "run once and exit" app, so it is unlikely anyone noticed there was a memory leak.

In my case, I typically run the tests in a loop for hours and days on embedded devices. See the #define TEST_LOOP setting.

After about 1,500+ loops, the ESP32 devices would run out of memory. I incorrectly assumed the problem was Espressif-specific.

I've run the benchmark in loops on 9 different devices for days now. Between ~5,000 and ~8,000 loops have completed successfully:

image

I've started a similar loop of the wolfcrypt test again with changes in this PR.

Changes

  • There's a new DEBUG_WOLFSSL_ESP32_HEAP now documented in esp32-crypt.h.

  • The PRINT_HEAP_CHECKPOINT(b, i) has been modified to accept two parameters: a breadcrumb string (b) and an integer index (i).

  • The existing implementation of PRINT_HEAP_CHECKPOINT was not otherwise changed. A new PRINT_HEAP_CHECKPOINT was added for the Espressif ESP-IDF environment.

  • There's a new PRINT_HEAP_ADDRESS that prints the address of a newly allocated memory segment. In this PR, the address of the hash object.

  • There's a new "placeholder" hash type created for known-but-not-implemented hash types. Previously the hash free would quietly fail due to type mismatch, contributing to the memory leak.

  • The default placeholder type is introduced in a new macro: PLACEHOLDER_TYPE. An arbitirary value of 0 is used; the first valid typesGood[] item is used when checking bad parameters.

  • More return codes are checked (e.g. on wc_HashFree) that were not previously checked.

  • The logic for "is this a valid hash?" when checking the typesNoImpl list has been revised. It is no longer sensitive to the order of items. It was also probably not fully working as intended anyhow.

  • New hash objects are created and destroyed as appropriate in the "Good type and implemented" tests.

Still outstanding

There's still another test.c memory leak in or around the PKCS12 test. See message from full log, below:

PKCS12   test passed!
I (13430) wc_test: Breadcrumb: TEST_PASS
W (13431) wc_test: Warning: this heap 308088 != last 308128

I will look at this one at a later date unless otherwise advised.

It would be nice to have the hash checks for other platforms to give similar warnings. For now the feature is Espressif-specific.

Full ESP32 Output

For reference:

I (453) wolfssl_test: --------------------------------------------------------
I (457) wolfssl_test: --------------------------------------------------------
I (465) wolfssl_test: ---------------------- BEGIN MAIN ----------------------
I (473) wolfssl_test: --------------------------------------------------------
I (481) wolfssl_test: --------------------------------------------------------
I (489) wolfssl_test: Stack Start: 0x3ffb52e0
W (494) wolfssl_test: Found WOLFSSL_ESP_NO_WATCHDOG, disabling...
I (501) wolfssl_test: CONFIG_ESP_MAIN_TASK_STACK_SIZE = 10500 bytes (2625 words)
I (509) wolfssl_test: Stack Start HWM: 9240 bytes
I (514) esp32_util: Extended Version and Platform Information.
I (521) esp32_util: Chip revision: v1.0
I (525) esp32_util: SSID and plain text WiFi password not displayed in startup logs.
I (534) esp32_util:   Define SHOW_SSID_AND_PASSWORD to enable display.
I (541) esp32_util: Using wolfSSL user_settings.h in C://workspace//wolfssl-gojimmypi-pr//IDE//Espressif//ESP-IDF//examples//wolfssl_test//components//wolfssl/include/user_settings.h
I (558) esp32_util: LIBWOLFSSL_VERSION_STRING = 5.7.6
I (563) esp32_util: LIBWOLFSSL_VERSION_HEX = 5007006
I (569) esp32_util: Stack HWM: 9160
I (573) esp32_util:
I (576) esp32_util: Macro Name                 Defined   Not Defined
I (583) esp32_util: ------------------------- --------- -------------
I (590) esp32_util: NO_ESP32_CRYPT...........                 X
I (597) esp32_util: NO_ESPIDF_DEFAULT........                 X
I (603) esp32_util: HW_MATH_ENABLED..........     X
I (609) esp32_util: WOLFSSL_SHA224...........     X
I (615) esp32_util: WOLFSSL_SHA384...........     X
I (620) esp32_util: WOLFSSL_SHA512...........     X
I (626) esp32_util: WOLFSSL_SHA3.............                 X
I (632) esp32_util: HAVE_ED25519.............     X
I (638) esp32_util: HAVE_AES_ECB.............                 X
I (644) esp32_util: HAVE_AES_DIRECT..........                 X
I (651) esp32_util: USE_FAST_MATH............     X
I (657) esp32_util: WOLFSSL_SP_MATH_ALL......                 X
I (663) esp32_util: SP_MATH..................                 X
I (670) esp32_util: WOLFSSL_HW_METRICS.......     X
I (675) esp32_util: RSA_LOW_MEM..............     X
I (681) esp32_util: SMALL_SESSION_CACHE......                 X
I (687) esp32_util: WC_NO_HARDEN.............                 X
I (694) esp32_util: TFM_TIMING_RESISTANT.....     X
I (700) esp32_util: ECC_TIMING_RESISTANT.....     X
I (705) esp32_util: WC_NO_CACHE_RESISTANT....     X
I (711) esp32_util: WC_AES_BITSLICED.........                 X
I (717) esp32_util: WOLFSSL_AES_NO_UNROLL....                 X
I (724) esp32_util: TFM_TIMING_RESISTANT.....     X
I (729) esp32_util: ECC_TIMING_RESISTANT.....     X
I (735) esp32_util: WC_RSA_BLINDING..........     X
I (741) esp32_util: NO_WRITEV................     X
I (746) esp32_util: FREERTOS.................     X
I (752) esp32_util: NO_WOLFSSL_DIR...........     X
I (757) esp32_util: WOLFSSL_NO_CURRDIR.......     X
I (763) esp32_util: WOLFSSL_LWIP.............     X
I (768) esp32_util:
I (771) esp32_util: Compiler Optimization: Default
I (777) esp32_util:
I (780) esp32_util: CONFIG_IDF_TARGET = esp32
I (785) esp32_util: Found WOLFSSL_ESP_NO_WATCHDOG
I (790) esp32_util: CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ: 240 MHz
I (796) esp32_util: Xthal_have_ccount: 1
I (801) esp32_util: CONFIG_MAIN_TASK_STACK_SIZE: 10500
I (807) esp32_util: CONFIG_ESP_MAIN_TASK_STACK_SIZE: 10500
I (813) esp32_util: CONFIG_TIMER_TASK_STACK_SIZE: 3584
I (819) esp32_util: CONFIG_TIMER_TASK_STACK_DEPTH: 2048
I (825) esp32_util: Stack HWM: 3ffb526f
I (829) esp32_util: CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ: 240 MHz
I (836) esp32_util: Xthal_have_ccount: 1
I (840) esp32_util: ESP32_CRYPT is enabled for ESP32.
I (846) esp32_util: SINGLE_THREADED
I (850) esp32_util: Boot count: 1
I (854) wolfssl_test: Stack HWM: 8936

I (859) wolfssl_test: Stack HWM: 8936

------------------------------------------------------------------------------
 wolfSSL version 5.7.6
------------------------------------------------------------------------------
error    test passed!
MEMORY   test passed!
base64   test passed!
asn      test passed!
RANDOM   test passed!
MD5      test passed!
MD4      test passed!
SHA      test passed!
SHA-224  test passed!
SHA-256  test passed!
SHA-384  test passed!
SHA-512  test passed!
SHA-512/224  test passed!
SHA-512/256  test passed!
Hash     test passed!
HMAC-MD5 test passed!
HMAC-SHA test passed!
HMAC-SHA224 test passed!
HMAC-SHA256 test passed!
HMAC-SHA384 test passed!
HMAC-SHA512 test passed!
HMAC-KDF    test passed!
PRF         test passed!
TLSv1.3 KDF test passed!
GMAC     test passed!
DES      test passed!
DES3     test passed!
AES      test passed!
AES192   test passed!
AES256   test passed!
AES-CBC  test passed!
AES-CTR  test passed!
AES-GCM  test passed!
RSA      test passed!
PWDBASED test passed!
PKCS12   test passed!
I (13430) wc_test: Breadcrumb: TEST_PASS
W (13431) wc_test: Warning: this heap 308088 != last 308128
ECC      test passed!
ECC buffer test passed!
CURVE25519 test passed!
ED25519  test passed!
mp       test passed!
logging  test passed!
time     test passed!
mutex    test passed!
I (84369) wolfssl_esp32_mp:
I (84370) wolfssl_esp32_mp: esp_mp_mul HW acceleration enabled.
I (84376) wolfssl_esp32_mp: Number of calls to esp_mp_mul: 1039421
I (84383) wolfssl_esp32_mp: Number of calls to esp_mp_mul with tiny operands: 1072
I (84391) wolfssl_esp32_mp: Number of calls to esp_mp_mul HW operand exceeded: 0
I (84399) wolfssl_esp32_mp: Success: no esp_mp_mul() errors.
I (84406) wolfssl_esp32_mp:
I (84409) wolfssl_esp32_mp: esp_mp_mulmod HW acceleration enabled.
I (84416) wolfssl_esp32_mp: Number of calls to esp_mp_mulmod: 1587
I (84423) wolfssl_esp32_mp: Number of calls to esp_mp_mulmod HW operand exceeded: 0
I (84431) wolfssl_esp32_mp: Number of fallback to SW mp_mulmod: 205
I (84438) wolfssl_esp32_mp: Success: no esp_mp_mulmod errors.
I (84445) wolfssl_esp32_mp: Success: no esp_mp_mulmod even mod.
I (84451) wolfssl_esp32_mp: Success: no esp_mp_mulmod small x or y.
I (84458) wolfssl_esp32_mp:
I (84462) wolfssl_esp32_mp: Number of calls to esp_mp_exptmod: 135
I (84468) wolfssl_esp32_mp: Number of calls to esp_mp_exptmod HW operand exceeded: 0
I (84477) wolfssl_esp32_mp: Number of fallback to SW mp_exptmod: 8
I (84484) wolfssl_esp32_mp: Success: no esp_mp_exptmod errors.
I (84490) wolfssl_esp32_mp: Max N->used: esp_mp_max_used = 128
I (84497) wolfssl_esp32_mp: Max hw wait timeout: esp_mp_max_wait_timeout = 1
I (84504) wolfssl_esp32_mp: Max calc timeout: esp_mp_max_timeout = 0x0012a665
Test complete
I (84514) wc_test: Exiting main with return code:  0

I (84519) wolf_hw_sha: --------------------------------------------------------
I (84527) wolf_hw_sha: -------------  wolfSSL ESP HW SHA Metrics  -------------
I (84535) wolf_hw_sha: --------------------------------------------------------
I (84543) wolf_hw_sha: esp_sha_hw_copy_ct            = 4
I (84549) wolf_hw_sha: esp_sha1_hw_usage_ct          = 0
I (84555) wolf_hw_sha: esp_sha1_sw_fallback_usage_ct = 0
I (84561) wolf_hw_sha: esp_sha_reverse_words_ct      = 0
I (84567) wolf_hw_sha: esp_sha1_hw_hash_usage_ct     = 2863
I (84574) wolf_hw_sha: esp_sha2_224_hw_hash_usage_ct = 0
I (84580) wolf_hw_sha: esp_sha2_256_hw_hash_usage_ct = 58591
I (84586) wolf_hw_sha: esp_byte_reversal_checks_ct   = 0
I (84592) wolf_hw_sha: esp_byte_reversal_needed_ct   = 0
I (84598) wolfssl_esp32_mp:
I (84601) wolfssl_esp32_mp: esp_mp_mul HW acceleration enabled.
I (84608) wolfssl_esp32_mp: Number of calls to esp_mp_mul: 1039421
I (84615) wolfssl_esp32_mp: Number of calls to esp_mp_mul with tiny operands: 1072
I (84623) wolfssl_esp32_mp: Number of calls to esp_mp_mul HW operand exceeded: 0
I (84631) wolfssl_esp32_mp: Success: no esp_mp_mul() errors.
I (84638) wolfssl_esp32_mp:
I (84641) wolfssl_esp32_mp: esp_mp_mulmod HW acceleration enabled.
I (84648) wolfssl_esp32_mp: Number of calls to esp_mp_mulmod: 1587
I (84655) wolfssl_esp32_mp: Number of calls to esp_mp_mulmod HW operand exceeded: 0
I (84663) wolfssl_esp32_mp: Number of fallback to SW mp_mulmod: 205
I (84670) wolfssl_esp32_mp: Success: no esp_mp_mulmod errors.
I (84677) wolfssl_esp32_mp: Success: no esp_mp_mulmod even mod.
I (84683) wolfssl_esp32_mp: Success: no esp_mp_mulmod small x or y.
I (84690) wolfssl_esp32_mp:
I (84694) wolfssl_esp32_mp: Number of calls to esp_mp_exptmod: 135
I (84700) wolfssl_esp32_mp: Number of calls to esp_mp_exptmod HW operand exceeded: 0
I (84709) wolfssl_esp32_mp: Number of fallback to SW mp_exptmod: 8
I (84716) wolfssl_esp32_mp: Success: no esp_mp_exptmod errors.
I (84722) wolfssl_esp32_mp: Max N->used: esp_mp_max_used = 128
I (84729) wolfssl_esp32_mp: Max hw wait timeout: esp_mp_max_wait_timeout = 1
I (84737) wolfssl_esp32_mp: Max calc timeout: esp_mp_max_timeout = 0x0012a665
I (84744) wolf_hw_aes: --------------------------------------------------------
I (84752) wolf_hw_aes: -------------  wolfSSL ESP HW AES Metrics  -------------
I (84760) wolf_hw_aes: --------------------------------------------------------
I (84768) wolf_hw_aes: esp_aes_unsupported_length_usage_ct = 2
I (84775) wolfssl_test: Stack HWM: 7688

I (84779) wolfssl_test: loops = 1
I (84783) wolfssl_test: Stack HWM: 7688
I (84788) wolfssl_test: Stack used: 2812
I (84793) wolfssl_test:

Device: esp32

Exit code: 0

Success!

WOLFSSL_COMPLETE

If running from idf.py monitor, press twice: Ctrl+]

Fixes zd# n/a

Testing

How did you test?

ESP32 with and without HW encryption, with and without WOLFSSL_SMALL_STACK on ESP-IDF v5.2.

./autogen.sh
./configure --enable-smallstack --enable-all
make clean
make -j$(nproc)
./wolfcrypt/test/testwolfcrypt

And

./autogen.sh
./configure --enable-all
make clean
make -j$(nproc)
./wolfcrypt/test/testwolfcrypt

Update:

Tests have been running on my 9-device jig since yesterday. Screen snip of memory shows no memory leak for the default tests running on the 8x ESP32 + ESP8266

image

Checklist

  • added tests
  • updated/added doxygen
  • updated appropriate READMEs
  • Updated manual and documentation

@gojimmypi
Copy link
Contributor Author

Jenkins retest this please

@gojimmypi
Copy link
Contributor Author

Jenkins retest this please.

to retry external tests

wolfSSL_connect error -308, error state on socket
wolfSSL error: wolfSSL_connect failed

@dgarske
Copy link
Contributor

dgarske commented Feb 26, 2025

Retest this please: "ERROR: Cannot delete workspace :Unable to delete"

@dgarske dgarske assigned wolfSSL-Bot and unassigned dgarske Feb 26, 2025
@dgarske dgarske requested review from SparkiDev and removed request for dgarske February 26, 2025 23:15
#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC)
this_type = i;
#endif
for(j = 0; j < (int)(sizeof(typesNoImpl) / sizeof(*typesNoImpl)); j++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't want to test if the algorithm isn't supported.
No point in retesting the first algorithm over and over again.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @SparkiDev thanks for taking a look at this.

This isn't a matter of unsupported algorithm, nor testing the same algo repeatedly.

We're also not testing the type of the placeholder hash assigned with this_hash. We just need any non-null value.

The tesdt.c loop on line 6238 iterates though all known good hash types from the typesGood list., testing each one time. However, it may be the case that 1 or more of those may not be implemented and/or (more likely) not compiled in. That list is assembled in typesNoImpl; see line 6111.

The tricky thing in this section is that we are looking for a "expected return value" in exp_ret to be HASH_TYPE_E.

The interesting thing here though, is that in order to do that testing, the hash object parameter created needs to have some valid type, otherwise for example the wc_HashUpdate falls through the case statement and ends up instead returning BAD_FUNC_ARG.

It's admittedly a bit eyebrow raising: as the hash type assigned for these tests is arbitrary (and fixed). But this is just so the subsequent wc_HashInit, wc_HashUpdate, wc_HashFinal for the actual hash being tested returns the expected error codes, instead of complaining about a bad hash object.

Otherwise without that placeholder valid hash type, the wc_HashNew won't create a hash object to test with if it is one of the typesNoImpl items.

It is a good test to ensure that all of the known-good, but not-compiled-in algorithms properly return the HASH_TYPE_E value, and we should be going through the entire list for all the functions.

Perhaps the change here is to have the code better documented?

What do you think of a comment just before the 'for j = 0 ... to be something like:

Ensure we create a valid object for all known hash algorithms, including those that may not be compiled in..
Functions not compiled in are expected to return HASH_TYPE_E and need a value hash object to test with.

@gojimmypi gojimmypi requested a review from SparkiDev February 27, 2025 01:24
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