From 7e3ed5dda0efdb7e9e080add16e1ef22855d88bb Mon Sep 17 00:00:00 2001 From: CinderSocket Date: Wed, 17 Jun 2026 16:41:56 -0700 Subject: [PATCH] t1: surface chained response allocation failures --- hf_read_lifecycle.c | 2 ++ hf_read_lifecycle.h | 1 + lib/host_tests/bit_buffer.h | 1 + lib/host_tests/bit_buffer_mock.c | 11 ++++++++ lib/host_tests/t1_test_stubs.c | 16 +++++++++++ lib/host_tests/t_1_host_env.h | 8 ++++++ lib/host_tests/test_hf_read_lifecycle.c | 7 +++++ lib/host_tests/test_t1_protocol.c | 35 +++++++++++++++++++++++++ sam_api.c | 6 +---- sam_api.h | 5 ++++ t_1.c | 8 ++++++ t_1_logic.c | 3 +++ t_1_logic.h | 1 + 13 files changed, 99 insertions(+), 5 deletions(-) diff --git a/hf_read_lifecycle.c b/hf_read_lifecycle.c index e12baec..52882e1 100644 --- a/hf_read_lifecycle.c +++ b/hf_read_lifecycle.c @@ -47,6 +47,8 @@ const char* seader_hf_read_failure_reason_text(SeaderHfReadFailureReason reason) return "Read state error"; case SeaderHfReadFailureReasonSamKeysMissing: return "SAM missing keys"; + case SeaderHfReadFailureReasonResourceExhausted: + return "SAM exchange memory error"; case SeaderHfReadFailureReasonNone: default: return "Read failed"; diff --git a/hf_read_lifecycle.h b/hf_read_lifecycle.h index c1a0806..d07a7f9 100644 --- a/hf_read_lifecycle.h +++ b/hf_read_lifecycle.h @@ -34,6 +34,7 @@ typedef enum { SeaderHfReadFailureReasonProtocolError, SeaderHfReadFailureReasonInternalState, SeaderHfReadFailureReasonSamKeysMissing, + SeaderHfReadFailureReasonResourceExhausted, } SeaderHfReadFailureReason; typedef enum { diff --git a/lib/host_tests/bit_buffer.h b/lib/host_tests/bit_buffer.h index 8af0ff0..20be7cb 100644 --- a/lib/host_tests/bit_buffer.h +++ b/lib/host_tests/bit_buffer.h @@ -7,6 +7,7 @@ typedef struct BitBuffer BitBuffer; BitBuffer* bit_buffer_alloc(size_t capacity_bytes); +void bit_buffer_test_fail_next_alloc(bool fail); void bit_buffer_free(BitBuffer* buf); void bit_buffer_reset(BitBuffer* buf); void bit_buffer_copy_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes); diff --git a/lib/host_tests/bit_buffer_mock.c b/lib/host_tests/bit_buffer_mock.c index fd34458..777427a 100644 --- a/lib/host_tests/bit_buffer_mock.c +++ b/lib/host_tests/bit_buffer_mock.c @@ -9,7 +9,14 @@ struct BitBuffer { size_t capacity_bytes; }; +static bool bit_buffer_fail_next_alloc = false; + BitBuffer* bit_buffer_alloc(size_t capacity_bytes) { + if(bit_buffer_fail_next_alloc) { + bit_buffer_fail_next_alloc = false; + return NULL; + } + BitBuffer* buf = malloc(sizeof(BitBuffer)); buf->data = calloc(1, capacity_bytes); buf->size_bits = 0; @@ -17,6 +24,10 @@ BitBuffer* bit_buffer_alloc(size_t capacity_bytes) { return buf; } +void bit_buffer_test_fail_next_alloc(bool fail) { + bit_buffer_fail_next_alloc = fail; +} + void bit_buffer_free(BitBuffer* buf) { if(buf) { free(buf->data); diff --git a/lib/host_tests/t1_test_stubs.c b/lib/host_tests/t1_test_stubs.c index da69ec0..4f98e45 100644 --- a/lib/host_tests/t1_test_stubs.c +++ b/lib/host_tests/t1_test_stubs.c @@ -29,3 +29,19 @@ void seader_worker_send_version(Seader* seader) { (void)seader; g_t1_host_test_state.send_version_call_count++; } + +void seader_abort_active_read_with_reason( + Seader* seader, + SeaderHfReadFailureReason reason, + const char* detail) { + g_t1_host_test_state.abort_call_count++; + seader->hf_read_failure_reason = reason; + if(detail && detail[0] != '\0') { + strncpy(seader->read_error, detail, sizeof(seader->read_error) - 1U); + } else { + strncpy( + seader->read_error, + seader_hf_read_failure_reason_text(reason), + sizeof(seader->read_error) - 1U); + } +} diff --git a/lib/host_tests/t_1_host_env.h b/lib/host_tests/t_1_host_env.h index 52ef0d6..d60d464 100644 --- a/lib/host_tests/t_1_host_env.h +++ b/lib/host_tests/t_1_host_env.h @@ -8,6 +8,7 @@ #include #include "bit_buffer.h" +#include "hf_read_lifecycle.h" #include "lrc.h" #include "t_1_logic.h" @@ -46,6 +47,8 @@ struct SeaderWorker { struct Seader { SeaderWorker* worker; + SeaderHfReadFailureReason hf_read_failure_reason; + char read_error[97]; }; typedef struct CCID_Message { @@ -62,6 +65,10 @@ typedef struct CCID_Message { void seader_ccid_XfrBlock(SeaderUartBridge* seader_uart, uint8_t* data, size_t len); bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t len); void seader_worker_send_version(Seader* seader); +void seader_abort_active_read_with_reason( + Seader* seader, + SeaderHfReadFailureReason reason, + const char* detail); typedef struct { /* Captured outbound CCID payload emitted by the T=1 implementation. */ @@ -77,6 +84,7 @@ typedef struct { size_t send_version_call_count; size_t callback_call_count; uint32_t last_callback_event; + size_t abort_call_count; } T1HostTestState; extern T1HostTestState g_t1_host_test_state; diff --git a/lib/host_tests/test_hf_read_lifecycle.c b/lib/host_tests/test_hf_read_lifecycle.c index 33bcf6d..0cea6d0 100644 --- a/lib/host_tests/test_hf_read_lifecycle.c +++ b/lib/host_tests/test_hf_read_lifecycle.c @@ -65,6 +65,9 @@ static MunitResult test_failure_reason_texts_are_stable( munit_assert_string_equal( seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonSamKeysMissing), "SAM missing keys"); + munit_assert_string_equal( + seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonResourceExhausted), + "SAM exchange memory error"); return MUNIT_OK; } @@ -143,6 +146,10 @@ static MunitResult test_error_texts_fit_read_error_storage( munit_assert_size(strlen(protected_read_timeout), <, 96U); munit_assert_size( strlen(seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonInternalState)), <, 96U); + munit_assert_size( + strlen(seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonResourceExhausted)), + <, + 96U); return MUNIT_OK; } diff --git a/lib/host_tests/test_t1_protocol.c b/lib/host_tests/test_t1_protocol.c index df97f55..c61072b 100644 --- a/lib/host_tests/test_t1_protocol.c +++ b/lib/host_tests/test_t1_protocol.c @@ -196,6 +196,35 @@ static MunitResult test_recv_i_block_too_large_rejected(const MunitParameter par return MUNIT_OK; } +static MunitResult test_recv_chained_i_block_oom_returns_error( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + SeaderUartBridge uart = {0}; + SeaderWorker worker = {0}; + Seader seader = make_test_seader(&uart, &worker); + uint8_t i_block_more[] = {0x00, 0x20, 0x02, 0xAA, 0xBB, 0x00}; + CCID_Message message = {.payload = i_block_more, .dwLength = sizeof(i_block_more)}; + + t1_host_test_reset(); + uart.t1.ifsd = SEADER_T1_IFS_DEFAULT; + uart.t1.recv_pcb = 0x00; + seader_add_lrc(i_block_more, sizeof(i_block_more) - 1U); + bit_buffer_test_fail_next_alloc(true); + + munit_assert_false(seader_recv_t1(&seader, &message)); + munit_assert_null(uart.t1.rx_buffer); + munit_assert_size(g_t1_host_test_state.xfrblock_call_count, ==, 0); + munit_assert_size(g_t1_host_test_state.process_call_count, ==, 0); + munit_assert_size(g_t1_host_test_state.abort_call_count, ==, 1); + munit_assert_int( + seader.hf_read_failure_reason, ==, SeaderHfReadFailureReasonResourceExhausted); + munit_assert_string_equal(seader.read_error, "SAM exchange memory error"); + return MUNIT_OK; +} + static MunitResult test_recv_r_block_nack_retransmits(const MunitParameter params[], void* fixture) { (void)params; (void)fixture; @@ -393,6 +422,12 @@ static MunitTest test_t1_regression_cases[] = { NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/recv/chained-i-block-oom-errors", + test_recv_chained_i_block_oom_returns_error, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, {(char*)"/recv/r-block-nack-retransmits", test_recv_r_block_nack_retransmits, NULL, diff --git a/sam_api.c b/sam_api.c index 4d6242a..8a1cfa0 100644 --- a/sam_api.c +++ b/sam_api.c @@ -357,10 +357,6 @@ void* calloc(size_t count, size_t size) { // Forward declarations static void seader_abort_active_read(Seader* seader); -static void seader_abort_active_read_with_reason( - Seader* seader, - SeaderHfReadFailureReason reason, - const char* detail); static void seader_sam_set_state( Seader* seader, @@ -1038,7 +1034,7 @@ bool seader_parse_serial_number(Seader* seader, uint8_t* buf, size_t size) { return seader_sam_save_serial(seader, buf, size); } -static void seader_abort_active_read_with_reason( +void seader_abort_active_read_with_reason( Seader* seader, SeaderHfReadFailureReason reason, const char* detail) { diff --git a/sam_api.h b/sam_api.h index 7e773ee..f995079 100644 --- a/sam_api.h +++ b/sam_api.h @@ -12,6 +12,7 @@ #include #include "hf_bridge_policy.h" +#include "hf_read_lifecycle.h" typedef struct Seader Seader; typedef struct SeaderPollerContainer SeaderPollerContainer; @@ -41,6 +42,10 @@ void seader_send_no_card_detected(Seader* seader); bool seader_sam_can_accept_card(const Seader* seader); bool seader_sam_has_active_card(const Seader* seader); void seader_sam_force_idle_for_recovery(Seader* seader); +void seader_abort_active_read_with_reason( + Seader* seader, + SeaderHfReadFailureReason reason, + const char* detail); bool seader_process_success_response_i( Seader* seader, diff --git a/t_1.c b/t_1.c index be46805..2d25218 100644 --- a/t_1.c +++ b/t_1.c @@ -1,6 +1,7 @@ #ifdef SEADER_HOST_TEST #include "lib/host_tests/t_1_host_env.h" #else +#include "sam_api.h" #include "t_1.h" #endif @@ -279,6 +280,13 @@ bool seader_recv_t1(Seader* seader, CCID_Message* message) { seader_send_t1(seader_uart, NULL, 0); return false; + case SeaderT1ActionResourceExhausted: + FURI_LOG_W(TAG, "T=1 chained APDU allocation failed"); + seader_t1_reset_link_state(t1); + seader_abort_active_read_with_reason( + seader, SeaderHfReadFailureReasonResourceExhausted, NULL); + return false; + case SeaderT1ActionNone: return true; diff --git a/t_1_logic.c b/t_1_logic.c index 8b27664..50719db 100644 --- a/t_1_logic.c +++ b/t_1_logic.c @@ -164,6 +164,9 @@ SeaderT1Action seader_t1_handle_block( t1->recv_pcb = seader_t1_next_pcb(t1->recv_pcb); if(t1->rx_buffer == NULL) { t1->rx_buffer = bit_buffer_alloc(512); + if(t1->rx_buffer == NULL) { + return SeaderT1ActionResourceExhausted; + } } bit_buffer_append_bytes(t1->rx_buffer, payload + 3, LEN); return SeaderT1ActionSendAck; diff --git a/t_1_logic.h b/t_1_logic.h index c19875c..b2f6d29 100644 --- a/t_1_logic.h +++ b/t_1_logic.h @@ -29,6 +29,7 @@ typedef enum { SeaderT1ActionSendMoreData, SeaderT1ActionRetransmit, SeaderT1ActionSendNak, + SeaderT1ActionResourceExhausted, SeaderT1ActionError, } SeaderT1Action;