Skip to content

Commit e74c62d

Browse files
committed
fix(fl): add icache invalidate after upload/write to prevent stale code execution
Upload and write commands only flushed dcache but did not invalidate icache. On platforms with instruction cache (Cortex-M7, etc.), the CPU could fetch stale instructions from icache after code was uploaded to RAM, causing Precise data bus errors when executing patched functions via dpatch/tpatch. - Add fl_invalidate_icache_cb_t callback and invalidate_icache_cb field - Call fl_invalidate_icache() after fl_flush_dcache() in upload/write - Register up_invalidate_icache in NuttX port - Add 7 test cases for dcache flush and icache invalidate verification
1 parent 81dfbc1 commit e74c62d

8 files changed

Lines changed: 218 additions & 0 deletions

File tree

App/func_loader/fl.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ void fl_flush_dcache(fl_context_t* ctx, const void* addr, size_t len) {
7171
}
7272
}
7373

74+
void fl_invalidate_icache(fl_context_t* ctx, const void* addr, size_t len) {
75+
if (ctx->invalidate_icache_cb) {
76+
ctx->invalidate_icache_cb((uintptr_t)addr, (uintptr_t)addr + len);
77+
}
78+
}
79+
7480
/**
7581
* @brief Check if [addr, addr+len) is a safe memory range
7682
* @note Rejects NULL pointer and address overflow (wrapping past 0xFFFFFFFF)

App/func_loader/fl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ typedef void (*fl_output_cb_t)(void* user, const char* str);
5555
typedef void* (*fl_malloc_cb_t)(size_t size);
5656
typedef void (*fl_free_cb_t)(void* ptr);
5757
typedef void (*fl_flush_dcache_cb_t)(uintptr_t start, uintptr_t end);
58+
typedef void (*fl_invalidate_icache_cb_t)(uintptr_t start, uintptr_t end);
5859

5960
/**
6061
* @brief Slot state for tracking injection info
@@ -84,6 +85,9 @@ typedef struct fl_context_s {
8485
/* Cache flush callback (optional, for platforms with dcache) */
8586
fl_flush_dcache_cb_t flush_dcache_cb;
8687

88+
/* ICache invalidate callback (optional, for platforms with icache) */
89+
fl_invalidate_icache_cb_t invalidate_icache_cb;
90+
8791
/* Internal state (managed by fl_init) */
8892
bool is_inited; /* true after first fl_init() call */
8993
uintptr_t last_alloc; /* Last dynamic allocation address */

App/func_loader/fl_cmd.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ bool fl_verify_crc(int crc, uint16_t calc);
7878
*/
7979
void fl_flush_dcache(fl_context_t* ctx, const void* addr, size_t len);
8080

81+
/**
82+
* @brief Invalidate instruction cache for a memory region
83+
*/
84+
void fl_invalidate_icache(fl_context_t* ctx, const void* addr, size_t len);
85+
8186
/**
8287
* @brief Check if [addr, addr+len) is a safe memory range
8388
* @return true if the range is safe to access

App/func_loader/fl_cmd_mem.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ fl_error_t fl_cmd_upload(fl_context_t* ctx, const cmd_args_t* args) {
121121
/* Flush data cache after upload to ensure code is visible to CPU */
122122
fl_flush_dcache(ctx, dest, n);
123123

124+
/* Invalidate instruction cache to prevent stale code execution */
125+
fl_invalidate_icache(ctx, dest, n);
126+
124127
fl_response(true, "Uploaded %d bytes to 0x%lX", n, (unsigned long)dest);
125128
return FL_OK;
126129
}
@@ -218,6 +221,9 @@ fl_error_t fl_cmd_write(fl_context_t* ctx, const cmd_args_t* args) {
218221
/* Flush data cache */
219222
fl_flush_dcache(ctx, dest, n);
220223

224+
/* Invalidate instruction cache */
225+
fl_invalidate_icache(ctx, dest, n);
226+
221227
fl_response(true, "WRITE %d bytes to 0x%lX", n, (unsigned long)args->addr);
222228
return FL_OK;
223229
}

