VYPR
High severity7.1NVD Advisory· Published Jun 10, 2026

CVE-2026-45542

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

2
  • Espressif/Esp Idfreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: <=5.2.6, <=5.3.5, <=5.4.4, <=5.5.4, <=6.0

Patches

6
f5d24a7e919b

fix(protocomm): fixes potential issues that can lead to crash during device provisioning

https://github.com/espressif/esp-idfAshish SharmaMar 24, 2026via nvd-ref
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
    
0ea58d79845a

fix(protocomm): fixes potential issues that can lead to crash during device provisioning

https://github.com/espressif/esp-idfAshish SharmaMar 24, 2026via nvd-ref
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
    
56c3e385611e

fix(protocomm): fixes potential issues that can lead to crash during device provisioning

https://github.com/espressif/esp-idfAshish SharmaMar 24, 2026via nvd-ref
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
    
71eb2dbe6aae

fix(protocomm): fixes potential issues that can lead to crash during device provisioning

https://github.com/espressif/esp-idfAshish SharmaMar 24, 2026via nvd-ref
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
    
9b4cacf9cbc6

fix(protocomm): fixes potential issues that can lead to crash during device provisioning

https://github.com/espressif/esp-idfAshish SharmaMar 24, 2026via nvd-ref
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
    
a2f4554f10ba

fix(protocomm): fixes potential issues that can lead to crash during device provisioning

https://github.com/espressif/esp-idfAshish SharmaMar 24, 2026via nvd-ref
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

News mentions

0

No linked articles in our index yet.