diff options
| author | Your Name <you@example.com> | 2026-05-15 23:05:46 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-15 23:05:46 +0530 |
| commit | aed51d824f598f7315282936037c4d5b6e7fb4b8 (patch) | |
| tree | 1ed148916cc0a90a991fb9d5e84a3197df8ae7ae | |
| parent | 1263d86314fc0760d9be8eea415ccecbc047a5eb (diff) | |
Fix stack overflow and heap-allocate large buffers in Cashu/payment path
- tollgate_api.c: increase httpd stack_size to 16384 (was default 4096)
- cashu.c: heap-allocate b64, json_buf, post_body, resp_buf instead of stack
- cashu.c: proper free() on all error paths
- Makefile: replace Go-based tokens target with nutshell wallet targets
- Makefile: add wallet-setup, wallet-info, wallet-balance, mint-token, send-token
| -rw-r--r-- | Makefile | 41 | ||||
| -rw-r--r-- | main/cashu.c | 31 | ||||
| -rw-r--r-- | main/tollgate_api.c | 1 |
3 files changed, 56 insertions, 17 deletions
| @@ -20,7 +20,7 @@ PYTHON ?= python3 | |||
| 20 | .PHONY: help setup detect-ports detect-chip detect-all | 20 | .PHONY: help setup detect-ports detect-chip detect-all |
| 21 | .PHONY: flash flash-a flash-b monitor monitor-a monitor-b | 21 | .PHONY: flash flash-a flash-b monitor monitor-a monitor-b |
| 22 | .PHONY: test smoke test-api test-portal test-network test-full | 22 | .PHONY: test smoke test-api test-portal test-network test-full |
| 23 | .PHONY: tokens test-payment | 23 | .PHONY: tokens test-payment wallet-setup wallet-info wallet-balance mint-token send-token |
| 24 | .PHONY: clean erase-nvs reset serial-log | 24 | .PHONY: clean erase-nvs reset serial-log |
| 25 | .PHONY: bootstrap-config | 25 | .PHONY: bootstrap-config |
| 26 | 26 | ||
| @@ -47,7 +47,12 @@ help: | |||
| 47 | @echo " test-full All 14 Phase 1 tests" | 47 | @echo " test-full All 14 Phase 1 tests" |
| 48 | @echo "" | 48 | @echo "" |
| 49 | @echo "Test (Phase 2):" | 49 | @echo "Test (Phase 2):" |
| 50 | @echo " tokens Mint test Cashu tokens (AMOUNT=21)" | 50 | @echo " wallet-setup Initialize nutshell wallet for test mint" |
| 51 | @echo " wallet-info Show mint info" | ||
| 52 | @echo " wallet-balance Show wallet balance" | ||
| 53 | @echo " mint-token Invoice + send test token (AMOUNT=21)" | ||
| 54 | @echo " send-token Send cashuA token (AMOUNT=21)" | ||
| 55 | @echo " tokens Alias for send-token" | ||
| 51 | @echo " test-payment Payment flow tests" | 56 | @echo " test-payment Payment flow tests" |
| 52 | @echo "" | 57 | @echo "" |
| 53 | @echo "Utilities:" | 58 | @echo "Utilities:" |
| @@ -165,17 +170,39 @@ test-full: test-api test-portal test-network | |||
| 165 | @echo "=== Full test suite passed ===" | 170 | @echo "=== Full test suite passed ===" |
| 166 | 171 | ||
| 167 | # ────────────────────────────────────────────── | 172 | # ────────────────────────────────────────────── |
| 168 | # Phase 2: Payment Testing | 173 | # Phase 2: Payment Testing (Nutshell wallet) |
| 169 | # ────────────────────────────────────────────── | 174 | # ────────────────────────────────────────────── |
| 170 | 175 | ||
| 171 | tokens: | 176 | wallet-setup: |
| 172 | @echo "=== Minting test tokens from $(TEST_MINT) ===" | 177 | @echo "=== Setting up Nutshell wallet for $(TEST_MINT) ===" |
| 178 | cashu --env-mint $(TEST_MINT) info 2>/dev/null || \ | ||
| 179 | cashu --env-mint $(TEST_MINT) restore | ||
| 180 | |||
| 181 | wallet-info: | ||
| 182 | @echo "=== Mint info ===" | ||
| 183 | cashu --env-mint $(TEST_MINT) info | ||
| 184 | |||
| 185 | wallet-balance: | ||
| 186 | @echo "=== Wallet balance ===" | ||
| 187 | cashu --env-mint $(TEST_MINT) balance | ||
| 188 | |||
| 189 | mint-token: | ||
| 190 | @echo "=== Minting test token (AMOUNT=$(or $(AMOUNT),21)) ===" | ||
| 173 | @AMOUNT=$${AMOUNT:-21}; \ | 191 | @AMOUNT=$${AMOUNT:-21}; \ |
| 174 | cd scripts/mint-token && go run main.go -mint https://$(TEST_MINT) -amount $$AMOUNT | 192 | cashu --env-mint $(TEST_MINT) invoice $$AMOUNT && \ |
| 193 | echo "--- Token (cashuA legacy) ---" && \ | ||
| 194 | cashu --env-mint $(TEST_MINT) send --legacy $$AMOUNT | ||
| 195 | |||
| 196 | send-token: | ||
| 197 | @AMOUNT=$${AMOUNT:-21}; \ | ||
| 198 | echo "=== Sending $$AMOUNT sats as cashuA token ===" && \ | ||
| 199 | cashu --env-mint $(TEST_MINT) send --legacy $$AMOUNT | ||
| 200 | |||
| 201 | tokens: send-token | ||
| 175 | 202 | ||
| 176 | test-payment: | 203 | test-payment: |
| 177 | @echo "=== Running payment tests ===" | 204 | @echo "=== Running payment tests ===" |
| 178 | $(NODE) tests/payment.mjs | 205 | $(NODE) tests/phase2.mjs |
| 179 | 206 | ||
| 180 | # ────────────────────────────────────────────── | 207 | # ────────────────────────────────────────────── |
| 181 | # Utilities | 208 | # Utilities |
diff --git a/main/cashu.c b/main/cashu.c index bafd000..8dffacc 100644 --- a/main/cashu.c +++ b/main/cashu.c | |||
| @@ -14,9 +14,9 @@ static const size_t V3_PREFIX_LEN = 6; | |||
| 14 | 14 | ||
| 15 | static int b64url_decode(const char *input, size_t input_len, char *out, size_t out_size, size_t *out_len) | 15 | static int b64url_decode(const char *input, size_t input_len, char *out, size_t out_size, size_t *out_len) |
| 16 | { | 16 | { |
| 17 | char b64[1024]; | 17 | char *b64 = malloc(input_len + 4); |
| 18 | if (!b64) return -1; | ||
| 18 | size_t b64_len = input_len; | 19 | size_t b64_len = input_len; |
| 19 | if (b64_len >= sizeof(b64)) return -1; | ||
| 20 | memcpy(b64, input, b64_len); | 20 | memcpy(b64, input, b64_len); |
| 21 | b64[b64_len] = '\0'; | 21 | b64[b64_len] = '\0'; |
| 22 | 22 | ||
| @@ -24,7 +24,7 @@ static int b64url_decode(const char *input, size_t input_len, char *out, size_t | |||
| 24 | if (b64[i] == '-') b64[i] = '+'; | 24 | if (b64[i] == '-') b64[i] = '+'; |
| 25 | else if (b64[i] == '_') b64[i] = '/'; | 25 | else if (b64[i] == '_') b64[i] = '/'; |
| 26 | } | 26 | } |
| 27 | while (b64_len % 4 != 0 && b64_len < sizeof(b64) - 1) { | 27 | while (b64_len % 4 != 0) { |
| 28 | b64[b64_len++] = '='; | 28 | b64[b64_len++] = '='; |
| 29 | } | 29 | } |
| 30 | b64[b64_len] = '\0'; | 30 | b64[b64_len] = '\0'; |
| @@ -32,6 +32,7 @@ static int b64url_decode(const char *input, size_t input_len, char *out, size_t | |||
| 32 | size_t olen = 0; | 32 | size_t olen = 0; |
| 33 | int ret = mbedtls_base64_decode((unsigned char *)out, out_size, &olen, | 33 | int ret = mbedtls_base64_decode((unsigned char *)out, out_size, &olen, |
| 34 | (const unsigned char *)b64, b64_len); | 34 | (const unsigned char *)b64, b64_len); |
| 35 | free(b64); | ||
| 35 | if (ret != 0) return -1; | 36 | if (ret != 0) return -1; |
| 36 | *out_len = olen; | 37 | *out_len = olen; |
| 37 | return 0; | 38 | return 0; |
| @@ -86,16 +87,19 @@ esp_err_t cashu_decode_token(const char *token_str, cashu_token_t *out) | |||
| 86 | return ESP_FAIL; | 87 | return ESP_FAIL; |
| 87 | } | 88 | } |
| 88 | 89 | ||
| 89 | char json_buf[2048]; | 90 | char *json_buf = malloc(2048); |
| 91 | if (!json_buf) return ESP_FAIL; | ||
| 90 | size_t json_len = 0; | 92 | size_t json_len = 0; |
| 91 | if (b64url_decode(token_str + V3_PREFIX_LEN, len - V3_PREFIX_LEN, | 93 | if (b64url_decode(token_str + V3_PREFIX_LEN, len - V3_PREFIX_LEN, |
| 92 | json_buf, sizeof(json_buf) - 1, &json_len) != 0) { | 94 | json_buf, 2047, &json_len) != 0) { |
| 93 | ESP_LOGE(TAG, "Base64url decode failed"); | 95 | ESP_LOGE(TAG, "Base64url decode failed"); |
| 96 | free(json_buf); | ||
| 94 | return ESP_FAIL; | 97 | return ESP_FAIL; |
| 95 | } | 98 | } |
| 96 | json_buf[json_len] = '\0'; | 99 | json_buf[json_len] = '\0'; |
| 97 | 100 | ||
| 98 | cJSON *root = cJSON_Parse(json_buf); | 101 | cJSON *root = cJSON_Parse(json_buf); |
| 102 | free(json_buf); | ||
| 99 | if (!root) { | 103 | if (!root) { |
| 100 | ESP_LOGE(TAG, "JSON parse failed"); | 104 | ESP_LOGE(TAG, "JSON parse failed"); |
| 101 | return ESP_FAIL; | 105 | return ESP_FAIL; |
| @@ -167,14 +171,16 @@ esp_err_t cashu_check_proof_states(const char *mint_url, const cashu_token_t *to | |||
| 167 | char *ys_json = cJSON_PrintUnformatted(ys_arr); | 171 | char *ys_json = cJSON_PrintUnformatted(ys_arr); |
| 168 | cJSON_Delete(ys_arr); | 172 | cJSON_Delete(ys_arr); |
| 169 | 173 | ||
| 170 | char post_body[2048]; | 174 | char *post_body = malloc(4096); |
| 171 | snprintf(post_body, sizeof(post_body), "{\"Ys\":%s}", ys_json); | 175 | if (!post_body) { cJSON_free(ys_json); return ESP_FAIL; } |
| 176 | snprintf(post_body, 4096, "{\"Ys\":%s}", ys_json); | ||
| 172 | cJSON_free(ys_json); | 177 | cJSON_free(ys_json); |
| 173 | 178 | ||
| 174 | char url[512]; | 179 | char url[512]; |
| 175 | snprintf(url, sizeof(url), "%s/v1/checkstate", mint_url); | 180 | snprintf(url, sizeof(url), "%s/v1/checkstate", mint_url); |
| 176 | 181 | ||
| 177 | char resp_buf[4096]; | 182 | char *resp_buf = malloc(8192); |
| 183 | if (!resp_buf) { free(post_body); return ESP_FAIL; } | ||
| 178 | int resp_len = 0; | 184 | int resp_len = 0; |
| 179 | 185 | ||
| 180 | esp_http_client_config_t config = { | 186 | esp_http_client_config_t config = { |
| @@ -183,27 +189,32 @@ esp_err_t cashu_check_proof_states(const char *mint_url, const cashu_token_t *to | |||
| 183 | .timeout_ms = 10000, | 189 | .timeout_ms = 10000, |
| 184 | }; | 190 | }; |
| 185 | esp_http_client_handle_t client = esp_http_client_init(&config); | 191 | esp_http_client_handle_t client = esp_http_client_init(&config); |
| 186 | if (!client) return ESP_FAIL; | 192 | if (!client) { free(post_body); free(resp_buf); return ESP_FAIL; } |
| 187 | 193 | ||
| 188 | esp_http_client_set_header(client, "Content-Type", "application/json"); | 194 | esp_http_client_set_header(client, "Content-Type", "application/json"); |
| 189 | esp_err_t err = esp_http_client_open(client, strlen(post_body)); | 195 | esp_err_t err = esp_http_client_open(client, strlen(post_body)); |
| 190 | if (err != ESP_OK) { | 196 | if (err != ESP_OK) { |
| 191 | esp_http_client_cleanup(client); | 197 | esp_http_client_cleanup(client); |
| 198 | free(post_body); | ||
| 199 | free(resp_buf); | ||
| 192 | return err; | 200 | return err; |
| 193 | } | 201 | } |
| 194 | esp_http_client_write(client, post_body, strlen(post_body)); | 202 | esp_http_client_write(client, post_body, strlen(post_body)); |
| 203 | free(post_body); | ||
| 195 | 204 | ||
| 196 | resp_len = esp_http_client_read(client, resp_buf, sizeof(resp_buf) - 1); | 205 | resp_len = esp_http_client_read(client, resp_buf, 8191); |
| 197 | int status = esp_http_client_get_status_code(client); | 206 | int status = esp_http_client_get_status_code(client); |
| 198 | esp_http_client_cleanup(client); | 207 | esp_http_client_cleanup(client); |
| 199 | 208 | ||
| 200 | if (status != 200 || resp_len <= 0) { | 209 | if (status != 200 || resp_len <= 0) { |
| 201 | ESP_LOGE(TAG, "checkstate returned %d", status); | 210 | ESP_LOGE(TAG, "checkstate returned %d", status); |
| 211 | free(resp_buf); | ||
| 202 | return ESP_FAIL; | 212 | return ESP_FAIL; |
| 203 | } | 213 | } |
| 204 | resp_buf[resp_len] = '\0'; | 214 | resp_buf[resp_len] = '\0'; |
| 205 | 215 | ||
| 206 | cJSON *root = cJSON_Parse(resp_buf); | 216 | cJSON *root = cJSON_Parse(resp_buf); |
| 217 | free(resp_buf); | ||
| 207 | if (!root) return ESP_FAIL; | 218 | if (!root) return ESP_FAIL; |
| 208 | 219 | ||
| 209 | cJSON *states_arr = cJSON_GetObjectItemCaseSensitive(root, "states"); | 220 | cJSON *states_arr = cJSON_GetObjectItemCaseSensitive(root, "states"); |
diff --git a/main/tollgate_api.c b/main/tollgate_api.c index 5ada3c7..b2ad647 100644 --- a/main/tollgate_api.c +++ b/main/tollgate_api.c | |||
| @@ -330,6 +330,7 @@ esp_err_t tollgate_api_start(void) | |||
| 330 | config.server_port = 2121; | 330 | config.server_port = 2121; |
| 331 | config.ctrl_port = 32769; | 331 | config.ctrl_port = 32769; |
| 332 | config.max_uri_handlers = 10; | 332 | config.max_uri_handlers = 10; |
| 333 | config.stack_size = 16384; | ||
| 333 | 334 | ||
| 334 | esp_err_t ret = httpd_start(&s_api_server, &config); | 335 | esp_err_t ret = httpd_start(&s_api_server, &config); |
| 335 | if (ret != ESP_OK) { | 336 | if (ret != ESP_OK) { |