App/func_loader/fl_port_nuttx.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ static void nuttx_flush_dcache_cb(uintptr_t start, uintptr_t end) {
8383
up_flush_dcache(start, end);
8484
}
8585

86+
/* Invalidate icache callback */
87+
static void nuttx_invalidate_icache_cb(uintptr_t start, uintptr_t end) {
88+
up_invalidate_icache(start, end);
89+
}
90+
8691
/* ==========================================================================
8792
* Memory Allocation Configuration
8893
* ========================================================================== */
@@ -211,6 +216,7 @@ int main(int argc, char** argv) {
211216
fl_init_default(&ctx);
212217
ctx.output_cb = nuttx_output_cb;
213218
ctx.flush_dcache_cb = nuttx_flush_dcache_cb;
219+
ctx.invalidate_icache_cb = nuttx_invalidate_icache_cb;
214220

215221
/* Initialize allocator */
216222
nuttx_alloc_init();

App/tests/mock_hardware.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,19 @@ void mock_free(void* ptr) {
171171
(void)ptr; /* Simple bump allocator, no actual free */
172172
g_mock_call_stats.free_count++;
173173
}
174+
175+
/* ============================================================================
176+
* Mock Cache Callbacks
177+
* ============================================================================ */
178+
179+
void mock_flush_dcache(uintptr_t start, uintptr_t end) {
180+
g_mock_call_stats.flush_dcache_count++;
181+
g_mock_call_stats.last_dcache_start = start;
182+
g_mock_call_stats.last_dcache_end = end;
183+
}
184+
185+
void mock_invalidate_icache(uintptr_t start, uintptr_t end) {
186+
g_mock_call_stats.invalidate_icache_count++;
187+
g_mock_call_stats.last_icache_start = start;
188+
g_mock_call_stats.last_icache_end = end;
189+
}

App/tests/mock_hardware.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ typedef struct {
5858
uint32_t free_count;
5959
size_t total_allocated;
6060
size_t total_freed;
61+
uint32_t flush_dcache_count;
62+
uint32_t invalidate_icache_count;
63+
uintptr_t last_dcache_start;
64+
uintptr_t last_dcache_end;
65+
uintptr_t last_icache_start;
66+
uintptr_t last_icache_end;
6167
} mock_call_stats_t;
6268

6369
extern mock_call_stats_t g_mock_call_stats;
@@ -112,6 +118,13 @@ void mock_heap_reset(void);
112118
void* mock_malloc(size_t size);
113119
void mock_free(void* ptr);
114120

121+
/* ============================================================================
122+
* Mock Cache Callbacks (for fl_context_t)
123+
* ============================================================================ */
124+
125+
void mock_flush_dcache(uintptr_t start, uintptr_t end);
126+
void mock_invalidate_icache(uintptr_t start, uintptr_t end);
127+
115128
#ifdef __cplusplus
116129
}
117130
#endif

App/tests/test_fl.c

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2040,6 +2040,158 @@ void test_loader_cmd_enable_crc_mismatch(void) {
20402040
TEST_ASSERT(mock_output_contains("CRC mismatch"));
20412041
}
20422042

