CVE-2026-45542
Description
ESF-IDF is the Espressif Internet of Things (IOT) Development Framework. In versions 5.2.6, 5.3.5, 5.4.4, 5.5.4, and 6.0, a heap buffer overflow exists in the Security Scheme 2 (SRP6a) session-setup path of the protocomm component. The first-phase handler (handle_session_command0() in components/protocomm/src/security/security2.c) trusts the length of a client-supplied protobuf field for the SRP6a username and copies it into a buffer whose size is derived from a narrower destination type. The resulting truncation-versus-copy asymmetry corrupts the heap when an oversized value is supplied. This issue has been patched in versions 5.2.7, 5.3.6, 5.4.5, 5.5.5, and 6.0.1.
Affected products
2Patches
6f5d24a7e919bfix(protocomm): fixes potential issues that can lead to crash during device provisioning
6 files changed · +882 −6
components/mbedtls/port/include/mbedtls/esp_config.h+2 −0 modified@@ -27,7 +27,9 @@ #include "sdkconfig.h" #include "mbedtls/mbedtls_config.h" +#ifndef CONFIG_IDF_TARGET_LINUX #include "soc/soc_caps.h" +#endif // !CONFIG_IDF_TARGET_LINUX /** * \name SECTION: System support
components/protocomm/src/security/security1.c+15 −2 modified@@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -102,7 +102,7 @@ static esp_err_t handle_session_command1(session_t *cur_session, } /* Validate client verifier data before processing */ - if (!in || !in->sc1 || + if (!in || in->payload_case != SEC1_PAYLOAD__PAYLOAD_SC1 || !in->sc1 || in->sc1->client_verify_data.data == NULL || in->sc1->client_verify_data.len != PUBLIC_KEY_LEN) { ESP_LOGE(TAG, "Invalid client verifier (ptr=%p len=%d)", @@ -229,6 +229,14 @@ static esp_err_t handle_session_command0(session_t *cur_session, sec1_new_session(cur_session, session_id); } + if (in->payload_case != SEC1_PAYLOAD__PAYLOAD_SC0 || !in->sc0) { + ESP_LOGE(TAG, "Missing or mismatched sc0 payload in session command0"); + if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { + ESP_LOGE(TAG, "Failed to post secure session invalid security params event"); + } + return ESP_ERR_INVALID_ARG; + } + if (in->sc0->client_pubkey.len != PUBLIC_KEY_LEN) { ESP_LOGE(TAG, "Invalid public key length"); if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { @@ -593,6 +601,11 @@ static esp_err_t sec1_req_handler(protocomm_security_handle_t handle, session_data__free_unpacked(req, NULL); return ESP_ERR_INVALID_ARG; } + if (req->proto_case != SESSION_DATA__PROTO_SEC1) { + ESP_LOGE(TAG, "Security scheme mismatch. Closing connection"); + session_data__free_unpacked(req, NULL); + return ESP_ERR_INVALID_ARG; + } session_data__init(&resp); ret = sec1_session_setup(cur_session, session_id, req, &resp, (protocomm_security1_params_t *) sec_params);
components/protocomm/src/security/security2.c+19 −4 modified@@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -91,6 +91,11 @@ static esp_err_t handle_session_command0(session_t *cur_session, ESP_LOGD(TAG, "Request to handle setup0_command"); Sec2Payload *in = (Sec2Payload *) req->sec2; + if (in->payload_case != SEC2_PAYLOAD__PAYLOAD_SC0 || !in->sc0) { + ESP_LOGE(TAG, "Missing or mismatched sc0 payload in session command0"); + return ESP_ERR_INVALID_ARG; + } + if (cur_session->state != SESSION_STATE_CMD0) { ESP_LOGW(TAG, "Invalid state of session %d (expected %d). Restarting session.", SESSION_STATE_CMD0, cur_session->state); @@ -105,8 +110,8 @@ static esp_err_t handle_session_command0(session_t *cur_session, return ESP_ERR_INVALID_ARG; } - if (in->sc0->client_username.len <= 0) { - ESP_LOGE(TAG, "Invalid username"); + if (in->sc0->client_username.len == 0 || in->sc0->client_username.len > UINT16_MAX) { + ESP_LOGE(TAG, "Invalid username length (%zu)", in->sc0->client_username.len); if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post secure session invalid security params event"); } @@ -197,7 +202,7 @@ static esp_err_t handle_session_command0(session_t *cur_session, free(out_resp); return ESP_ERR_NO_MEM; } - memcpy(cur_session->username, in->sc0->client_username.data, in->sc0->client_username.len); + memcpy(cur_session->username, in->sc0->client_username.data, cur_session->username_len); resp->sec_ver = SEC_SCHEME_VERSION__SecScheme2; resp->proto_case = SESSION_DATA__PROTO_SEC2; @@ -217,6 +222,11 @@ static esp_err_t handle_session_command1(session_t *cur_session, Sec2Payload *in = (Sec2Payload *) req->sec2; int mbed_err = -0x0001; + if (in->payload_case != SEC2_PAYLOAD__PAYLOAD_SC1 || !in->sc1) { + ESP_LOGE(TAG, "Missing or mismatched sc1 payload in session command1"); + return ESP_ERR_INVALID_ARG; + } + if (cur_session->state != SESSION_STATE_CMD1) { ESP_LOGE(TAG, "Invalid state of session %d (expected %d)", SESSION_STATE_CMD1, cur_session->state); return ESP_ERR_INVALID_STATE; @@ -576,6 +586,11 @@ static esp_err_t sec2_req_handler(protocomm_security_handle_t handle, session_data__free_unpacked(req, NULL); return ESP_ERR_INVALID_ARG; } + if (req->proto_case != SESSION_DATA__PROTO_SEC2) { + ESP_LOGE(TAG, "Security scheme mismatch. Closing connection"); + session_data__free_unpacked(req, NULL); + return ESP_ERR_INVALID_ARG; + } session_data__init(&resp); ret = sec2_session_setup(cur_session, session_id, req, &resp, (protocomm_security2_params_t *) sec_params);
components/protocomm/test_apps/main/test_security1.c+376 −0 added@@ -0,0 +1,376 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <esp_err.h> +#include <esp_log.h> +#include <unity.h> +#include <protocomm.h> +#include <protocomm_security.h> +#include <protocomm_security1.h> +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "sec2.pb-c.h" + +#if CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 + +static const char *TAG = "test_security1"; + +/* Must match PUBLIC_KEY_LEN in security1.c */ +#define SEC1_PUBLIC_KEY_LEN 32 + +static esp_err_t test_cmd0_missing_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 200; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + /* msg=Command0 but payload_case left as NOT_SET and sc0 left as NULL */ + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command0; + /* payload.payload_case deliberately left as SEC1_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc0 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc0 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc0 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd0 missing sc0 payload NULL dereference", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_missing_sc0_payload()); +} + +static esp_err_t test_cmd0_msg_with_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 201; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command0 but sc1 (not sc0) populated in the oneof */ + uint8_t verify_data[SEC1_PUBLIC_KEY_LEN]; + memset(verify_data, 0x55, sizeof(verify_data)); + + SessionCmd1 cmd1 = SESSION_CMD1__INIT; + cmd1.client_verify_data.data = verify_data; + cmd1.client_verify_data.len = sizeof(verify_data); + + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command0; /* CMD0 msg */ + payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC1; /* SC1 payload — mismatch */ + payload.sc1 = &cmd1; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD0/sc1 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD0/sc1 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd0 msg with sc1 payload oneof type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_msg_with_sc1_payload()); +} + +static esp_err_t test_cmd1_missing_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 202; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command1 but payload_case left as NOT_SET and sc1 left as NULL */ + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command1; + /* payload.payload_case deliberately left as SEC1_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc1 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc1 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc1 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd1 missing sc1 payload NULL dereference", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_missing_sc1_payload()); +} + +static esp_err_t test_cmd1_msg_with_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 203; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command1 but sc0 (not sc1) populated in the oneof */ + uint8_t pubkey[SEC1_PUBLIC_KEY_LEN]; + memset(pubkey, 0xAB, sizeof(pubkey)); + + SessionCmd0 cmd0 = SESSION_CMD0__INIT; + cmd0.client_pubkey.data = pubkey; + cmd0.client_pubkey.len = sizeof(pubkey); + + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command1; /* CMD1 msg */ + payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; /* SC0 payload — mismatch */ + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD1/sc0 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD1/sc0 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd1 msg with sc0 payload oneof type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_msg_with_sc0_payload()); +} + +static esp_err_t test_sec2_payload_with_sec1_version(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 204; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* SessionData with sec_ver=1 but proto_case=SEC2, carrying a Sec2 CMD0. + * Sec2's S2SessionCmd0 (56 bytes) is larger than Sec1's SessionCmd0 + * (40 bytes), so sec1 code reading the union as Sec1Payload causes + * type confusion. */ + uint8_t pubkey[384]; + memset(pubkey, 0xAB, sizeof(pubkey)); + uint8_t uname[] = "wifiprov"; + + S2SessionCmd0 s2cmd0 = S2_SESSION_CMD0__INIT; + s2cmd0.client_pubkey.data = pubkey; + s2cmd0.client_pubkey.len = sizeof(pubkey); + s2cmd0.client_username.data = uname; + s2cmd0.client_username.len = sizeof(uname) - 1; + + Sec2Payload sec2_payload = SEC2_PAYLOAD__INIT; + sec2_payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + sec2_payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; + sec2_payload.sc0 = &s2cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = SEC_SCHEME_VERSION__SecScheme1; /* sec_ver=1 — passes ver check */ + req.proto_case = SESSION_DATA__PROTO_SEC2; /* but SEC2 proto — mismatch */ + req.sec2 = &sec2_payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "sec2 payload with sec_ver=1 was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "sec2 payload with sec_ver=1 correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 sec2 payload with sec_ver=1 cross-scheme type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_sec2_payload_with_sec1_version()); +} + +#endif /* CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 */
components/protocomm/test_apps/main/test_security2.c+465 −0 added@@ -0,0 +1,465 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <esp_err.h> +#include <esp_log.h> +#include <unity.h> +#include <protocomm.h> +#include <protocomm_security.h> +#include <protocomm_security2.h> +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "sec2.pb-c.h" + +#if CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 + +static const char *TAG = "test_security2"; + +/* + * Pre-computed salt and verifier for username="wifiprov", password="abcd1234". + * Identical to the vectors used in test_srp.c. + */ +static const char sec2_salt[] = { + 0x03, 0x6e, 0xe0, 0xc7, 0xbc, 0xb9, 0xed, 0xa8, + 0x4c, 0x9e, 0xac, 0x97, 0xd9, 0x3d, 0xec, 0xf4 +}; + +static const char sec2_verifier[] = { + 0x7c, 0x7c, 0x85, 0x47, 0x65, 0x08, 0x94, 0x6d, 0xd6, 0x36, 0xaf, 0x37, 0xd7, 0xe8, 0x91, 0x43, + 0x78, 0xcf, 0xfd, 0x61, 0x6c, 0x59, 0xd2, 0xf8, 0x39, 0x08, 0x12, 0x72, 0x38, 0xde, 0x9e, 0x24, + 0xa4, 0x70, 0x26, 0x1c, 0xdf, 0xa9, 0x03, 0xc2, 0xb2, 0x70, 0xe7, 0xb1, 0x32, 0x24, 0xda, 0x11, + 0x1d, 0x97, 0x18, 0xdc, 0x60, 0x72, 0x08, 0xcc, 0x9a, 0xc9, 0x0c, 0x48, 0x27, 0xe2, 0xae, 0x89, + 0xaa, 0x16, 0x25, 0xb8, 0x04, 0xd2, 0x1a, 0x9b, 0x3a, 0x8f, 0x37, 0xf6, 0xe4, 0x3a, 0x71, 0x2e, + 0xe1, 0x27, 0x86, 0x6e, 0xad, 0xce, 0x28, 0xff, 0x54, 0x46, 0x60, 0x1f, 0xb9, 0x96, 0x87, 0xdc, + 0x57, 0x40, 0xa7, 0xd4, 0x6c, 0xc9, 0x77, 0x54, 0xdc, 0x16, 0x82, 0xf0, 0xed, 0x35, 0x6a, 0xc4, + 0x70, 0xad, 0x3d, 0x90, 0xb5, 0x81, 0x94, 0x70, 0xd7, 0xbc, 0x65, 0xb2, 0xd5, 0x18, 0xe0, 0x2e, + 0xc3, 0xa5, 0xf9, 0x68, 0xdd, 0x64, 0x7b, 0xb8, 0xb7, 0x3c, 0x9c, 0xfc, 0x00, 0xd8, 0x71, 0x7e, + 0xb7, 0x9a, 0x7c, 0xb1, 0xb7, 0xc2, 0xc3, 0x18, 0x34, 0x29, 0x32, 0x43, 0x3e, 0x00, 0x99, 0xe9, + 0x82, 0x94, 0xe3, 0xd8, 0x2a, 0xb0, 0x96, 0x29, 0xb7, 0xdf, 0x0e, 0x5f, 0x08, 0x33, 0x40, 0x76, + 0x52, 0x91, 0x32, 0x00, 0x9f, 0x97, 0x2c, 0x89, 0x6c, 0x39, 0x1e, 0xc8, 0x28, 0x05, 0x44, 0x17, + 0x3f, 0x68, 0x02, 0x8a, 0x9f, 0x44, 0x61, 0xd1, 0xf5, 0xa1, 0x7e, 0x5a, 0x70, 0xd2, 0xc7, 0x23, + 0x81, 0xcb, 0x38, 0x68, 0xe4, 0x2c, 0x20, 0xbc, 0x40, 0x57, 0x76, 0x17, 0xbd, 0x08, 0xb8, 0x96, + 0xbc, 0x26, 0xeb, 0x32, 0x46, 0x69, 0x35, 0x05, 0x8c, 0x15, 0x70, 0xd9, 0x1b, 0xe9, 0xbe, 0xcc, + 0xa9, 0x38, 0xa6, 0x67, 0xf0, 0xad, 0x50, 0x13, 0x19, 0x72, 0x64, 0xbf, 0x52, 0xc2, 0x34, 0xe2, + 0x1b, 0x11, 0x79, 0x74, 0x72, 0xbd, 0x34, 0x5b, 0xb1, 0xe2, 0xfd, 0x66, 0x73, 0xfe, 0x71, 0x64, + 0x74, 0xd0, 0x4e, 0xbc, 0x51, 0x24, 0x19, 0x40, 0x87, 0x0e, 0x92, 0x40, 0xe6, 0x21, 0xe7, 0x2d, + 0x4e, 0x37, 0x76, 0x2f, 0x2e, 0xe2, 0x68, 0xc7, 0x89, 0xe8, 0x32, 0x13, 0x42, 0x06, 0x84, 0x84, + 0x53, 0x4a, 0xb3, 0x0c, 0x1b, 0x4c, 0x8d, 0x1c, 0x51, 0x97, 0x19, 0xab, 0xae, 0x77, 0xff, 0xdb, + 0xec, 0xf0, 0x10, 0x95, 0x34, 0x33, 0x6b, 0xcb, 0x3e, 0x84, 0x0f, 0xb9, 0xd8, 0x5f, 0xb8, 0xa0, + 0xb8, 0x55, 0x53, 0x3e, 0x70, 0xf7, 0x18, 0xf5, 0xce, 0x7b, 0x4e, 0xbf, 0x27, 0xce, 0xce, 0xa8, + 0xb3, 0xbe, 0x40, 0xc5, 0xc5, 0x32, 0x29, 0x3e, 0x71, 0x64, 0x9e, 0xde, 0x8c, 0xf6, 0x75, 0xa1, + 0xe6, 0xf6, 0x53, 0xc8, 0x31, 0xa8, 0x78, 0xde, 0x50, 0x40, 0xf7, 0x62, 0xde, 0x36, 0xb2, 0xba +}; + +/* Must match PUBLIC_KEY_LEN in security2.c */ +#define SEC2_PUBLIC_KEY_LEN 384 + +static esp_err_t test_cmd0_oversized_username(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 100; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const size_t username_len = (size_t)UINT16_MAX + 2; + uint8_t *username_buf = calloc(1, username_len); + if (!username_buf) { + ESP_LOGE(TAG, "Cannot allocate %zu-byte username buffer — skipping test", username_len); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + memset(username_buf, 'A', username_len); + + uint8_t client_pubkey[SEC2_PUBLIC_KEY_LEN]; + memset(client_pubkey, 0xAB, sizeof(client_pubkey)); + + S2SessionCmd0 cmd0 = S2_SESSION_CMD0__INIT; + cmd0.client_username.data = username_buf; + cmd0.client_username.len = username_len; + cmd0.client_pubkey.data = client_pubkey; + cmd0.client_pubkey.len = SEC2_PUBLIC_KEY_LEN; + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + ESP_LOGE(TAG, "Cannot allocate %zu-byte packed buffer — skipping test", packed_len); + free(username_buf); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + free(username_buf); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Oversized username was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Oversized username correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 oversized username uint16_t truncation heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_oversized_username()); +} + +static esp_err_t test_cmd0_missing_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 101; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + /* payload.payload_case deliberately left as SEC2_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc0 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + ESP_LOGE(TAG, "Cannot allocate packed buffer"); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc0 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc0 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 missing sc0 payload NULL dereference", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_missing_sc0_payload()); +} + +static esp_err_t test_cmd0_msg_with_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 102; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* Build a Sec2Payload with msg=CMD0 but sc1 (not sc0) in the oneof. */ + uint8_t proof[64]; + memset(proof, 0x33, sizeof(proof)); + + S2SessionCmd1 cmd1 = S2_SESSION_CMD1__INIT; + cmd1.client_proof.data = proof; + cmd1.client_proof.len = sizeof(proof); + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; /* CMD0 msg */ + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC1; /* SC1 payload — mismatch */ + payload.sc1 = &cmd1; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD0/sc1 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD0/sc1 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 msg with sc1 payload oneof type confusion heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_msg_with_sc1_payload()); +} + +static esp_err_t test_cmd1_msg_with_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 103; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* Build a Sec2Payload with msg=CMD1 but sc0 (not sc1) in the oneof. */ + uint8_t pubkey[SEC2_PUBLIC_KEY_LEN]; + memset(pubkey, 0xAB, sizeof(pubkey)); + uint8_t uname[] = "alice"; + + S2SessionCmd0 cmd0 = S2_SESSION_CMD0__INIT; + cmd0.client_username.data = uname; + cmd0.client_username.len = sizeof(uname) - 1; + cmd0.client_pubkey.data = pubkey; + cmd0.client_pubkey.len = sizeof(pubkey); + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command1; /* CMD1 msg */ + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; /* SC0 payload — mismatch */ + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD1/sc0 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD1/sc0 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd1 msg with sc0 payload oneof type confusion", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_msg_with_sc0_payload()); +} + +static esp_err_t test_sec1_payload_with_sec2_version(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 104; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + uint8_t pubkey[32]; + memset(pubkey, 0xAB, sizeof(pubkey)); + + SessionCmd0 sec1_cmd0 = SESSION_CMD0__INIT; + sec1_cmd0.client_pubkey.data = pubkey; + sec1_cmd0.client_pubkey.len = sizeof(pubkey); + + Sec1Payload sec1_payload = SEC1_PAYLOAD__INIT; + sec1_payload.msg = SEC1_MSG_TYPE__Session_Command0; + sec1_payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; + sec1_payload.sc0 = &sec1_cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = SEC_SCHEME_VERSION__SecScheme2; /* sec_ver=2 — passes ver check */ + req.proto_case = SESSION_DATA__PROTO_SEC1; /* but SEC1 proto — mismatch */ + req.sec1 = &sec1_payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "sec1 payload with sec_ver=2 was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "sec1 payload with sec_ver=2 correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 sec1 payload with sec_ver=2 cross-scheme type confusion heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_sec1_payload_with_sec2_version()); +} + +#endif /* CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 */
components/protocomm/test_apps/sdkconfig.defaults+5 −0 modified@@ -7,3 +7,8 @@ CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y CONFIG_COMPILER_STACK_CHECK=y CONFIG_ESP_TASK_WDT_EN=n + +# Enable all protocomm security versions for testing +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y
0ea58d79845afix(protocomm): fixes potential issues that can lead to crash during device provisioning
6 files changed · +882 −6
components/mbedtls/port/include/mbedtls/esp_config.h+2 −0 modified@@ -27,7 +27,9 @@ #include "sdkconfig.h" #include "mbedtls/mbedtls_config.h" +#ifndef CONFIG_IDF_TARGET_LINUX #include "soc/soc_caps.h" +#endif // !CONFIG_IDF_TARGET_LINUX /** * \name SECTION: System support
components/protocomm/src/security/security1.c+15 −2 modified@@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -102,7 +102,7 @@ static esp_err_t handle_session_command1(session_t *cur_session, } /* Validate client verifier data before processing */ - if (!in || !in->sc1 || + if (!in || in->payload_case != SEC1_PAYLOAD__PAYLOAD_SC1 || !in->sc1 || in->sc1->client_verify_data.data == NULL || in->sc1->client_verify_data.len != PUBLIC_KEY_LEN) { ESP_LOGE(TAG, "Invalid client verifier (ptr=%p len=%d)", @@ -229,6 +229,14 @@ static esp_err_t handle_session_command0(session_t *cur_session, sec1_new_session(cur_session, session_id); } + if (in->payload_case != SEC1_PAYLOAD__PAYLOAD_SC0 || !in->sc0) { + ESP_LOGE(TAG, "Missing or mismatched sc0 payload in session command0"); + if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { + ESP_LOGE(TAG, "Failed to post secure session invalid security params event"); + } + return ESP_ERR_INVALID_ARG; + } + if (in->sc0->client_pubkey.len != PUBLIC_KEY_LEN) { ESP_LOGE(TAG, "Invalid public key length"); if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { @@ -593,6 +601,11 @@ static esp_err_t sec1_req_handler(protocomm_security_handle_t handle, session_data__free_unpacked(req, NULL); return ESP_ERR_INVALID_ARG; } + if (req->proto_case != SESSION_DATA__PROTO_SEC1) { + ESP_LOGE(TAG, "Security scheme mismatch. Closing connection"); + session_data__free_unpacked(req, NULL); + return ESP_ERR_INVALID_ARG; + } session_data__init(&resp); ret = sec1_session_setup(cur_session, session_id, req, &resp, (protocomm_security1_params_t *) sec_params);
components/protocomm/src/security/security2.c+19 −4 modified@@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -91,6 +91,11 @@ static esp_err_t handle_session_command0(session_t *cur_session, ESP_LOGD(TAG, "Request to handle setup0_command"); Sec2Payload *in = (Sec2Payload *) req->sec2; + if (in->payload_case != SEC2_PAYLOAD__PAYLOAD_SC0 || !in->sc0) { + ESP_LOGE(TAG, "Missing or mismatched sc0 payload in session command0"); + return ESP_ERR_INVALID_ARG; + } + if (cur_session->state != SESSION_STATE_CMD0) { ESP_LOGW(TAG, "Invalid state of session %d (expected %d). Restarting session.", SESSION_STATE_CMD0, cur_session->state); @@ -105,8 +110,8 @@ static esp_err_t handle_session_command0(session_t *cur_session, return ESP_ERR_INVALID_ARG; } - if (in->sc0->client_username.len <= 0) { - ESP_LOGE(TAG, "Invalid username"); + if (in->sc0->client_username.len == 0 || in->sc0->client_username.len > UINT16_MAX) { + ESP_LOGE(TAG, "Invalid username length (%zu)", in->sc0->client_username.len); if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post secure session invalid security params event"); } @@ -197,7 +202,7 @@ static esp_err_t handle_session_command0(session_t *cur_session, free(out_resp); return ESP_ERR_NO_MEM; } - memcpy(cur_session->username, in->sc0->client_username.data, in->sc0->client_username.len); + memcpy(cur_session->username, in->sc0->client_username.data, cur_session->username_len); resp->sec_ver = SEC_SCHEME_VERSION__SecScheme2; resp->proto_case = SESSION_DATA__PROTO_SEC2; @@ -217,6 +222,11 @@ static esp_err_t handle_session_command1(session_t *cur_session, Sec2Payload *in = (Sec2Payload *) req->sec2; int mbed_err = -0x0001; + if (in->payload_case != SEC2_PAYLOAD__PAYLOAD_SC1 || !in->sc1) { + ESP_LOGE(TAG, "Missing or mismatched sc1 payload in session command1"); + return ESP_ERR_INVALID_ARG; + } + if (cur_session->state != SESSION_STATE_CMD1) { ESP_LOGE(TAG, "Invalid state of session %d (expected %d)", SESSION_STATE_CMD1, cur_session->state); return ESP_ERR_INVALID_STATE; @@ -576,6 +586,11 @@ static esp_err_t sec2_req_handler(protocomm_security_handle_t handle, session_data__free_unpacked(req, NULL); return ESP_ERR_INVALID_ARG; } + if (req->proto_case != SESSION_DATA__PROTO_SEC2) { + ESP_LOGE(TAG, "Security scheme mismatch. Closing connection"); + session_data__free_unpacked(req, NULL); + return ESP_ERR_INVALID_ARG; + } session_data__init(&resp); ret = sec2_session_setup(cur_session, session_id, req, &resp, (protocomm_security2_params_t *) sec_params);
components/protocomm/test_apps/main/test_security1.c+376 −0 added@@ -0,0 +1,376 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <esp_err.h> +#include <esp_log.h> +#include <unity.h> +#include <protocomm.h> +#include <protocomm_security.h> +#include <protocomm_security1.h> +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "sec2.pb-c.h" + +#if CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 + +static const char *TAG = "test_security1"; + +/* Must match PUBLIC_KEY_LEN in security1.c */ +#define SEC1_PUBLIC_KEY_LEN 32 + +static esp_err_t test_cmd0_missing_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 200; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + /* msg=Command0 but payload_case left as NOT_SET and sc0 left as NULL */ + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command0; + /* payload.payload_case deliberately left as SEC1_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc0 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc0 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc0 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd0 missing sc0 payload NULL dereference", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_missing_sc0_payload()); +} + +static esp_err_t test_cmd0_msg_with_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 201; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command0 but sc1 (not sc0) populated in the oneof */ + uint8_t verify_data[SEC1_PUBLIC_KEY_LEN]; + memset(verify_data, 0x55, sizeof(verify_data)); + + SessionCmd1 cmd1 = SESSION_CMD1__INIT; + cmd1.client_verify_data.data = verify_data; + cmd1.client_verify_data.len = sizeof(verify_data); + + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command0; /* CMD0 msg */ + payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC1; /* SC1 payload — mismatch */ + payload.sc1 = &cmd1; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD0/sc1 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD0/sc1 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd0 msg with sc1 payload oneof type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_msg_with_sc1_payload()); +} + +static esp_err_t test_cmd1_missing_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 202; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command1 but payload_case left as NOT_SET and sc1 left as NULL */ + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command1; + /* payload.payload_case deliberately left as SEC1_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc1 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc1 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc1 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd1 missing sc1 payload NULL dereference", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_missing_sc1_payload()); +} + +static esp_err_t test_cmd1_msg_with_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 203; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command1 but sc0 (not sc1) populated in the oneof */ + uint8_t pubkey[SEC1_PUBLIC_KEY_LEN]; + memset(pubkey, 0xAB, sizeof(pubkey)); + + SessionCmd0 cmd0 = SESSION_CMD0__INIT; + cmd0.client_pubkey.data = pubkey; + cmd0.client_pubkey.len = sizeof(pubkey); + + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command1; /* CMD1 msg */ + payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; /* SC0 payload — mismatch */ + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD1/sc0 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD1/sc0 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd1 msg with sc0 payload oneof type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_msg_with_sc0_payload()); +} + +static esp_err_t test_sec2_payload_with_sec1_version(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 204; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* SessionData with sec_ver=1 but proto_case=SEC2, carrying a Sec2 CMD0. + * Sec2's S2SessionCmd0 (56 bytes) is larger than Sec1's SessionCmd0 + * (40 bytes), so sec1 code reading the union as Sec1Payload causes + * type confusion. */ + uint8_t pubkey[384]; + memset(pubkey, 0xAB, sizeof(pubkey)); + uint8_t uname[] = "wifiprov"; + + S2SessionCmd0 s2cmd0 = S2_SESSION_CMD0__INIT; + s2cmd0.client_pubkey.data = pubkey; + s2cmd0.client_pubkey.len = sizeof(pubkey); + s2cmd0.client_username.data = uname; + s2cmd0.client_username.len = sizeof(uname) - 1; + + Sec2Payload sec2_payload = SEC2_PAYLOAD__INIT; + sec2_payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + sec2_payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; + sec2_payload.sc0 = &s2cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = SEC_SCHEME_VERSION__SecScheme1; /* sec_ver=1 — passes ver check */ + req.proto_case = SESSION_DATA__PROTO_SEC2; /* but SEC2 proto — mismatch */ + req.sec2 = &sec2_payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "sec2 payload with sec_ver=1 was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "sec2 payload with sec_ver=1 correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 sec2 payload with sec_ver=1 cross-scheme type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_sec2_payload_with_sec1_version()); +} + +#endif /* CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 */
components/protocomm/test_apps/main/test_security2.c+465 −0 added@@ -0,0 +1,465 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <esp_err.h> +#include <esp_log.h> +#include <unity.h> +#include <protocomm.h> +#include <protocomm_security.h> +#include <protocomm_security2.h> +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "sec2.pb-c.h" + +#if CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 + +static const char *TAG = "test_security2"; + +/* + * Pre-computed salt and verifier for username="wifiprov", password="abcd1234". + * Identical to the vectors used in test_srp.c. + */ +static const char sec2_salt[] = { + 0x03, 0x6e, 0xe0, 0xc7, 0xbc, 0xb9, 0xed, 0xa8, + 0x4c, 0x9e, 0xac, 0x97, 0xd9, 0x3d, 0xec, 0xf4 +}; + +static const char sec2_verifier[] = { + 0x7c, 0x7c, 0x85, 0x47, 0x65, 0x08, 0x94, 0x6d, 0xd6, 0x36, 0xaf, 0x37, 0xd7, 0xe8, 0x91, 0x43, + 0x78, 0xcf, 0xfd, 0x61, 0x6c, 0x59, 0xd2, 0xf8, 0x39, 0x08, 0x12, 0x72, 0x38, 0xde, 0x9e, 0x24, + 0xa4, 0x70, 0x26, 0x1c, 0xdf, 0xa9, 0x03, 0xc2, 0xb2, 0x70, 0xe7, 0xb1, 0x32, 0x24, 0xda, 0x11, + 0x1d, 0x97, 0x18, 0xdc, 0x60, 0x72, 0x08, 0xcc, 0x9a, 0xc9, 0x0c, 0x48, 0x27, 0xe2, 0xae, 0x89, + 0xaa, 0x16, 0x25, 0xb8, 0x04, 0xd2, 0x1a, 0x9b, 0x3a, 0x8f, 0x37, 0xf6, 0xe4, 0x3a, 0x71, 0x2e, + 0xe1, 0x27, 0x86, 0x6e, 0xad, 0xce, 0x28, 0xff, 0x54, 0x46, 0x60, 0x1f, 0xb9, 0x96, 0x87, 0xdc, + 0x57, 0x40, 0xa7, 0xd4, 0x6c, 0xc9, 0x77, 0x54, 0xdc, 0x16, 0x82, 0xf0, 0xed, 0x35, 0x6a, 0xc4, + 0x70, 0xad, 0x3d, 0x90, 0xb5, 0x81, 0x94, 0x70, 0xd7, 0xbc, 0x65, 0xb2, 0xd5, 0x18, 0xe0, 0x2e, + 0xc3, 0xa5, 0xf9, 0x68, 0xdd, 0x64, 0x7b, 0xb8, 0xb7, 0x3c, 0x9c, 0xfc, 0x00, 0xd8, 0x71, 0x7e, + 0xb7, 0x9a, 0x7c, 0xb1, 0xb7, 0xc2, 0xc3, 0x18, 0x34, 0x29, 0x32, 0x43, 0x3e, 0x00, 0x99, 0xe9, + 0x82, 0x94, 0xe3, 0xd8, 0x2a, 0xb0, 0x96, 0x29, 0xb7, 0xdf, 0x0e, 0x5f, 0x08, 0x33, 0x40, 0x76, + 0x52, 0x91, 0x32, 0x00, 0x9f, 0x97, 0x2c, 0x89, 0x6c, 0x39, 0x1e, 0xc8, 0x28, 0x05, 0x44, 0x17, + 0x3f, 0x68, 0x02, 0x8a, 0x9f, 0x44, 0x61, 0xd1, 0xf5, 0xa1, 0x7e, 0x5a, 0x70, 0xd2, 0xc7, 0x23, + 0x81, 0xcb, 0x38, 0x68, 0xe4, 0x2c, 0x20, 0xbc, 0x40, 0x57, 0x76, 0x17, 0xbd, 0x08, 0xb8, 0x96, + 0xbc, 0x26, 0xeb, 0x32, 0x46, 0x69, 0x35, 0x05, 0x8c, 0x15, 0x70, 0xd9, 0x1b, 0xe9, 0xbe, 0xcc, + 0xa9, 0x38, 0xa6, 0x67, 0xf0, 0xad, 0x50, 0x13, 0x19, 0x72, 0x64, 0xbf, 0x52, 0xc2, 0x34, 0xe2, + 0x1b, 0x11, 0x79, 0x74, 0x72, 0xbd, 0x34, 0x5b, 0xb1, 0xe2, 0xfd, 0x66, 0x73, 0xfe, 0x71, 0x64, + 0x74, 0xd0, 0x4e, 0xbc, 0x51, 0x24, 0x19, 0x40, 0x87, 0x0e, 0x92, 0x40, 0xe6, 0x21, 0xe7, 0x2d, + 0x4e, 0x37, 0x76, 0x2f, 0x2e, 0xe2, 0x68, 0xc7, 0x89, 0xe8, 0x32, 0x13, 0x42, 0x06, 0x84, 0x84, + 0x53, 0x4a, 0xb3, 0x0c, 0x1b, 0x4c, 0x8d, 0x1c, 0x51, 0x97, 0x19, 0xab, 0xae, 0x77, 0xff, 0xdb, + 0xec, 0xf0, 0x10, 0x95, 0x34, 0x33, 0x6b, 0xcb, 0x3e, 0x84, 0x0f, 0xb9, 0xd8, 0x5f, 0xb8, 0xa0, + 0xb8, 0x55, 0x53, 0x3e, 0x70, 0xf7, 0x18, 0xf5, 0xce, 0x7b, 0x4e, 0xbf, 0x27, 0xce, 0xce, 0xa8, + 0xb3, 0xbe, 0x40, 0xc5, 0xc5, 0x32, 0x29, 0x3e, 0x71, 0x64, 0x9e, 0xde, 0x8c, 0xf6, 0x75, 0xa1, + 0xe6, 0xf6, 0x53, 0xc8, 0x31, 0xa8, 0x78, 0xde, 0x50, 0x40, 0xf7, 0x62, 0xde, 0x36, 0xb2, 0xba +}; + +/* Must match PUBLIC_KEY_LEN in security2.c */ +#define SEC2_PUBLIC_KEY_LEN 384 + +static esp_err_t test_cmd0_oversized_username(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 100; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const size_t username_len = (size_t)UINT16_MAX + 2; + uint8_t *username_buf = calloc(1, username_len); + if (!username_buf) { + ESP_LOGE(TAG, "Cannot allocate %zu-byte username buffer — skipping test", username_len); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + memset(username_buf, 'A', username_len); + + uint8_t client_pubkey[SEC2_PUBLIC_KEY_LEN]; + memset(client_pubkey, 0xAB, sizeof(client_pubkey)); + + S2SessionCmd0 cmd0 = S2_SESSION_CMD0__INIT; + cmd0.client_username.data = username_buf; + cmd0.client_username.len = username_len; + cmd0.client_pubkey.data = client_pubkey; + cmd0.client_pubkey.len = SEC2_PUBLIC_KEY_LEN; + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + ESP_LOGE(TAG, "Cannot allocate %zu-byte packed buffer — skipping test", packed_len); + free(username_buf); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + free(username_buf); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Oversized username was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Oversized username correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 oversized username uint16_t truncation heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_oversized_username()); +} + +static esp_err_t test_cmd0_missing_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 101; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + /* payload.payload_case deliberately left as SEC2_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc0 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + ESP_LOGE(TAG, "Cannot allocate packed buffer"); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc0 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc0 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 missing sc0 payload NULL dereference", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_missing_sc0_payload()); +} + +static esp_err_t test_cmd0_msg_with_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 102; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* Build a Sec2Payload with msg=CMD0 but sc1 (not sc0) in the oneof. */ + uint8_t proof[64]; + memset(proof, 0x33, sizeof(proof)); + + S2SessionCmd1 cmd1 = S2_SESSION_CMD1__INIT; + cmd1.client_proof.data = proof; + cmd1.client_proof.len = sizeof(proof); + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; /* CMD0 msg */ + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC1; /* SC1 payload — mismatch */ + payload.sc1 = &cmd1; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD0/sc1 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD0/sc1 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 msg with sc1 payload oneof type confusion heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_msg_with_sc1_payload()); +} + +static esp_err_t test_cmd1_msg_with_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 103; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* Build a Sec2Payload with msg=CMD1 but sc0 (not sc1) in the oneof. */ + uint8_t pubkey[SEC2_PUBLIC_KEY_LEN]; + memset(pubkey, 0xAB, sizeof(pubkey)); + uint8_t uname[] = "alice"; + + S2SessionCmd0 cmd0 = S2_SESSION_CMD0__INIT; + cmd0.client_username.data = uname; + cmd0.client_username.len = sizeof(uname) - 1; + cmd0.client_pubkey.data = pubkey; + cmd0.client_pubkey.len = sizeof(pubkey); + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command1; /* CMD1 msg */ + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; /* SC0 payload — mismatch */ + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD1/sc0 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD1/sc0 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd1 msg with sc0 payload oneof type confusion", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_msg_with_sc0_payload()); +} + +static esp_err_t test_sec1_payload_with_sec2_version(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 104; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + uint8_t pubkey[32]; + memset(pubkey, 0xAB, sizeof(pubkey)); + + SessionCmd0 sec1_cmd0 = SESSION_CMD0__INIT; + sec1_cmd0.client_pubkey.data = pubkey; + sec1_cmd0.client_pubkey.len = sizeof(pubkey); + + Sec1Payload sec1_payload = SEC1_PAYLOAD__INIT; + sec1_payload.msg = SEC1_MSG_TYPE__Session_Command0; + sec1_payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; + sec1_payload.sc0 = &sec1_cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = SEC_SCHEME_VERSION__SecScheme2; /* sec_ver=2 — passes ver check */ + req.proto_case = SESSION_DATA__PROTO_SEC1; /* but SEC1 proto — mismatch */ + req.sec1 = &sec1_payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "sec1 payload with sec_ver=2 was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "sec1 payload with sec_ver=2 correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 sec1 payload with sec_ver=2 cross-scheme type confusion heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_sec1_payload_with_sec2_version()); +} + +#endif /* CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 */
components/protocomm/test_apps/sdkconfig.defaults+5 −0 modified@@ -7,3 +7,8 @@ CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y CONFIG_COMPILER_STACK_CHECK=y CONFIG_ESP_TASK_WDT_EN=n + +# Enable all protocomm security versions for testing +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y
56c3e385611efix(protocomm): fixes potential issues that can lead to crash during device provisioning
6 files changed · +882 −6
components/mbedtls/port/include/mbedtls/esp_config.h+2 −0 modified@@ -27,7 +27,9 @@ #include "sdkconfig.h" #include "mbedtls/mbedtls_config.h" +#ifndef CONFIG_IDF_TARGET_LINUX #include "soc/soc_caps.h" +#endif // !CONFIG_IDF_TARGET_LINUX /** * \name SECTION: System support
components/protocomm/src/security/security1.c+15 −2 modified@@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -102,7 +102,7 @@ static esp_err_t handle_session_command1(session_t *cur_session, } /* Validate client verifier data before processing */ - if (!in || !in->sc1 || + if (!in || in->payload_case != SEC1_PAYLOAD__PAYLOAD_SC1 || !in->sc1 || in->sc1->client_verify_data.data == NULL || in->sc1->client_verify_data.len != PUBLIC_KEY_LEN) { ESP_LOGE(TAG, "Invalid client verifier (ptr=%p len=%d)", @@ -229,6 +229,14 @@ static esp_err_t handle_session_command0(session_t *cur_session, sec1_new_session(cur_session, session_id); } + if (in->payload_case != SEC1_PAYLOAD__PAYLOAD_SC0 || !in->sc0) { + ESP_LOGE(TAG, "Missing or mismatched sc0 payload in session command0"); + if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { + ESP_LOGE(TAG, "Failed to post secure session invalid security params event"); + } + return ESP_ERR_INVALID_ARG; + } + if (in->sc0->client_pubkey.len != PUBLIC_KEY_LEN) { ESP_LOGE(TAG, "Invalid public key length"); if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { @@ -593,6 +601,11 @@ static esp_err_t sec1_req_handler(protocomm_security_handle_t handle, session_data__free_unpacked(req, NULL); return ESP_ERR_INVALID_ARG; } + if (req->proto_case != SESSION_DATA__PROTO_SEC1) { + ESP_LOGE(TAG, "Security scheme mismatch. Closing connection"); + session_data__free_unpacked(req, NULL); + return ESP_ERR_INVALID_ARG; + } session_data__init(&resp); ret = sec1_session_setup(cur_session, session_id, req, &resp, (protocomm_security1_params_t *) sec_params);
components/protocomm/src/security/security2.c+19 −4 modified@@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -91,6 +91,11 @@ static esp_err_t handle_session_command0(session_t *cur_session, ESP_LOGD(TAG, "Request to handle setup0_command"); Sec2Payload *in = (Sec2Payload *) req->sec2; + if (in->payload_case != SEC2_PAYLOAD__PAYLOAD_SC0 || !in->sc0) { + ESP_LOGE(TAG, "Missing or mismatched sc0 payload in session command0"); + return ESP_ERR_INVALID_ARG; + } + if (cur_session->state != SESSION_STATE_CMD0) { ESP_LOGW(TAG, "Invalid state of session %d (expected %d). Restarting session.", SESSION_STATE_CMD0, cur_session->state); @@ -105,8 +110,8 @@ static esp_err_t handle_session_command0(session_t *cur_session, return ESP_ERR_INVALID_ARG; } - if (in->sc0->client_username.len <= 0) { - ESP_LOGE(TAG, "Invalid username"); + if (in->sc0->client_username.len == 0 || in->sc0->client_username.len > UINT16_MAX) { + ESP_LOGE(TAG, "Invalid username length (%zu)", in->sc0->client_username.len); if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post secure session invalid security params event"); } @@ -197,7 +202,7 @@ static esp_err_t handle_session_command0(session_t *cur_session, free(out_resp); return ESP_ERR_NO_MEM; } - memcpy(cur_session->username, in->sc0->client_username.data, in->sc0->client_username.len); + memcpy(cur_session->username, in->sc0->client_username.data, cur_session->username_len); resp->sec_ver = SEC_SCHEME_VERSION__SecScheme2; resp->proto_case = SESSION_DATA__PROTO_SEC2; @@ -217,6 +222,11 @@ static esp_err_t handle_session_command1(session_t *cur_session, Sec2Payload *in = (Sec2Payload *) req->sec2; int mbed_err = -0x0001; + if (in->payload_case != SEC2_PAYLOAD__PAYLOAD_SC1 || !in->sc1) { + ESP_LOGE(TAG, "Missing or mismatched sc1 payload in session command1"); + return ESP_ERR_INVALID_ARG; + } + if (cur_session->state != SESSION_STATE_CMD1) { ESP_LOGE(TAG, "Invalid state of session %d (expected %d)", SESSION_STATE_CMD1, cur_session->state); return ESP_ERR_INVALID_STATE; @@ -576,6 +586,11 @@ static esp_err_t sec2_req_handler(protocomm_security_handle_t handle, session_data__free_unpacked(req, NULL); return ESP_ERR_INVALID_ARG; } + if (req->proto_case != SESSION_DATA__PROTO_SEC2) { + ESP_LOGE(TAG, "Security scheme mismatch. Closing connection"); + session_data__free_unpacked(req, NULL); + return ESP_ERR_INVALID_ARG; + } session_data__init(&resp); ret = sec2_session_setup(cur_session, session_id, req, &resp, (protocomm_security2_params_t *) sec_params);
components/protocomm/test_apps/main/test_security1.c+376 −0 added@@ -0,0 +1,376 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <esp_err.h> +#include <esp_log.h> +#include <unity.h> +#include <protocomm.h> +#include <protocomm_security.h> +#include <protocomm_security1.h> +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "sec2.pb-c.h" + +#if CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 + +static const char *TAG = "test_security1"; + +/* Must match PUBLIC_KEY_LEN in security1.c */ +#define SEC1_PUBLIC_KEY_LEN 32 + +static esp_err_t test_cmd0_missing_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 200; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + /* msg=Command0 but payload_case left as NOT_SET and sc0 left as NULL */ + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command0; + /* payload.payload_case deliberately left as SEC1_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc0 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc0 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc0 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd0 missing sc0 payload NULL dereference", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_missing_sc0_payload()); +} + +static esp_err_t test_cmd0_msg_with_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 201; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command0 but sc1 (not sc0) populated in the oneof */ + uint8_t verify_data[SEC1_PUBLIC_KEY_LEN]; + memset(verify_data, 0x55, sizeof(verify_data)); + + SessionCmd1 cmd1 = SESSION_CMD1__INIT; + cmd1.client_verify_data.data = verify_data; + cmd1.client_verify_data.len = sizeof(verify_data); + + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command0; /* CMD0 msg */ + payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC1; /* SC1 payload — mismatch */ + payload.sc1 = &cmd1; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD0/sc1 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD0/sc1 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd0 msg with sc1 payload oneof type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_msg_with_sc1_payload()); +} + +static esp_err_t test_cmd1_missing_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 202; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command1 but payload_case left as NOT_SET and sc1 left as NULL */ + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command1; + /* payload.payload_case deliberately left as SEC1_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc1 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc1 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc1 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd1 missing sc1 payload NULL dereference", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_missing_sc1_payload()); +} + +static esp_err_t test_cmd1_msg_with_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 203; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command1 but sc0 (not sc1) populated in the oneof */ + uint8_t pubkey[SEC1_PUBLIC_KEY_LEN]; + memset(pubkey, 0xAB, sizeof(pubkey)); + + SessionCmd0 cmd0 = SESSION_CMD0__INIT; + cmd0.client_pubkey.data = pubkey; + cmd0.client_pubkey.len = sizeof(pubkey); + + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command1; /* CMD1 msg */ + payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; /* SC0 payload — mismatch */ + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD1/sc0 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD1/sc0 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd1 msg with sc0 payload oneof type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_msg_with_sc0_payload()); +} + +static esp_err_t test_sec2_payload_with_sec1_version(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 204; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* SessionData with sec_ver=1 but proto_case=SEC2, carrying a Sec2 CMD0. + * Sec2's S2SessionCmd0 (56 bytes) is larger than Sec1's SessionCmd0 + * (40 bytes), so sec1 code reading the union as Sec1Payload causes + * type confusion. */ + uint8_t pubkey[384]; + memset(pubkey, 0xAB, sizeof(pubkey)); + uint8_t uname[] = "wifiprov"; + + S2SessionCmd0 s2cmd0 = S2_SESSION_CMD0__INIT; + s2cmd0.client_pubkey.data = pubkey; + s2cmd0.client_pubkey.len = sizeof(pubkey); + s2cmd0.client_username.data = uname; + s2cmd0.client_username.len = sizeof(uname) - 1; + + Sec2Payload sec2_payload = SEC2_PAYLOAD__INIT; + sec2_payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + sec2_payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; + sec2_payload.sc0 = &s2cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = SEC_SCHEME_VERSION__SecScheme1; /* sec_ver=1 — passes ver check */ + req.proto_case = SESSION_DATA__PROTO_SEC2; /* but SEC2 proto — mismatch */ + req.sec2 = &sec2_payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "sec2 payload with sec_ver=1 was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "sec2 payload with sec_ver=1 correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 sec2 payload with sec_ver=1 cross-scheme type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_sec2_payload_with_sec1_version()); +} + +#endif /* CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 */
components/protocomm/test_apps/main/test_security2.c+465 −0 added@@ -0,0 +1,465 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <esp_err.h> +#include <esp_log.h> +#include <unity.h> +#include <protocomm.h> +#include <protocomm_security.h> +#include <protocomm_security2.h> +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "sec2.pb-c.h" + +#if CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 + +static const char *TAG = "test_security2"; + +/* + * Pre-computed salt and verifier for username="wifiprov", password="abcd1234". + * Identical to the vectors used in test_srp.c. + */ +static const char sec2_salt[] = { + 0x03, 0x6e, 0xe0, 0xc7, 0xbc, 0xb9, 0xed, 0xa8, + 0x4c, 0x9e, 0xac, 0x97, 0xd9, 0x3d, 0xec, 0xf4 +}; + +static const char sec2_verifier[] = { + 0x7c, 0x7c, 0x85, 0x47, 0x65, 0x08, 0x94, 0x6d, 0xd6, 0x36, 0xaf, 0x37, 0xd7, 0xe8, 0x91, 0x43, + 0x78, 0xcf, 0xfd, 0x61, 0x6c, 0x59, 0xd2, 0xf8, 0x39, 0x08, 0x12, 0x72, 0x38, 0xde, 0x9e, 0x24, + 0xa4, 0x70, 0x26, 0x1c, 0xdf, 0xa9, 0x03, 0xc2, 0xb2, 0x70, 0xe7, 0xb1, 0x32, 0x24, 0xda, 0x11, + 0x1d, 0x97, 0x18, 0xdc, 0x60, 0x72, 0x08, 0xcc, 0x9a, 0xc9, 0x0c, 0x48, 0x27, 0xe2, 0xae, 0x89, + 0xaa, 0x16, 0x25, 0xb8, 0x04, 0xd2, 0x1a, 0x9b, 0x3a, 0x8f, 0x37, 0xf6, 0xe4, 0x3a, 0x71, 0x2e, + 0xe1, 0x27, 0x86, 0x6e, 0xad, 0xce, 0x28, 0xff, 0x54, 0x46, 0x60, 0x1f, 0xb9, 0x96, 0x87, 0xdc, + 0x57, 0x40, 0xa7, 0xd4, 0x6c, 0xc9, 0x77, 0x54, 0xdc, 0x16, 0x82, 0xf0, 0xed, 0x35, 0x6a, 0xc4, + 0x70, 0xad, 0x3d, 0x90, 0xb5, 0x81, 0x94, 0x70, 0xd7, 0xbc, 0x65, 0xb2, 0xd5, 0x18, 0xe0, 0x2e, + 0xc3, 0xa5, 0xf9, 0x68, 0xdd, 0x64, 0x7b, 0xb8, 0xb7, 0x3c, 0x9c, 0xfc, 0x00, 0xd8, 0x71, 0x7e, + 0xb7, 0x9a, 0x7c, 0xb1, 0xb7, 0xc2, 0xc3, 0x18, 0x34, 0x29, 0x32, 0x43, 0x3e, 0x00, 0x99, 0xe9, + 0x82, 0x94, 0xe3, 0xd8, 0x2a, 0xb0, 0x96, 0x29, 0xb7, 0xdf, 0x0e, 0x5f, 0x08, 0x33, 0x40, 0x76, + 0x52, 0x91, 0x32, 0x00, 0x9f, 0x97, 0x2c, 0x89, 0x6c, 0x39, 0x1e, 0xc8, 0x28, 0x05, 0x44, 0x17, + 0x3f, 0x68, 0x02, 0x8a, 0x9f, 0x44, 0x61, 0xd1, 0xf5, 0xa1, 0x7e, 0x5a, 0x70, 0xd2, 0xc7, 0x23, + 0x81, 0xcb, 0x38, 0x68, 0xe4, 0x2c, 0x20, 0xbc, 0x40, 0x57, 0x76, 0x17, 0xbd, 0x08, 0xb8, 0x96, + 0xbc, 0x26, 0xeb, 0x32, 0x46, 0x69, 0x35, 0x05, 0x8c, 0x15, 0x70, 0xd9, 0x1b, 0xe9, 0xbe, 0xcc, + 0xa9, 0x38, 0xa6, 0x67, 0xf0, 0xad, 0x50, 0x13, 0x19, 0x72, 0x64, 0xbf, 0x52, 0xc2, 0x34, 0xe2, + 0x1b, 0x11, 0x79, 0x74, 0x72, 0xbd, 0x34, 0x5b, 0xb1, 0xe2, 0xfd, 0x66, 0x73, 0xfe, 0x71, 0x64, + 0x74, 0xd0, 0x4e, 0xbc, 0x51, 0x24, 0x19, 0x40, 0x87, 0x0e, 0x92, 0x40, 0xe6, 0x21, 0xe7, 0x2d, + 0x4e, 0x37, 0x76, 0x2f, 0x2e, 0xe2, 0x68, 0xc7, 0x89, 0xe8, 0x32, 0x13, 0x42, 0x06, 0x84, 0x84, + 0x53, 0x4a, 0xb3, 0x0c, 0x1b, 0x4c, 0x8d, 0x1c, 0x51, 0x97, 0x19, 0xab, 0xae, 0x77, 0xff, 0xdb, + 0xec, 0xf0, 0x10, 0x95, 0x34, 0x33, 0x6b, 0xcb, 0x3e, 0x84, 0x0f, 0xb9, 0xd8, 0x5f, 0xb8, 0xa0, + 0xb8, 0x55, 0x53, 0x3e, 0x70, 0xf7, 0x18, 0xf5, 0xce, 0x7b, 0x4e, 0xbf, 0x27, 0xce, 0xce, 0xa8, + 0xb3, 0xbe, 0x40, 0xc5, 0xc5, 0x32, 0x29, 0x3e, 0x71, 0x64, 0x9e, 0xde, 0x8c, 0xf6, 0x75, 0xa1, + 0xe6, 0xf6, 0x53, 0xc8, 0x31, 0xa8, 0x78, 0xde, 0x50, 0x40, 0xf7, 0x62, 0xde, 0x36, 0xb2, 0xba +}; + +/* Must match PUBLIC_KEY_LEN in security2.c */ +#define SEC2_PUBLIC_KEY_LEN 384 + +static esp_err_t test_cmd0_oversized_username(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 100; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const size_t username_len = (size_t)UINT16_MAX + 2; + uint8_t *username_buf = calloc(1, username_len); + if (!username_buf) { + ESP_LOGE(TAG, "Cannot allocate %zu-byte username buffer — skipping test", username_len); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + memset(username_buf, 'A', username_len); + + uint8_t client_pubkey[SEC2_PUBLIC_KEY_LEN]; + memset(client_pubkey, 0xAB, sizeof(client_pubkey)); + + S2SessionCmd0 cmd0 = S2_SESSION_CMD0__INIT; + cmd0.client_username.data = username_buf; + cmd0.client_username.len = username_len; + cmd0.client_pubkey.data = client_pubkey; + cmd0.client_pubkey.len = SEC2_PUBLIC_KEY_LEN; + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + ESP_LOGE(TAG, "Cannot allocate %zu-byte packed buffer — skipping test", packed_len); + free(username_buf); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + free(username_buf); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Oversized username was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Oversized username correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 oversized username uint16_t truncation heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_oversized_username()); +} + +static esp_err_t test_cmd0_missing_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 101; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + /* payload.payload_case deliberately left as SEC2_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc0 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + ESP_LOGE(TAG, "Cannot allocate packed buffer"); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc0 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc0 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 missing sc0 payload NULL dereference", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_missing_sc0_payload()); +} + +static esp_err_t test_cmd0_msg_with_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 102; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* Build a Sec2Payload with msg=CMD0 but sc1 (not sc0) in the oneof. */ + uint8_t proof[64]; + memset(proof, 0x33, sizeof(proof)); + + S2SessionCmd1 cmd1 = S2_SESSION_CMD1__INIT; + cmd1.client_proof.data = proof; + cmd1.client_proof.len = sizeof(proof); + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; /* CMD0 msg */ + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC1; /* SC1 payload — mismatch */ + payload.sc1 = &cmd1; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD0/sc1 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD0/sc1 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 msg with sc1 payload oneof type confusion heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_msg_with_sc1_payload()); +} + +static esp_err_t test_cmd1_msg_with_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 103; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* Build a Sec2Payload with msg=CMD1 but sc0 (not sc1) in the oneof. */ + uint8_t pubkey[SEC2_PUBLIC_KEY_LEN]; + memset(pubkey, 0xAB, sizeof(pubkey)); + uint8_t uname[] = "alice"; + + S2SessionCmd0 cmd0 = S2_SESSION_CMD0__INIT; + cmd0.client_username.data = uname; + cmd0.client_username.len = sizeof(uname) - 1; + cmd0.client_pubkey.data = pubkey; + cmd0.client_pubkey.len = sizeof(pubkey); + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command1; /* CMD1 msg */ + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; /* SC0 payload — mismatch */ + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD1/sc0 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD1/sc0 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd1 msg with sc0 payload oneof type confusion", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_msg_with_sc0_payload()); +} + +static esp_err_t test_sec1_payload_with_sec2_version(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 104; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + uint8_t pubkey[32]; + memset(pubkey, 0xAB, sizeof(pubkey)); + + SessionCmd0 sec1_cmd0 = SESSION_CMD0__INIT; + sec1_cmd0.client_pubkey.data = pubkey; + sec1_cmd0.client_pubkey.len = sizeof(pubkey); + + Sec1Payload sec1_payload = SEC1_PAYLOAD__INIT; + sec1_payload.msg = SEC1_MSG_TYPE__Session_Command0; + sec1_payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; + sec1_payload.sc0 = &sec1_cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = SEC_SCHEME_VERSION__SecScheme2; /* sec_ver=2 — passes ver check */ + req.proto_case = SESSION_DATA__PROTO_SEC1; /* but SEC1 proto — mismatch */ + req.sec1 = &sec1_payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "sec1 payload with sec_ver=2 was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "sec1 payload with sec_ver=2 correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 sec1 payload with sec_ver=2 cross-scheme type confusion heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_sec1_payload_with_sec2_version()); +} + +#endif /* CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 */
components/protocomm/test_apps/sdkconfig.defaults+5 −0 modified@@ -7,3 +7,8 @@ CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y CONFIG_COMPILER_STACK_CHECK=y CONFIG_ESP_TASK_WDT_EN=n + +# Enable all protocomm security versions for testing +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y
71eb2dbe6aaefix(protocomm): fixes potential issues that can lead to crash during device provisioning
6 files changed · +882 −6
components/mbedtls/port/include/mbedtls/esp_config.h+2 −0 modified@@ -27,7 +27,9 @@ #include "sdkconfig.h" #include "mbedtls/mbedtls_config.h" +#ifndef CONFIG_IDF_TARGET_LINUX #include "soc/soc_caps.h" +#endif // !CONFIG_IDF_TARGET_LINUX /** * \name SECTION: System support
components/protocomm/src/security/security1.c+15 −2 modified@@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -102,7 +102,7 @@ static esp_err_t handle_session_command1(session_t *cur_session, } /* Validate client verifier data before processing */ - if (!in || !in->sc1 || + if (!in || in->payload_case != SEC1_PAYLOAD__PAYLOAD_SC1 || !in->sc1 || in->sc1->client_verify_data.data == NULL || in->sc1->client_verify_data.len != PUBLIC_KEY_LEN) { ESP_LOGE(TAG, "Invalid client verifier (ptr=%p len=%d)", @@ -229,6 +229,14 @@ static esp_err_t handle_session_command0(session_t *cur_session, sec1_new_session(cur_session, session_id); } + if (in->payload_case != SEC1_PAYLOAD__PAYLOAD_SC0 || !in->sc0) { + ESP_LOGE(TAG, "Missing or mismatched sc0 payload in session command0"); + if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { + ESP_LOGE(TAG, "Failed to post secure session invalid security params event"); + } + return ESP_ERR_INVALID_ARG; + } + if (in->sc0->client_pubkey.len != PUBLIC_KEY_LEN) { ESP_LOGE(TAG, "Invalid public key length"); if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { @@ -593,6 +601,11 @@ static esp_err_t sec1_req_handler(protocomm_security_handle_t handle, session_data__free_unpacked(req, NULL); return ESP_ERR_INVALID_ARG; } + if (req->proto_case != SESSION_DATA__PROTO_SEC1) { + ESP_LOGE(TAG, "Security scheme mismatch. Closing connection"); + session_data__free_unpacked(req, NULL); + return ESP_ERR_INVALID_ARG; + } session_data__init(&resp); ret = sec1_session_setup(cur_session, session_id, req, &resp, (protocomm_security1_params_t *) sec_params);
components/protocomm/src/security/security2.c+19 −4 modified@@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -91,6 +91,11 @@ static esp_err_t handle_session_command0(session_t *cur_session, ESP_LOGD(TAG, "Request to handle setup0_command"); Sec2Payload *in = (Sec2Payload *) req->sec2; + if (in->payload_case != SEC2_PAYLOAD__PAYLOAD_SC0 || !in->sc0) { + ESP_LOGE(TAG, "Missing or mismatched sc0 payload in session command0"); + return ESP_ERR_INVALID_ARG; + } + if (cur_session->state != SESSION_STATE_CMD0) { ESP_LOGW(TAG, "Invalid state of session %d (expected %d). Restarting session.", SESSION_STATE_CMD0, cur_session->state); @@ -105,8 +110,8 @@ static esp_err_t handle_session_command0(session_t *cur_session, return ESP_ERR_INVALID_ARG; } - if (in->sc0->client_username.len <= 0) { - ESP_LOGE(TAG, "Invalid username"); + if (in->sc0->client_username.len == 0 || in->sc0->client_username.len > UINT16_MAX) { + ESP_LOGE(TAG, "Invalid username length (%zu)", in->sc0->client_username.len); if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post secure session invalid security params event"); } @@ -202,7 +207,7 @@ static esp_err_t handle_session_command0(session_t *cur_session, free(out_resp); return ESP_ERR_NO_MEM; } - memcpy(cur_session->username, in->sc0->client_username.data, in->sc0->client_username.len); + memcpy(cur_session->username, in->sc0->client_username.data, cur_session->username_len); resp->sec_ver = SEC_SCHEME_VERSION__SecScheme2; resp->proto_case = SESSION_DATA__PROTO_SEC2; @@ -222,6 +227,11 @@ static esp_err_t handle_session_command1(session_t *cur_session, Sec2Payload *in = (Sec2Payload *) req->sec2; int mbed_err = -0x0001; + if (in->payload_case != SEC2_PAYLOAD__PAYLOAD_SC1 || !in->sc1) { + ESP_LOGE(TAG, "Missing or mismatched sc1 payload in session command1"); + return ESP_ERR_INVALID_ARG; + } + if (cur_session->state != SESSION_STATE_CMD1) { ESP_LOGE(TAG, "Invalid state of session %d (expected %d)", SESSION_STATE_CMD1, cur_session->state); return ESP_ERR_INVALID_STATE; @@ -581,6 +591,11 @@ static esp_err_t sec2_req_handler(protocomm_security_handle_t handle, session_data__free_unpacked(req, NULL); return ESP_ERR_INVALID_ARG; } + if (req->proto_case != SESSION_DATA__PROTO_SEC2) { + ESP_LOGE(TAG, "Security scheme mismatch. Closing connection"); + session_data__free_unpacked(req, NULL); + return ESP_ERR_INVALID_ARG; + } session_data__init(&resp); ret = sec2_session_setup(cur_session, session_id, req, &resp, (protocomm_security2_params_t *) sec_params);
components/protocomm/test_apps/main/test_security1.c+376 −0 added@@ -0,0 +1,376 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <esp_err.h> +#include <esp_log.h> +#include <unity.h> +#include <protocomm.h> +#include <protocomm_security.h> +#include <protocomm_security1.h> +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "sec2.pb-c.h" + +#if CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 + +static const char *TAG = "test_security1"; + +/* Must match PUBLIC_KEY_LEN in security1.c */ +#define SEC1_PUBLIC_KEY_LEN 32 + +static esp_err_t test_cmd0_missing_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 200; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + /* msg=Command0 but payload_case left as NOT_SET and sc0 left as NULL */ + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command0; + /* payload.payload_case deliberately left as SEC1_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc0 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc0 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc0 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd0 missing sc0 payload NULL dereference", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_missing_sc0_payload()); +} + +static esp_err_t test_cmd0_msg_with_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 201; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command0 but sc1 (not sc0) populated in the oneof */ + uint8_t verify_data[SEC1_PUBLIC_KEY_LEN]; + memset(verify_data, 0x55, sizeof(verify_data)); + + SessionCmd1 cmd1 = SESSION_CMD1__INIT; + cmd1.client_verify_data.data = verify_data; + cmd1.client_verify_data.len = sizeof(verify_data); + + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command0; /* CMD0 msg */ + payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC1; /* SC1 payload — mismatch */ + payload.sc1 = &cmd1; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD0/sc1 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD0/sc1 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd0 msg with sc1 payload oneof type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_msg_with_sc1_payload()); +} + +static esp_err_t test_cmd1_missing_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 202; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command1 but payload_case left as NOT_SET and sc1 left as NULL */ + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command1; + /* payload.payload_case deliberately left as SEC1_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc1 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc1 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc1 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd1 missing sc1 payload NULL dereference", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_missing_sc1_payload()); +} + +static esp_err_t test_cmd1_msg_with_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 203; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command1 but sc0 (not sc1) populated in the oneof */ + uint8_t pubkey[SEC1_PUBLIC_KEY_LEN]; + memset(pubkey, 0xAB, sizeof(pubkey)); + + SessionCmd0 cmd0 = SESSION_CMD0__INIT; + cmd0.client_pubkey.data = pubkey; + cmd0.client_pubkey.len = sizeof(pubkey); + + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command1; /* CMD1 msg */ + payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; /* SC0 payload — mismatch */ + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD1/sc0 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD1/sc0 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd1 msg with sc0 payload oneof type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_msg_with_sc0_payload()); +} + +static esp_err_t test_sec2_payload_with_sec1_version(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 204; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* SessionData with sec_ver=1 but proto_case=SEC2, carrying a Sec2 CMD0. + * Sec2's S2SessionCmd0 (56 bytes) is larger than Sec1's SessionCmd0 + * (40 bytes), so sec1 code reading the union as Sec1Payload causes + * type confusion. */ + uint8_t pubkey[384]; + memset(pubkey, 0xAB, sizeof(pubkey)); + uint8_t uname[] = "wifiprov"; + + S2SessionCmd0 s2cmd0 = S2_SESSION_CMD0__INIT; + s2cmd0.client_pubkey.data = pubkey; + s2cmd0.client_pubkey.len = sizeof(pubkey); + s2cmd0.client_username.data = uname; + s2cmd0.client_username.len = sizeof(uname) - 1; + + Sec2Payload sec2_payload = SEC2_PAYLOAD__INIT; + sec2_payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + sec2_payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; + sec2_payload.sc0 = &s2cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = SEC_SCHEME_VERSION__SecScheme1; /* sec_ver=1 — passes ver check */ + req.proto_case = SESSION_DATA__PROTO_SEC2; /* but SEC2 proto — mismatch */ + req.sec2 = &sec2_payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "sec2 payload with sec_ver=1 was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "sec2 payload with sec_ver=1 correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 sec2 payload with sec_ver=1 cross-scheme type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_sec2_payload_with_sec1_version()); +} + +#endif /* CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 */
components/protocomm/test_apps/main/test_security2.c+465 −0 added@@ -0,0 +1,465 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <esp_err.h> +#include <esp_log.h> +#include <unity.h> +#include <protocomm.h> +#include <protocomm_security.h> +#include <protocomm_security2.h> +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "sec2.pb-c.h" + +#if CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 + +static const char *TAG = "test_security2"; + +/* + * Pre-computed salt and verifier for username="wifiprov", password="abcd1234". + * Identical to the vectors used in test_srp.c. + */ +static const char sec2_salt[] = { + 0x03, 0x6e, 0xe0, 0xc7, 0xbc, 0xb9, 0xed, 0xa8, + 0x4c, 0x9e, 0xac, 0x97, 0xd9, 0x3d, 0xec, 0xf4 +}; + +static const char sec2_verifier[] = { + 0x7c, 0x7c, 0x85, 0x47, 0x65, 0x08, 0x94, 0x6d, 0xd6, 0x36, 0xaf, 0x37, 0xd7, 0xe8, 0x91, 0x43, + 0x78, 0xcf, 0xfd, 0x61, 0x6c, 0x59, 0xd2, 0xf8, 0x39, 0x08, 0x12, 0x72, 0x38, 0xde, 0x9e, 0x24, + 0xa4, 0x70, 0x26, 0x1c, 0xdf, 0xa9, 0x03, 0xc2, 0xb2, 0x70, 0xe7, 0xb1, 0x32, 0x24, 0xda, 0x11, + 0x1d, 0x97, 0x18, 0xdc, 0x60, 0x72, 0x08, 0xcc, 0x9a, 0xc9, 0x0c, 0x48, 0x27, 0xe2, 0xae, 0x89, + 0xaa, 0x16, 0x25, 0xb8, 0x04, 0xd2, 0x1a, 0x9b, 0x3a, 0x8f, 0x37, 0xf6, 0xe4, 0x3a, 0x71, 0x2e, + 0xe1, 0x27, 0x86, 0x6e, 0xad, 0xce, 0x28, 0xff, 0x54, 0x46, 0x60, 0x1f, 0xb9, 0x96, 0x87, 0xdc, + 0x57, 0x40, 0xa7, 0xd4, 0x6c, 0xc9, 0x77, 0x54, 0xdc, 0x16, 0x82, 0xf0, 0xed, 0x35, 0x6a, 0xc4, + 0x70, 0xad, 0x3d, 0x90, 0xb5, 0x81, 0x94, 0x70, 0xd7, 0xbc, 0x65, 0xb2, 0xd5, 0x18, 0xe0, 0x2e, + 0xc3, 0xa5, 0xf9, 0x68, 0xdd, 0x64, 0x7b, 0xb8, 0xb7, 0x3c, 0x9c, 0xfc, 0x00, 0xd8, 0x71, 0x7e, + 0xb7, 0x9a, 0x7c, 0xb1, 0xb7, 0xc2, 0xc3, 0x18, 0x34, 0x29, 0x32, 0x43, 0x3e, 0x00, 0x99, 0xe9, + 0x82, 0x94, 0xe3, 0xd8, 0x2a, 0xb0, 0x96, 0x29, 0xb7, 0xdf, 0x0e, 0x5f, 0x08, 0x33, 0x40, 0x76, + 0x52, 0x91, 0x32, 0x00, 0x9f, 0x97, 0x2c, 0x89, 0x6c, 0x39, 0x1e, 0xc8, 0x28, 0x05, 0x44, 0x17, + 0x3f, 0x68, 0x02, 0x8a, 0x9f, 0x44, 0x61, 0xd1, 0xf5, 0xa1, 0x7e, 0x5a, 0x70, 0xd2, 0xc7, 0x23, + 0x81, 0xcb, 0x38, 0x68, 0xe4, 0x2c, 0x20, 0xbc, 0x40, 0x57, 0x76, 0x17, 0xbd, 0x08, 0xb8, 0x96, + 0xbc, 0x26, 0xeb, 0x32, 0x46, 0x69, 0x35, 0x05, 0x8c, 0x15, 0x70, 0xd9, 0x1b, 0xe9, 0xbe, 0xcc, + 0xa9, 0x38, 0xa6, 0x67, 0xf0, 0xad, 0x50, 0x13, 0x19, 0x72, 0x64, 0xbf, 0x52, 0xc2, 0x34, 0xe2, + 0x1b, 0x11, 0x79, 0x74, 0x72, 0xbd, 0x34, 0x5b, 0xb1, 0xe2, 0xfd, 0x66, 0x73, 0xfe, 0x71, 0x64, + 0x74, 0xd0, 0x4e, 0xbc, 0x51, 0x24, 0x19, 0x40, 0x87, 0x0e, 0x92, 0x40, 0xe6, 0x21, 0xe7, 0x2d, + 0x4e, 0x37, 0x76, 0x2f, 0x2e, 0xe2, 0x68, 0xc7, 0x89, 0xe8, 0x32, 0x13, 0x42, 0x06, 0x84, 0x84, + 0x53, 0x4a, 0xb3, 0x0c, 0x1b, 0x4c, 0x8d, 0x1c, 0x51, 0x97, 0x19, 0xab, 0xae, 0x77, 0xff, 0xdb, + 0xec, 0xf0, 0x10, 0x95, 0x34, 0x33, 0x6b, 0xcb, 0x3e, 0x84, 0x0f, 0xb9, 0xd8, 0x5f, 0xb8, 0xa0, + 0xb8, 0x55, 0x53, 0x3e, 0x70, 0xf7, 0x18, 0xf5, 0xce, 0x7b, 0x4e, 0xbf, 0x27, 0xce, 0xce, 0xa8, + 0xb3, 0xbe, 0x40, 0xc5, 0xc5, 0x32, 0x29, 0x3e, 0x71, 0x64, 0x9e, 0xde, 0x8c, 0xf6, 0x75, 0xa1, + 0xe6, 0xf6, 0x53, 0xc8, 0x31, 0xa8, 0x78, 0xde, 0x50, 0x40, 0xf7, 0x62, 0xde, 0x36, 0xb2, 0xba +}; + +/* Must match PUBLIC_KEY_LEN in security2.c */ +#define SEC2_PUBLIC_KEY_LEN 384 + +static esp_err_t test_cmd0_oversized_username(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 100; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const size_t username_len = (size_t)UINT16_MAX + 2; + uint8_t *username_buf = calloc(1, username_len); + if (!username_buf) { + ESP_LOGE(TAG, "Cannot allocate %zu-byte username buffer — skipping test", username_len); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + memset(username_buf, 'A', username_len); + + uint8_t client_pubkey[SEC2_PUBLIC_KEY_LEN]; + memset(client_pubkey, 0xAB, sizeof(client_pubkey)); + + S2SessionCmd0 cmd0 = S2_SESSION_CMD0__INIT; + cmd0.client_username.data = username_buf; + cmd0.client_username.len = username_len; + cmd0.client_pubkey.data = client_pubkey; + cmd0.client_pubkey.len = SEC2_PUBLIC_KEY_LEN; + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + ESP_LOGE(TAG, "Cannot allocate %zu-byte packed buffer — skipping test", packed_len); + free(username_buf); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + free(username_buf); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Oversized username was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Oversized username correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 oversized username uint16_t truncation heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_oversized_username()); +} + +static esp_err_t test_cmd0_missing_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 101; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + /* payload.payload_case deliberately left as SEC2_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc0 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + ESP_LOGE(TAG, "Cannot allocate packed buffer"); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc0 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc0 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 missing sc0 payload NULL dereference", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_missing_sc0_payload()); +} + +static esp_err_t test_cmd0_msg_with_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 102; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* Build a Sec2Payload with msg=CMD0 but sc1 (not sc0) in the oneof. */ + uint8_t proof[64]; + memset(proof, 0x33, sizeof(proof)); + + S2SessionCmd1 cmd1 = S2_SESSION_CMD1__INIT; + cmd1.client_proof.data = proof; + cmd1.client_proof.len = sizeof(proof); + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; /* CMD0 msg */ + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC1; /* SC1 payload — mismatch */ + payload.sc1 = &cmd1; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD0/sc1 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD0/sc1 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 msg with sc1 payload oneof type confusion heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_msg_with_sc1_payload()); +} + +static esp_err_t test_cmd1_msg_with_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 103; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* Build a Sec2Payload with msg=CMD1 but sc0 (not sc1) in the oneof. */ + uint8_t pubkey[SEC2_PUBLIC_KEY_LEN]; + memset(pubkey, 0xAB, sizeof(pubkey)); + uint8_t uname[] = "alice"; + + S2SessionCmd0 cmd0 = S2_SESSION_CMD0__INIT; + cmd0.client_username.data = uname; + cmd0.client_username.len = sizeof(uname) - 1; + cmd0.client_pubkey.data = pubkey; + cmd0.client_pubkey.len = sizeof(pubkey); + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command1; /* CMD1 msg */ + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; /* SC0 payload — mismatch */ + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD1/sc0 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD1/sc0 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd1 msg with sc0 payload oneof type confusion", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_msg_with_sc0_payload()); +} + +static esp_err_t test_sec1_payload_with_sec2_version(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 104; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + uint8_t pubkey[32]; + memset(pubkey, 0xAB, sizeof(pubkey)); + + SessionCmd0 sec1_cmd0 = SESSION_CMD0__INIT; + sec1_cmd0.client_pubkey.data = pubkey; + sec1_cmd0.client_pubkey.len = sizeof(pubkey); + + Sec1Payload sec1_payload = SEC1_PAYLOAD__INIT; + sec1_payload.msg = SEC1_MSG_TYPE__Session_Command0; + sec1_payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; + sec1_payload.sc0 = &sec1_cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = SEC_SCHEME_VERSION__SecScheme2; /* sec_ver=2 — passes ver check */ + req.proto_case = SESSION_DATA__PROTO_SEC1; /* but SEC1 proto — mismatch */ + req.sec1 = &sec1_payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "sec1 payload with sec_ver=2 was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "sec1 payload with sec_ver=2 correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 sec1 payload with sec_ver=2 cross-scheme type confusion heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_sec1_payload_with_sec2_version()); +} + +#endif /* CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 */
components/protocomm/test_apps/sdkconfig.defaults+5 −0 modified@@ -7,3 +7,8 @@ CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y CONFIG_COMPILER_STACK_CHECK=y CONFIG_ESP_TASK_WDT_EN=n + +# Enable all protocomm security versions for testing +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y
9b4cacf9cbc6fix(protocomm): fixes potential issues that can lead to crash during device provisioning
6 files changed · +877 −8
components/mbedtls/port/include/mbedtls/esp_config.h+1 −2 modified@@ -31,10 +31,9 @@ #if (defined(MBEDTLS_MAJOR_VERSION) && (MBEDTLS_MAJOR_VERSION < 4)) #include "mbedtls/mbedtls_config.h" #endif // MBEDTLS_MAJOR_VERSION < 4 +#ifndef CONFIG_IDF_TARGET_LINUX #include "soc/soc_caps.h" - -#ifndef CONFIG_IDF_TARGET_LINUX #undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY #define MBEDTLS_PSA_DRIVER_GET_ENTROPY #define MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG
components/protocomm/src/security/security1.c+15 −2 modified@@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -91,7 +91,7 @@ static esp_err_t handle_session_command1(session_t *cur_session, } /* Validate client verifier data before processing */ - if (!in || !in->sc1 || + if (!in || in->payload_case != SEC1_PAYLOAD__PAYLOAD_SC1 || !in->sc1 || in->sc1->client_verify_data.data == NULL || in->sc1->client_verify_data.len != PUBLIC_KEY_LEN) { ESP_LOGE(TAG, "Invalid client verifier (ptr=%p len=%d)", @@ -237,6 +237,14 @@ static esp_err_t handle_session_command0(session_t *cur_session, sec1_new_session(cur_session, session_id); } + if (in->payload_case != SEC1_PAYLOAD__PAYLOAD_SC0 || !in->sc0) { + ESP_LOGE(TAG, "Missing or mismatched sc0 payload in session command0"); + if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { + ESP_LOGE(TAG, "Failed to post secure session invalid security params event"); + } + return ESP_ERR_INVALID_ARG; + } + if (in->sc0->client_pubkey.len != PUBLIC_KEY_LEN) { ESP_LOGE(TAG, "Invalid public key length"); if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { @@ -584,6 +592,11 @@ static esp_err_t sec1_req_handler(protocomm_security_handle_t handle, session_data__free_unpacked(req, NULL); return ESP_ERR_INVALID_ARG; } + if (req->proto_case != SESSION_DATA__PROTO_SEC1) { + ESP_LOGE(TAG, "Security scheme mismatch. Closing connection"); + session_data__free_unpacked(req, NULL); + return ESP_ERR_INVALID_ARG; + } session_data__init(&resp); ret = sec1_session_setup(cur_session, session_id, req, &resp, (protocomm_security1_params_t *) sec_params);
components/protocomm/src/security/security2.c+19 −4 modified@@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -89,6 +89,11 @@ static esp_err_t handle_session_command0(session_t *cur_session, ESP_LOGD(TAG, "Request to handle setup0_command"); Sec2Payload *in = (Sec2Payload *) req->sec2; + if (in->payload_case != SEC2_PAYLOAD__PAYLOAD_SC0 || !in->sc0) { + ESP_LOGE(TAG, "Missing or mismatched sc0 payload in session command0"); + return ESP_ERR_INVALID_ARG; + } + if (cur_session->state != SESSION_STATE_CMD0) { ESP_LOGW(TAG, "Invalid state of session %d (expected %d). Restarting session.", SESSION_STATE_CMD0, cur_session->state); @@ -103,8 +108,8 @@ static esp_err_t handle_session_command0(session_t *cur_session, return ESP_ERR_INVALID_ARG; } - if (in->sc0->client_username.len <= 0) { - ESP_LOGE(TAG, "Invalid username"); + if (in->sc0->client_username.len == 0 || in->sc0->client_username.len > UINT16_MAX) { + ESP_LOGE(TAG, "Invalid username length (%zu)", in->sc0->client_username.len); if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post secure session invalid security params event"); } @@ -195,7 +200,7 @@ static esp_err_t handle_session_command0(session_t *cur_session, free(out_resp); return ESP_ERR_NO_MEM; } - memcpy(cur_session->username, in->sc0->client_username.data, in->sc0->client_username.len); + memcpy(cur_session->username, in->sc0->client_username.data, cur_session->username_len); resp->sec_ver = SEC_SCHEME_VERSION__SecScheme2; resp->proto_case = SESSION_DATA__PROTO_SEC2; @@ -214,6 +219,11 @@ static esp_err_t handle_session_command1(session_t *cur_session, ESP_LOGD(TAG, "Request to handle setup1_command"); Sec2Payload *in = (Sec2Payload *) req->sec2; + if (in->payload_case != SEC2_PAYLOAD__PAYLOAD_SC1 || !in->sc1) { + ESP_LOGE(TAG, "Missing or mismatched sc1 payload in session command1"); + return ESP_ERR_INVALID_ARG; + } + if (cur_session->state != SESSION_STATE_CMD1) { ESP_LOGE(TAG, "Invalid state of session %d (expected %d)", SESSION_STATE_CMD1, cur_session->state); return ESP_ERR_INVALID_STATE; @@ -598,6 +608,11 @@ static esp_err_t sec2_req_handler(protocomm_security_handle_t handle, session_data__free_unpacked(req, NULL); return ESP_ERR_INVALID_ARG; } + if (req->proto_case != SESSION_DATA__PROTO_SEC2) { + ESP_LOGE(TAG, "Security scheme mismatch. Closing connection"); + session_data__free_unpacked(req, NULL); + return ESP_ERR_INVALID_ARG; + } session_data__init(&resp); ret = sec2_session_setup(cur_session, session_id, req, &resp, (protocomm_security2_params_t *) sec_params);
components/protocomm/test_apps/main/test_security1.c+376 −0 added@@ -0,0 +1,376 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <esp_err.h> +#include <esp_log.h> +#include <unity.h> +#include <protocomm.h> +#include <protocomm_security.h> +#include <protocomm_security1.h> +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "sec2.pb-c.h" + +#if CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 + +static const char *TAG = "test_security1"; + +/* Must match PUBLIC_KEY_LEN in security1.c */ +#define SEC1_PUBLIC_KEY_LEN 32 + +static esp_err_t test_cmd0_missing_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 200; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + /* msg=Command0 but payload_case left as NOT_SET and sc0 left as NULL */ + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command0; + /* payload.payload_case deliberately left as SEC1_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc0 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc0 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc0 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd0 missing sc0 payload NULL dereference", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_missing_sc0_payload()); +} + +static esp_err_t test_cmd0_msg_with_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 201; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command0 but sc1 (not sc0) populated in the oneof */ + uint8_t verify_data[SEC1_PUBLIC_KEY_LEN]; + memset(verify_data, 0x55, sizeof(verify_data)); + + SessionCmd1 cmd1 = SESSION_CMD1__INIT; + cmd1.client_verify_data.data = verify_data; + cmd1.client_verify_data.len = sizeof(verify_data); + + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command0; /* CMD0 msg */ + payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC1; /* SC1 payload — mismatch */ + payload.sc1 = &cmd1; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD0/sc1 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD0/sc1 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd0 msg with sc1 payload oneof type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_msg_with_sc1_payload()); +} + +static esp_err_t test_cmd1_missing_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 202; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command1 but payload_case left as NOT_SET and sc1 left as NULL */ + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command1; + /* payload.payload_case deliberately left as SEC1_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc1 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc1 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc1 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd1 missing sc1 payload NULL dereference", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_missing_sc1_payload()); +} + +static esp_err_t test_cmd1_msg_with_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 203; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command1 but sc0 (not sc1) populated in the oneof */ + uint8_t pubkey[SEC1_PUBLIC_KEY_LEN]; + memset(pubkey, 0xAB, sizeof(pubkey)); + + SessionCmd0 cmd0 = SESSION_CMD0__INIT; + cmd0.client_pubkey.data = pubkey; + cmd0.client_pubkey.len = sizeof(pubkey); + + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command1; /* CMD1 msg */ + payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; /* SC0 payload — mismatch */ + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD1/sc0 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD1/sc0 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd1 msg with sc0 payload oneof type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_msg_with_sc0_payload()); +} + +static esp_err_t test_sec2_payload_with_sec1_version(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 204; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* SessionData with sec_ver=1 but proto_case=SEC2, carrying a Sec2 CMD0. + * Sec2's S2SessionCmd0 (56 bytes) is larger than Sec1's SessionCmd0 + * (40 bytes), so sec1 code reading the union as Sec1Payload causes + * type confusion. */ + uint8_t pubkey[384]; + memset(pubkey, 0xAB, sizeof(pubkey)); + uint8_t uname[] = "wifiprov"; + + S2SessionCmd0 s2cmd0 = S2_SESSION_CMD0__INIT; + s2cmd0.client_pubkey.data = pubkey; + s2cmd0.client_pubkey.len = sizeof(pubkey); + s2cmd0.client_username.data = uname; + s2cmd0.client_username.len = sizeof(uname) - 1; + + Sec2Payload sec2_payload = SEC2_PAYLOAD__INIT; + sec2_payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + sec2_payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; + sec2_payload.sc0 = &s2cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = SEC_SCHEME_VERSION__SecScheme1; /* sec_ver=1 — passes ver check */ + req.proto_case = SESSION_DATA__PROTO_SEC2; /* but SEC2 proto — mismatch */ + req.sec2 = &sec2_payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "sec2 payload with sec_ver=1 was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "sec2 payload with sec_ver=1 correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 sec2 payload with sec_ver=1 cross-scheme type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_sec2_payload_with_sec1_version()); +} + +#endif /* CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 */
components/protocomm/test_apps/main/test_security2.c+465 −0 added@@ -0,0 +1,465 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <esp_err.h> +#include <esp_log.h> +#include <unity.h> +#include <protocomm.h> +#include <protocomm_security.h> +#include <protocomm_security2.h> +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "sec2.pb-c.h" + +#if CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 + +static const char *TAG = "test_security2"; + +/* + * Pre-computed salt and verifier for username="wifiprov", password="abcd1234". + * Identical to the vectors used in test_srp.c. + */ +static const char sec2_salt[] = { + 0x03, 0x6e, 0xe0, 0xc7, 0xbc, 0xb9, 0xed, 0xa8, + 0x4c, 0x9e, 0xac, 0x97, 0xd9, 0x3d, 0xec, 0xf4 +}; + +static const char sec2_verifier[] = { + 0x7c, 0x7c, 0x85, 0x47, 0x65, 0x08, 0x94, 0x6d, 0xd6, 0x36, 0xaf, 0x37, 0xd7, 0xe8, 0x91, 0x43, + 0x78, 0xcf, 0xfd, 0x61, 0x6c, 0x59, 0xd2, 0xf8, 0x39, 0x08, 0x12, 0x72, 0x38, 0xde, 0x9e, 0x24, + 0xa4, 0x70, 0x26, 0x1c, 0xdf, 0xa9, 0x03, 0xc2, 0xb2, 0x70, 0xe7, 0xb1, 0x32, 0x24, 0xda, 0x11, + 0x1d, 0x97, 0x18, 0xdc, 0x60, 0x72, 0x08, 0xcc, 0x9a, 0xc9, 0x0c, 0x48, 0x27, 0xe2, 0xae, 0x89, + 0xaa, 0x16, 0x25, 0xb8, 0x04, 0xd2, 0x1a, 0x9b, 0x3a, 0x8f, 0x37, 0xf6, 0xe4, 0x3a, 0x71, 0x2e, + 0xe1, 0x27, 0x86, 0x6e, 0xad, 0xce, 0x28, 0xff, 0x54, 0x46, 0x60, 0x1f, 0xb9, 0x96, 0x87, 0xdc, + 0x57, 0x40, 0xa7, 0xd4, 0x6c, 0xc9, 0x77, 0x54, 0xdc, 0x16, 0x82, 0xf0, 0xed, 0x35, 0x6a, 0xc4, + 0x70, 0xad, 0x3d, 0x90, 0xb5, 0x81, 0x94, 0x70, 0xd7, 0xbc, 0x65, 0xb2, 0xd5, 0x18, 0xe0, 0x2e, + 0xc3, 0xa5, 0xf9, 0x68, 0xdd, 0x64, 0x7b, 0xb8, 0xb7, 0x3c, 0x9c, 0xfc, 0x00, 0xd8, 0x71, 0x7e, + 0xb7, 0x9a, 0x7c, 0xb1, 0xb7, 0xc2, 0xc3, 0x18, 0x34, 0x29, 0x32, 0x43, 0x3e, 0x00, 0x99, 0xe9, + 0x82, 0x94, 0xe3, 0xd8, 0x2a, 0xb0, 0x96, 0x29, 0xb7, 0xdf, 0x0e, 0x5f, 0x08, 0x33, 0x40, 0x76, + 0x52, 0x91, 0x32, 0x00, 0x9f, 0x97, 0x2c, 0x89, 0x6c, 0x39, 0x1e, 0xc8, 0x28, 0x05, 0x44, 0x17, + 0x3f, 0x68, 0x02, 0x8a, 0x9f, 0x44, 0x61, 0xd1, 0xf5, 0xa1, 0x7e, 0x5a, 0x70, 0xd2, 0xc7, 0x23, + 0x81, 0xcb, 0x38, 0x68, 0xe4, 0x2c, 0x20, 0xbc, 0x40, 0x57, 0x76, 0x17, 0xbd, 0x08, 0xb8, 0x96, + 0xbc, 0x26, 0xeb, 0x32, 0x46, 0x69, 0x35, 0x05, 0x8c, 0x15, 0x70, 0xd9, 0x1b, 0xe9, 0xbe, 0xcc, + 0xa9, 0x38, 0xa6, 0x67, 0xf0, 0xad, 0x50, 0x13, 0x19, 0x72, 0x64, 0xbf, 0x52, 0xc2, 0x34, 0xe2, + 0x1b, 0x11, 0x79, 0x74, 0x72, 0xbd, 0x34, 0x5b, 0xb1, 0xe2, 0xfd, 0x66, 0x73, 0xfe, 0x71, 0x64, + 0x74, 0xd0, 0x4e, 0xbc, 0x51, 0x24, 0x19, 0x40, 0x87, 0x0e, 0x92, 0x40, 0xe6, 0x21, 0xe7, 0x2d, + 0x4e, 0x37, 0x76, 0x2f, 0x2e, 0xe2, 0x68, 0xc7, 0x89, 0xe8, 0x32, 0x13, 0x42, 0x06, 0x84, 0x84, + 0x53, 0x4a, 0xb3, 0x0c, 0x1b, 0x4c, 0x8d, 0x1c, 0x51, 0x97, 0x19, 0xab, 0xae, 0x77, 0xff, 0xdb, + 0xec, 0xf0, 0x10, 0x95, 0x34, 0x33, 0x6b, 0xcb, 0x3e, 0x84, 0x0f, 0xb9, 0xd8, 0x5f, 0xb8, 0xa0, + 0xb8, 0x55, 0x53, 0x3e, 0x70, 0xf7, 0x18, 0xf5, 0xce, 0x7b, 0x4e, 0xbf, 0x27, 0xce, 0xce, 0xa8, + 0xb3, 0xbe, 0x40, 0xc5, 0xc5, 0x32, 0x29, 0x3e, 0x71, 0x64, 0x9e, 0xde, 0x8c, 0xf6, 0x75, 0xa1, + 0xe6, 0xf6, 0x53, 0xc8, 0x31, 0xa8, 0x78, 0xde, 0x50, 0x40, 0xf7, 0x62, 0xde, 0x36, 0xb2, 0xba +}; + +/* Must match PUBLIC_KEY_LEN in security2.c */ +#define SEC2_PUBLIC_KEY_LEN 384 + +static esp_err_t test_cmd0_oversized_username(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 100; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const size_t username_len = (size_t)UINT16_MAX + 2; + uint8_t *username_buf = calloc(1, username_len); + if (!username_buf) { + ESP_LOGE(TAG, "Cannot allocate %zu-byte username buffer — skipping test", username_len); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + memset(username_buf, 'A', username_len); + + uint8_t client_pubkey[SEC2_PUBLIC_KEY_LEN]; + memset(client_pubkey, 0xAB, sizeof(client_pubkey)); + + S2SessionCmd0 cmd0 = S2_SESSION_CMD0__INIT; + cmd0.client_username.data = username_buf; + cmd0.client_username.len = username_len; + cmd0.client_pubkey.data = client_pubkey; + cmd0.client_pubkey.len = SEC2_PUBLIC_KEY_LEN; + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + ESP_LOGE(TAG, "Cannot allocate %zu-byte packed buffer — skipping test", packed_len); + free(username_buf); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + free(username_buf); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Oversized username was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Oversized username correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 oversized username uint16_t truncation heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_oversized_username()); +} + +static esp_err_t test_cmd0_missing_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 101; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + /* payload.payload_case deliberately left as SEC2_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc0 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + ESP_LOGE(TAG, "Cannot allocate packed buffer"); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc0 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc0 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 missing sc0 payload NULL dereference", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_missing_sc0_payload()); +} + +static esp_err_t test_cmd0_msg_with_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 102; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* Build a Sec2Payload with msg=CMD0 but sc1 (not sc0) in the oneof. */ + uint8_t proof[64]; + memset(proof, 0x33, sizeof(proof)); + + S2SessionCmd1 cmd1 = S2_SESSION_CMD1__INIT; + cmd1.client_proof.data = proof; + cmd1.client_proof.len = sizeof(proof); + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; /* CMD0 msg */ + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC1; /* SC1 payload — mismatch */ + payload.sc1 = &cmd1; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD0/sc1 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD0/sc1 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 msg with sc1 payload oneof type confusion heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_msg_with_sc1_payload()); +} + +static esp_err_t test_cmd1_msg_with_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 103; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* Build a Sec2Payload with msg=CMD1 but sc0 (not sc1) in the oneof. */ + uint8_t pubkey[SEC2_PUBLIC_KEY_LEN]; + memset(pubkey, 0xAB, sizeof(pubkey)); + uint8_t uname[] = "alice"; + + S2SessionCmd0 cmd0 = S2_SESSION_CMD0__INIT; + cmd0.client_username.data = uname; + cmd0.client_username.len = sizeof(uname) - 1; + cmd0.client_pubkey.data = pubkey; + cmd0.client_pubkey.len = sizeof(pubkey); + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command1; /* CMD1 msg */ + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; /* SC0 payload — mismatch */ + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD1/sc0 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD1/sc0 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd1 msg with sc0 payload oneof type confusion", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_msg_with_sc0_payload()); +} + +static esp_err_t test_sec1_payload_with_sec2_version(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 104; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + uint8_t pubkey[32]; + memset(pubkey, 0xAB, sizeof(pubkey)); + + SessionCmd0 sec1_cmd0 = SESSION_CMD0__INIT; + sec1_cmd0.client_pubkey.data = pubkey; + sec1_cmd0.client_pubkey.len = sizeof(pubkey); + + Sec1Payload sec1_payload = SEC1_PAYLOAD__INIT; + sec1_payload.msg = SEC1_MSG_TYPE__Session_Command0; + sec1_payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; + sec1_payload.sc0 = &sec1_cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = SEC_SCHEME_VERSION__SecScheme2; /* sec_ver=2 — passes ver check */ + req.proto_case = SESSION_DATA__PROTO_SEC1; /* but SEC1 proto — mismatch */ + req.sec1 = &sec1_payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "sec1 payload with sec_ver=2 was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "sec1 payload with sec_ver=2 correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 sec1 payload with sec_ver=2 cross-scheme type confusion heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_sec1_payload_with_sec2_version()); +} + +#endif /* CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 */
components/protocomm/test_apps/sdkconfig.defaults+1 −0 modified@@ -11,3 +11,4 @@ CONFIG_ESP_TASK_WDT_EN=n # Enable all protocomm security versions for testing CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y
a2f4554f10bafix(protocomm): fixes potential issues that can lead to crash during device provisioning
6 files changed · +877 −8
components/mbedtls/port/include/mbedtls/esp_config.h+1 −2 modified@@ -31,10 +31,9 @@ #if (defined(MBEDTLS_MAJOR_VERSION) && (MBEDTLS_MAJOR_VERSION < 4)) #include "mbedtls/mbedtls_config.h" #endif // MBEDTLS_MAJOR_VERSION < 4 +#ifndef CONFIG_IDF_TARGET_LINUX #include "soc/soc_caps.h" - -#ifndef CONFIG_IDF_TARGET_LINUX #undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY #define MBEDTLS_PSA_DRIVER_GET_ENTROPY #define MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG
components/protocomm/src/security/security1.c+15 −2 modified@@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -91,7 +91,7 @@ static esp_err_t handle_session_command1(session_t *cur_session, } /* Validate client verifier data before processing */ - if (!in || !in->sc1 || + if (!in || in->payload_case != SEC1_PAYLOAD__PAYLOAD_SC1 || !in->sc1 || in->sc1->client_verify_data.data == NULL || in->sc1->client_verify_data.len != PUBLIC_KEY_LEN) { ESP_LOGE(TAG, "Invalid client verifier (ptr=%p len=%d)", @@ -237,6 +237,14 @@ static esp_err_t handle_session_command0(session_t *cur_session, sec1_new_session(cur_session, session_id); } + if (in->payload_case != SEC1_PAYLOAD__PAYLOAD_SC0 || !in->sc0) { + ESP_LOGE(TAG, "Missing or mismatched sc0 payload in session command0"); + if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { + ESP_LOGE(TAG, "Failed to post secure session invalid security params event"); + } + return ESP_ERR_INVALID_ARG; + } + if (in->sc0->client_pubkey.len != PUBLIC_KEY_LEN) { ESP_LOGE(TAG, "Invalid public key length"); if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { @@ -584,6 +592,11 @@ static esp_err_t sec1_req_handler(protocomm_security_handle_t handle, session_data__free_unpacked(req, NULL); return ESP_ERR_INVALID_ARG; } + if (req->proto_case != SESSION_DATA__PROTO_SEC1) { + ESP_LOGE(TAG, "Security scheme mismatch. Closing connection"); + session_data__free_unpacked(req, NULL); + return ESP_ERR_INVALID_ARG; + } session_data__init(&resp); ret = sec1_session_setup(cur_session, session_id, req, &resp, (protocomm_security1_params_t *) sec_params);
components/protocomm/src/security/security2.c+19 −4 modified@@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -89,6 +89,11 @@ static esp_err_t handle_session_command0(session_t *cur_session, ESP_LOGD(TAG, "Request to handle setup0_command"); Sec2Payload *in = (Sec2Payload *) req->sec2; + if (in->payload_case != SEC2_PAYLOAD__PAYLOAD_SC0 || !in->sc0) { + ESP_LOGE(TAG, "Missing or mismatched sc0 payload in session command0"); + return ESP_ERR_INVALID_ARG; + } + if (cur_session->state != SESSION_STATE_CMD0) { ESP_LOGW(TAG, "Invalid state of session %d (expected %d). Restarting session.", SESSION_STATE_CMD0, cur_session->state); @@ -103,8 +108,8 @@ static esp_err_t handle_session_command0(session_t *cur_session, return ESP_ERR_INVALID_ARG; } - if (in->sc0->client_username.len <= 0) { - ESP_LOGE(TAG, "Invalid username"); + if (in->sc0->client_username.len == 0 || in->sc0->client_username.len > UINT16_MAX) { + ESP_LOGE(TAG, "Invalid username length (%zu)", in->sc0->client_username.len); if (esp_event_post(PROTOCOMM_SECURITY_SESSION_EVENT, PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS, NULL, 0, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post secure session invalid security params event"); } @@ -195,7 +200,7 @@ static esp_err_t handle_session_command0(session_t *cur_session, free(out_resp); return ESP_ERR_NO_MEM; } - memcpy(cur_session->username, in->sc0->client_username.data, in->sc0->client_username.len); + memcpy(cur_session->username, in->sc0->client_username.data, cur_session->username_len); resp->sec_ver = SEC_SCHEME_VERSION__SecScheme2; resp->proto_case = SESSION_DATA__PROTO_SEC2; @@ -214,6 +219,11 @@ static esp_err_t handle_session_command1(session_t *cur_session, ESP_LOGD(TAG, "Request to handle setup1_command"); Sec2Payload *in = (Sec2Payload *) req->sec2; + if (in->payload_case != SEC2_PAYLOAD__PAYLOAD_SC1 || !in->sc1) { + ESP_LOGE(TAG, "Missing or mismatched sc1 payload in session command1"); + return ESP_ERR_INVALID_ARG; + } + if (cur_session->state != SESSION_STATE_CMD1) { ESP_LOGE(TAG, "Invalid state of session %d (expected %d)", SESSION_STATE_CMD1, cur_session->state); return ESP_ERR_INVALID_STATE; @@ -598,6 +608,11 @@ static esp_err_t sec2_req_handler(protocomm_security_handle_t handle, session_data__free_unpacked(req, NULL); return ESP_ERR_INVALID_ARG; } + if (req->proto_case != SESSION_DATA__PROTO_SEC2) { + ESP_LOGE(TAG, "Security scheme mismatch. Closing connection"); + session_data__free_unpacked(req, NULL); + return ESP_ERR_INVALID_ARG; + } session_data__init(&resp); ret = sec2_session_setup(cur_session, session_id, req, &resp, (protocomm_security2_params_t *) sec_params);
components/protocomm/test_apps/main/test_security1.c+376 −0 added@@ -0,0 +1,376 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <esp_err.h> +#include <esp_log.h> +#include <unity.h> +#include <protocomm.h> +#include <protocomm_security.h> +#include <protocomm_security1.h> +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "sec2.pb-c.h" + +#if CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 + +static const char *TAG = "test_security1"; + +/* Must match PUBLIC_KEY_LEN in security1.c */ +#define SEC1_PUBLIC_KEY_LEN 32 + +static esp_err_t test_cmd0_missing_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 200; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + /* msg=Command0 but payload_case left as NOT_SET and sc0 left as NULL */ + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command0; + /* payload.payload_case deliberately left as SEC1_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc0 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc0 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc0 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd0 missing sc0 payload NULL dereference", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_missing_sc0_payload()); +} + +static esp_err_t test_cmd0_msg_with_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 201; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command0 but sc1 (not sc0) populated in the oneof */ + uint8_t verify_data[SEC1_PUBLIC_KEY_LEN]; + memset(verify_data, 0x55, sizeof(verify_data)); + + SessionCmd1 cmd1 = SESSION_CMD1__INIT; + cmd1.client_verify_data.data = verify_data; + cmd1.client_verify_data.len = sizeof(verify_data); + + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command0; /* CMD0 msg */ + payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC1; /* SC1 payload — mismatch */ + payload.sc1 = &cmd1; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD0/sc1 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD0/sc1 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd0 msg with sc1 payload oneof type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_msg_with_sc1_payload()); +} + +static esp_err_t test_cmd1_missing_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 202; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command1 but payload_case left as NOT_SET and sc1 left as NULL */ + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command1; + /* payload.payload_case deliberately left as SEC1_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc1 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc1 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc1 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd1 missing sc1 payload NULL dereference", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_missing_sc1_payload()); +} + +static esp_err_t test_cmd1_msg_with_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 203; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* msg=Command1 but sc0 (not sc1) populated in the oneof */ + uint8_t pubkey[SEC1_PUBLIC_KEY_LEN]; + memset(pubkey, 0xAB, sizeof(pubkey)); + + SessionCmd0 cmd0 = SESSION_CMD0__INIT; + cmd0.client_pubkey.data = pubkey; + cmd0.client_pubkey.len = sizeof(pubkey); + + Sec1Payload payload = SEC1_PAYLOAD__INIT; + payload.msg = SEC1_MSG_TYPE__Session_Command1; /* CMD1 msg */ + payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; /* SC0 payload — mismatch */ + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security1.ver; + req.proto_case = SESSION_DATA__PROTO_SEC1; + req.sec1 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD1/sc0 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD1/sc0 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 cmd1 msg with sc0 payload oneof type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_msg_with_sc0_payload()); +} + +static esp_err_t test_sec2_payload_with_sec1_version(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security1, NULL); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 204; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* SessionData with sec_ver=1 but proto_case=SEC2, carrying a Sec2 CMD0. + * Sec2's S2SessionCmd0 (56 bytes) is larger than Sec1's SessionCmd0 + * (40 bytes), so sec1 code reading the union as Sec1Payload causes + * type confusion. */ + uint8_t pubkey[384]; + memset(pubkey, 0xAB, sizeof(pubkey)); + uint8_t uname[] = "wifiprov"; + + S2SessionCmd0 s2cmd0 = S2_SESSION_CMD0__INIT; + s2cmd0.client_pubkey.data = pubkey; + s2cmd0.client_pubkey.len = sizeof(pubkey); + s2cmd0.client_username.data = uname; + s2cmd0.client_username.len = sizeof(uname) - 1; + + Sec2Payload sec2_payload = SEC2_PAYLOAD__INIT; + sec2_payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + sec2_payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; + sec2_payload.sc0 = &s2cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = SEC_SCHEME_VERSION__SecScheme1; /* sec_ver=1 — passes ver check */ + req.proto_case = SESSION_DATA__PROTO_SEC2; /* but SEC2 proto — mismatch */ + req.sec2 = &sec2_payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "sec2 payload with sec_ver=1 was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "sec2 payload with sec_ver=1 correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security1 sec2 payload with sec_ver=1 cross-scheme type confusion", "[PROTOCOMM_SEC1]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_sec2_payload_with_sec1_version()); +} + +#endif /* CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 */
components/protocomm/test_apps/main/test_security2.c+465 −0 added@@ -0,0 +1,465 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <esp_err.h> +#include <esp_log.h> +#include <unity.h> +#include <protocomm.h> +#include <protocomm_security.h> +#include <protocomm_security2.h> +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "sec2.pb-c.h" + +#if CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 + +static const char *TAG = "test_security2"; + +/* + * Pre-computed salt and verifier for username="wifiprov", password="abcd1234". + * Identical to the vectors used in test_srp.c. + */ +static const char sec2_salt[] = { + 0x03, 0x6e, 0xe0, 0xc7, 0xbc, 0xb9, 0xed, 0xa8, + 0x4c, 0x9e, 0xac, 0x97, 0xd9, 0x3d, 0xec, 0xf4 +}; + +static const char sec2_verifier[] = { + 0x7c, 0x7c, 0x85, 0x47, 0x65, 0x08, 0x94, 0x6d, 0xd6, 0x36, 0xaf, 0x37, 0xd7, 0xe8, 0x91, 0x43, + 0x78, 0xcf, 0xfd, 0x61, 0x6c, 0x59, 0xd2, 0xf8, 0x39, 0x08, 0x12, 0x72, 0x38, 0xde, 0x9e, 0x24, + 0xa4, 0x70, 0x26, 0x1c, 0xdf, 0xa9, 0x03, 0xc2, 0xb2, 0x70, 0xe7, 0xb1, 0x32, 0x24, 0xda, 0x11, + 0x1d, 0x97, 0x18, 0xdc, 0x60, 0x72, 0x08, 0xcc, 0x9a, 0xc9, 0x0c, 0x48, 0x27, 0xe2, 0xae, 0x89, + 0xaa, 0x16, 0x25, 0xb8, 0x04, 0xd2, 0x1a, 0x9b, 0x3a, 0x8f, 0x37, 0xf6, 0xe4, 0x3a, 0x71, 0x2e, + 0xe1, 0x27, 0x86, 0x6e, 0xad, 0xce, 0x28, 0xff, 0x54, 0x46, 0x60, 0x1f, 0xb9, 0x96, 0x87, 0xdc, + 0x57, 0x40, 0xa7, 0xd4, 0x6c, 0xc9, 0x77, 0x54, 0xdc, 0x16, 0x82, 0xf0, 0xed, 0x35, 0x6a, 0xc4, + 0x70, 0xad, 0x3d, 0x90, 0xb5, 0x81, 0x94, 0x70, 0xd7, 0xbc, 0x65, 0xb2, 0xd5, 0x18, 0xe0, 0x2e, + 0xc3, 0xa5, 0xf9, 0x68, 0xdd, 0x64, 0x7b, 0xb8, 0xb7, 0x3c, 0x9c, 0xfc, 0x00, 0xd8, 0x71, 0x7e, + 0xb7, 0x9a, 0x7c, 0xb1, 0xb7, 0xc2, 0xc3, 0x18, 0x34, 0x29, 0x32, 0x43, 0x3e, 0x00, 0x99, 0xe9, + 0x82, 0x94, 0xe3, 0xd8, 0x2a, 0xb0, 0x96, 0x29, 0xb7, 0xdf, 0x0e, 0x5f, 0x08, 0x33, 0x40, 0x76, + 0x52, 0x91, 0x32, 0x00, 0x9f, 0x97, 0x2c, 0x89, 0x6c, 0x39, 0x1e, 0xc8, 0x28, 0x05, 0x44, 0x17, + 0x3f, 0x68, 0x02, 0x8a, 0x9f, 0x44, 0x61, 0xd1, 0xf5, 0xa1, 0x7e, 0x5a, 0x70, 0xd2, 0xc7, 0x23, + 0x81, 0xcb, 0x38, 0x68, 0xe4, 0x2c, 0x20, 0xbc, 0x40, 0x57, 0x76, 0x17, 0xbd, 0x08, 0xb8, 0x96, + 0xbc, 0x26, 0xeb, 0x32, 0x46, 0x69, 0x35, 0x05, 0x8c, 0x15, 0x70, 0xd9, 0x1b, 0xe9, 0xbe, 0xcc, + 0xa9, 0x38, 0xa6, 0x67, 0xf0, 0xad, 0x50, 0x13, 0x19, 0x72, 0x64, 0xbf, 0x52, 0xc2, 0x34, 0xe2, + 0x1b, 0x11, 0x79, 0x74, 0x72, 0xbd, 0x34, 0x5b, 0xb1, 0xe2, 0xfd, 0x66, 0x73, 0xfe, 0x71, 0x64, + 0x74, 0xd0, 0x4e, 0xbc, 0x51, 0x24, 0x19, 0x40, 0x87, 0x0e, 0x92, 0x40, 0xe6, 0x21, 0xe7, 0x2d, + 0x4e, 0x37, 0x76, 0x2f, 0x2e, 0xe2, 0x68, 0xc7, 0x89, 0xe8, 0x32, 0x13, 0x42, 0x06, 0x84, 0x84, + 0x53, 0x4a, 0xb3, 0x0c, 0x1b, 0x4c, 0x8d, 0x1c, 0x51, 0x97, 0x19, 0xab, 0xae, 0x77, 0xff, 0xdb, + 0xec, 0xf0, 0x10, 0x95, 0x34, 0x33, 0x6b, 0xcb, 0x3e, 0x84, 0x0f, 0xb9, 0xd8, 0x5f, 0xb8, 0xa0, + 0xb8, 0x55, 0x53, 0x3e, 0x70, 0xf7, 0x18, 0xf5, 0xce, 0x7b, 0x4e, 0xbf, 0x27, 0xce, 0xce, 0xa8, + 0xb3, 0xbe, 0x40, 0xc5, 0xc5, 0x32, 0x29, 0x3e, 0x71, 0x64, 0x9e, 0xde, 0x8c, 0xf6, 0x75, 0xa1, + 0xe6, 0xf6, 0x53, 0xc8, 0x31, 0xa8, 0x78, 0xde, 0x50, 0x40, 0xf7, 0x62, 0xde, 0x36, 0xb2, 0xba +}; + +/* Must match PUBLIC_KEY_LEN in security2.c */ +#define SEC2_PUBLIC_KEY_LEN 384 + +static esp_err_t test_cmd0_oversized_username(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 100; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const size_t username_len = (size_t)UINT16_MAX + 2; + uint8_t *username_buf = calloc(1, username_len); + if (!username_buf) { + ESP_LOGE(TAG, "Cannot allocate %zu-byte username buffer — skipping test", username_len); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + memset(username_buf, 'A', username_len); + + uint8_t client_pubkey[SEC2_PUBLIC_KEY_LEN]; + memset(client_pubkey, 0xAB, sizeof(client_pubkey)); + + S2SessionCmd0 cmd0 = S2_SESSION_CMD0__INIT; + cmd0.client_username.data = username_buf; + cmd0.client_username.len = username_len; + cmd0.client_pubkey.data = client_pubkey; + cmd0.client_pubkey.len = SEC2_PUBLIC_KEY_LEN; + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + ESP_LOGE(TAG, "Cannot allocate %zu-byte packed buffer — skipping test", packed_len); + free(username_buf); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + free(username_buf); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Oversized username was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Oversized username correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 oversized username uint16_t truncation heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_oversized_username()); +} + +static esp_err_t test_cmd0_missing_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_set_security failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 101; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "protocomm_open_session failed (0x%x)", ret); + protocomm_delete(pc); + return ret; + } + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; + /* payload.payload_case deliberately left as SEC2_PAYLOAD__PAYLOAD__NOT_SET */ + /* payload.sc0 deliberately left as NULL */ + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + ESP_LOGE(TAG, "Cannot allocate packed buffer"); + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "Missing sc0 payload was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Missing sc0 payload correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 missing sc0 payload NULL dereference", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_missing_sc0_payload()); +} + +static esp_err_t test_cmd0_msg_with_sc1_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 102; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* Build a Sec2Payload with msg=CMD0 but sc1 (not sc0) in the oneof. */ + uint8_t proof[64]; + memset(proof, 0x33, sizeof(proof)); + + S2SessionCmd1 cmd1 = S2_SESSION_CMD1__INIT; + cmd1.client_proof.data = proof; + cmd1.client_proof.len = sizeof(proof); + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command0; /* CMD0 msg */ + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC1; /* SC1 payload — mismatch */ + payload.sc1 = &cmd1; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD0/sc1 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD0/sc1 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd0 msg with sc1 payload oneof type confusion heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd0_msg_with_sc1_payload()); +} + +static esp_err_t test_cmd1_msg_with_sc0_payload(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 103; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + /* Build a Sec2Payload with msg=CMD1 but sc0 (not sc1) in the oneof. */ + uint8_t pubkey[SEC2_PUBLIC_KEY_LEN]; + memset(pubkey, 0xAB, sizeof(pubkey)); + uint8_t uname[] = "alice"; + + S2SessionCmd0 cmd0 = S2_SESSION_CMD0__INIT; + cmd0.client_username.data = uname; + cmd0.client_username.len = sizeof(uname) - 1; + cmd0.client_pubkey.data = pubkey; + cmd0.client_pubkey.len = sizeof(pubkey); + + Sec2Payload payload = SEC2_PAYLOAD__INIT; + payload.msg = SEC2_MSG_TYPE__S2Session_Command1; /* CMD1 msg */ + payload.payload_case = SEC2_PAYLOAD__PAYLOAD_SC0; /* SC0 payload — mismatch */ + payload.sc0 = &cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = (SecSchemeVersion)protocomm_security2.ver; + req.proto_case = SESSION_DATA__PROTO_SEC2; + req.sec2 = &payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "msg=CMD1/sc0 payload mismatch was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "msg=CMD1/sc0 payload mismatch correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 cmd1 msg with sc0 payload oneof type confusion", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_cmd1_msg_with_sc0_payload()); +} + +static esp_err_t test_sec1_payload_with_sec2_version(void) +{ + esp_err_t ret; + + protocomm_t *pc = protocomm_new(); + if (!pc) { + return ESP_ERR_NO_MEM; + } + + protocomm_security2_params_t sv = { + .salt = sec2_salt, + .salt_len = sizeof(sec2_salt), + .verifier = sec2_verifier, + .verifier_len = sizeof(sec2_verifier), + }; + + ret = protocomm_set_security(pc, "test-sec", &protocomm_security2, &sv); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + const uint32_t session_id = 104; + ret = protocomm_open_session(pc, session_id); + if (ret != ESP_OK) { + protocomm_delete(pc); + return ret; + } + + uint8_t pubkey[32]; + memset(pubkey, 0xAB, sizeof(pubkey)); + + SessionCmd0 sec1_cmd0 = SESSION_CMD0__INIT; + sec1_cmd0.client_pubkey.data = pubkey; + sec1_cmd0.client_pubkey.len = sizeof(pubkey); + + Sec1Payload sec1_payload = SEC1_PAYLOAD__INIT; + sec1_payload.msg = SEC1_MSG_TYPE__Session_Command0; + sec1_payload.payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; + sec1_payload.sc0 = &sec1_cmd0; + + SessionData req = SESSION_DATA__INIT; + req.sec_ver = SEC_SCHEME_VERSION__SecScheme2; /* sec_ver=2 — passes ver check */ + req.proto_case = SESSION_DATA__PROTO_SEC1; /* but SEC1 proto — mismatch */ + req.sec1 = &sec1_payload; + + size_t packed_len = session_data__get_packed_size(&req); + uint8_t *packed = malloc(packed_len); + if (!packed) { + protocomm_delete(pc); + return ESP_ERR_NO_MEM; + } + session_data__pack(&req, packed); + + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ret = protocomm_req_handle(pc, "test-sec", session_id, + packed, (ssize_t)packed_len, + &outbuf, &outlen); + free(packed); + free(outbuf); + protocomm_delete(pc); + + if (ret == ESP_OK) { + ESP_LOGE(TAG, "sec1 payload with sec_ver=2 was accepted — expected rejection"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "sec1 payload with sec_ver=2 correctly rejected (ret=0x%x)", ret); + return ESP_OK; +} + +TEST_CASE("security2 sec1 payload with sec_ver=2 cross-scheme type confusion heap overflow", "[PROTOCOMM_SEC2]") +{ + TEST_ASSERT_EQUAL(ESP_OK, test_sec1_payload_with_sec2_version()); +} + +#endif /* CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 */
components/protocomm/test_apps/sdkconfig.defaults+1 −0 modified@@ -11,3 +11,4 @@ CONFIG_ESP_TASK_WDT_EN=n # Enable all protocomm security versions for testing CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y
Vulnerability mechanics
Root cause
"The protocomm component trusts an oversized client-supplied length for the SRP6a username, leading to a heap buffer overflow."
Attack vector
An unauthenticated attacker within Bluetooth Low Energy range can trigger this vulnerability by initiating a provisioning session with an oversized SRP6a username field [ref_id=1]. The vulnerable handler, `handle_session_command0()`, is reachable when the application uses Wi-Fi provisioning over the NimBLE BLE transport with Security Scheme 2 [ref_id=1]. The attacker's payload exploits a truncation-versus-copy asymmetry by supplying a username length that exceeds the destination buffer's capacity, corrupting the heap.
Affected code
The vulnerability resides in the `handle_session_command0()` function within `components/protocomm/src/security/security2.c` [ref_id=1]. Specifically, the code mishandles the `client_username.len` field from the `Sec2Payload` structure when processing the session setup command [ref_id=2, ref_id=3, ref_id=4].
What the fix does
The patch modifies `handle_session_command0()` to validate the client-supplied username length against the destination buffer's size before copying [ref_id=1, ref_id=2, ref_id=3, ref_id=4]. It now checks if the length is within a valid range (not zero and not exceeding `UINT16_MAX`) and rejects oversized or zero-length values before they can cause a heap corruption [ref_id=1, ref_id=2, ref_id=3, ref_id=4]. Additionally, the copy operation now uses the validated length, preventing future regressions [ref_id=1].
Preconditions
- configThe application must perform Wi-Fi provisioning over the NimBLE BLE transport with Security Scheme 2 enabled [ref_id=1].
- authNo authentication or privileges are required to trigger the vulnerability, as the handler runs before any credential checks [ref_id=1].
- networkThe attacker must be within Bluetooth Low Energy radio range of the target device [ref_id=1].
Generated on Jun 10, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/espressif/esp-idf/commit/0ea58d79845ad674d0358d5de246015a68c4cb4fnvd
- github.com/espressif/esp-idf/commit/56c3e385611e63162d0f2f8504ac4ae2ccfccef0nvd
- github.com/espressif/esp-idf/commit/71eb2dbe6aaef830719ecac8edf409e2992b64b2nvd
- github.com/espressif/esp-idf/commit/9b4cacf9cbc69379972de6a2247fcf5af9240961nvd
- github.com/espressif/esp-idf/commit/a2f4554f10ba075c98cbc67464db096ba32497cfnvd
- github.com/espressif/esp-idf/commit/f5d24a7e919bc5f447091479656b86da6762a103nvd
- github.com/espressif/esp-idf/security/advisories/GHSA-9r76-858f-v6jhnvd
News mentions
0No linked articles in our index yet.