2043+
/* ============================================================================
2044+
* Cache Flush / ICache Invalidate Tests
2045+
* ============================================================================ */
2046+
2047+
static void setup_loader_with_cache(void) {
2048+
setup_loader();
2049+
test_ctx.flush_dcache_cb = mock_flush_dcache;
2050+
test_ctx.invalidate_icache_cb = mock_invalidate_icache;
2051+
}
2052+
2053+
void test_loader_upload_flushes_dcache(void) {
2054+
setup_loader_with_cache();
2055+
fl_init(&test_ctx);
2056+
2057+
const char* alloc_argv[] = {"fl", "--cmd", "alloc", "--size", "64"};
2058+
fl_exec_cmd(&test_ctx, 5, alloc_argv);
2059+
2060+
mock_reset_call_stats();
2061+
2062+
const char* argv[] = {"fl", "--cmd", "upload", "--addr", "0", "--data", "AQIDBA=="};
2063+
fl_error_t result = fl_exec_cmd(&test_ctx, 7, argv);
2064+
2065+
TEST_ASSERT_EQUAL(FL_OK, result);
2066+
const mock_call_stats_t* stats = mock_get_call_stats();
2067+
TEST_ASSERT(stats->flush_dcache_count >= 1);
2068+
TEST_ASSERT(stats->last_dcache_start != 0);
2069+
TEST_ASSERT(stats->last_dcache_end > stats->last_dcache_start);
2070+
}
2071+
2072+
void test_loader_upload_invalidates_icache(void) {
2073+
setup_loader_with_cache();
2074+
fl_init(&test_ctx);
2075+
2076+
const char* alloc_argv[] = {"fl", "--cmd", "alloc", "--size", "64"};
2077+
fl_exec_cmd(&test_ctx, 5, alloc_argv);
2078+
2079+
mock_reset_call_stats();
2080+
2081+
const char* argv[] = {"fl", "--cmd", "upload", "--addr", "0", "--data", "AQIDBA=="};
2082+
fl_error_t result = fl_exec_cmd(&test_ctx, 7, argv);
2083+
2084+
TEST_ASSERT_EQUAL(FL_OK, result);
2085+
const mock_call_stats_t* stats = mock_get_call_stats();
2086+
TEST_ASSERT(stats->invalidate_icache_count >= 1);
2087+
TEST_ASSERT(stats->last_icache_start != 0);
2088+
TEST_ASSERT(stats->last_icache_end > stats->last_icache_start);
2089+
}
2090+
2091+
void test_loader_upload_cache_range_matches(void) {
2092+
setup_loader_with_cache();
2093+
fl_init(&test_ctx);
2094+
2095+
const char* alloc_argv[] = {"fl", "--cmd", "alloc", "--size", "64"};
2096+
fl_exec_cmd(&test_ctx, 5, alloc_argv);
2097+
2098+
uintptr_t alloc_addr = test_ctx.last_alloc;
2099+
mock_reset_call_stats();
2100+
2101+
/* Upload 4 bytes at offset 0 */
2102+
const char* argv[] = {"fl", "--cmd", "upload", "--addr", "0", "--data", "AQIDBA=="};
2103+
fl_exec_cmd(&test_ctx, 7, argv);
2104+
2105+
const mock_call_stats_t* stats = mock_get_call_stats();
2106+
/* dcache and icache should cover the same range */
2107+
TEST_ASSERT_EQUAL_HEX(alloc_addr, stats->last_dcache_start);
2108+
TEST_ASSERT_EQUAL_HEX(alloc_addr + 4, stats->last_dcache_end);
2109+
TEST_ASSERT_EQUAL_HEX(alloc_addr, stats->last_icache_start);
2110+
TEST_ASSERT_EQUAL_HEX(alloc_addr + 4, stats->last_icache_end);
2111+
}
2112+
2113+
void test_loader_write_flushes_dcache(void) {
2114+
setup_loader_with_cache();
2115+
fl_init(&test_ctx);
2116+
2117+
const char* alloc_argv[] = {"fl", "--cmd", "alloc", "--size", "64"};
2118+
fl_exec_cmd(&test_ctx, 5, alloc_argv);
2119+
2120+
uintptr_t alloc_addr = test_ctx.last_alloc;
2121+
char addr_str[32];
2122+
snprintf(addr_str, sizeof(addr_str), "0x%lX", (unsigned long)alloc_addr);
2123+
2124+
mock_reset_call_stats();
2125+
2126+
const char* argv[] = {"fl", "--cmd", "write", "--addr", addr_str, "--data", "AQIDBA=="};
2127+
fl_error_t result = fl_exec_cmd(&test_ctx, 7, argv);
2128+
2129+
TEST_ASSERT_EQUAL(FL_OK, result);
2130+
const mock_call_stats_t* stats = mock_get_call_stats();
2131+
TEST_ASSERT(stats->flush_dcache_count >= 1);
2132+
}
2133+
2134+
void test_loader_write_invalidates_icache(void) {
2135+
setup_loader_with_cache();
2136+
fl_init(&test_ctx);
2137+
2138+
const char* alloc_argv[] = {"fl", "--cmd", "alloc", "--size", "64"};
2139+
fl_exec_cmd(&test_ctx, 5, alloc_argv);
2140+
2141+
uintptr_t alloc_addr = test_ctx.last_alloc;
2142+
char addr_str[32];
2143+
snprintf(addr_str, sizeof(addr_str), "0x%lX", (unsigned long)alloc_addr);
2144+
2145+
mock_reset_call_stats();
2146+
2147+
const char* argv[] = {"fl", "--cmd", "write", "--addr", addr_str, "--data", "AQIDBA=="};
2148+
fl_error_t result = fl_exec_cmd(&test_ctx, 7, argv);
2149+
2150+
TEST_ASSERT_EQUAL(FL_OK, result);
2151+
const mock_call_stats_t* stats = mock_get_call_stats();
2152+
TEST_ASSERT(stats->invalidate_icache_count >= 1);
2153+
}
2154+
2155+
void test_loader_no_cache_cb_no_crash(void) {
2156+
/* Without cache callbacks set, upload/write should still work */
2157+
setup_loader();
2158+
fl_init(&test_ctx);
2159+
2160+
/* Ensure no cache callbacks */
2161+
TEST_ASSERT(test_ctx.flush_dcache_cb == NULL);
2162+
TEST_ASSERT(test_ctx.invalidate_icache_cb == NULL);
2163+
2164+
const char* alloc_argv[] = {"fl", "--cmd", "alloc", "--size", "64"};
2165+
fl_exec_cmd(&test_ctx, 5, alloc_argv);
2166+
2167+
const char* argv[] = {"fl", "--cmd", "upload", "--addr", "0", "--data", "AQIDBA=="};
2168+
fl_error_t result = fl_exec_cmd(&test_ctx, 7, argv);
2169+
2170+
TEST_ASSERT_EQUAL(FL_OK, result);
2171+
}
2172+
2173+
void test_loader_upload_offset_cache_range(void) {
2174+
setup_loader_with_cache();
2175+
fl_init(&test_ctx);
2176+
2177+
const char* alloc_argv[] = {"fl", "--cmd", "alloc", "--size", "128"};
2178+
fl_exec_cmd(&test_ctx, 5, alloc_argv);
2179+
2180+
uintptr_t alloc_addr = test_ctx.last_alloc;
2181+
mock_reset_call_stats();
2182+
2183+
/* Upload 4 bytes at offset 16 */
2184+
const char* argv[] = {"fl", "--cmd", "upload", "--addr", "16", "--data", "AQIDBA=="};
2185+
fl_exec_cmd(&test_ctx, 7, argv);
2186+
2187+
const mock_call_stats_t* stats = mock_get_call_stats();
2188+
/* Cache range should be alloc_addr+16 to alloc_addr+20 */
2189+
TEST_ASSERT_EQUAL_HEX(alloc_addr + 16, stats->last_dcache_start);
2190+
TEST_ASSERT_EQUAL_HEX(alloc_addr + 20, stats->last_dcache_end);
2191+
TEST_ASSERT_EQUAL_HEX(alloc_addr + 16, stats->last_icache_start);
2192+
TEST_ASSERT_EQUAL_HEX(alloc_addr + 20, stats->last_icache_end);
2193+
}
2194+
20432195
/* ============================================================================
20442196
* Test Runner
20452197
* ============================================================================ */
@@ -2196,4 +2348,14 @@ void run_loader_tests(void) {
21962348
RUN_TEST(test_loader_cmd_enable_with_crc);
21972349
RUN_TEST(test_loader_cmd_enable_crc_mismatch);
21982350
TEST_SUITE_END();
2351+
2352+
TEST_SUITE_BEGIN("func_loader - Cache Flush / ICache Invalidate");
2353+
RUN_TEST(test_loader_upload_flushes_dcache);
2354+
RUN_TEST(test_loader_upload_invalidates_icache);
2355+
RUN_TEST(test_loader_upload_cache_range_matches);
2356+
RUN_TEST(test_loader_write_flushes_dcache);
2357+
RUN_TEST(test_loader_write_invalidates_icache);
2358+
RUN_TEST(test_loader_no_cache_cb_no_crash);
2359+
RUN_TEST(test_loader_upload_offset_cache_range);
2360+
TEST_SUITE_END();
21992361
}

0 commit comments

Comments
 (0)