Skip to content

Commit 4e29364

Browse files
committed
extmod/zephyr_kernel: Fix GC canary corruption with protected pointer.
The thread list contained interior pointers (th at offset +8) rather than the full mp_thread_protected_t allocation start. When GC scanned these interior pointers, it marked the mp_thread_t structure but not the preceding canary_before field at offset 0. This caused GC to incorrectly treat the allocation as starting at offset 8, allowing heap reuse to corrupt the canary during concurrent gc.collect(). Fix by adding protected back-pointer to mp_thread_t that references the full mp_thread_protected_t allocation. GC now scans this pointer in mp_thread_gc_others(), ensuring the entire structure including canaries is marked and protected from collection. Also initialize all mp_thread_t fields before adding to thread list, and add threads to list immediately after allocation (before k_thread_create) to prevent mid-initialization GC from collecting the structure. Tested: Concurrent gc.collect() from 3 threads completes without canary corruption (previously failed consistently). Signed-off-by: Andrew Leech <[email protected]>
1 parent 2ed7e57 commit 4e29364

File tree

1 file changed

+30
-3
lines changed

1 file changed

+30
-3
lines changed

extmod/zephyr_kernel/kernel/mpthread_zephyr.c

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ typedef struct _mp_thread_stack_slot_t {
7070
bool used;
7171
} mp_thread_stack_slot_t;
7272

73+
// Forward declaration for circular reference
74+
typedef struct _mp_thread_protected_t mp_thread_protected_t;
75+
7376
// Linked list node per active thread
7477
typedef struct _mp_thread_t {
7578
k_tid_t id; // Zephyr thread ID
@@ -80,6 +83,7 @@ typedef struct _mp_thread_t {
8083
void *arg; // Python args (GC root pointer)
8184
void *stack; // Stack pointer
8285
size_t stack_len; // Stack size in words
86+
mp_thread_protected_t *protected; // Back-reference to protected structure (GC root)
8387
struct _mp_thread_t *next; // Next in linked list
8488
} mp_thread_t;
8589

@@ -205,6 +209,7 @@ bool mp_thread_init(void *stack) {
205209
th->status = MP_THREAD_STATUS_READY;
206210
th->alive = 1;
207211
th->arg = NULL;
212+
th->protected = protected; // Back-reference for GC
208213

209214
// Get main thread's stack info from Zephyr (matches ports/zephyr pattern)
210215
// The main thread was created by Zephyr before mp_thread_init(), so we
@@ -328,6 +333,7 @@ void mp_thread_gc_others(void) {
328333

329334
gc_collect_root((void **)&th, 1);
330335
gc_collect_root(&th->arg, 1);
336+
gc_collect_root((void **)&th->protected, 1); // Mark full protected structure
331337

332338
if (th->id == k_current_get()) {
333339
continue; // Don't scan current thread's stack (done separately)
@@ -433,10 +439,30 @@ mp_uint_t mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_s
433439
protected->canary_after = CANARY_AFTER;
434440
mp_thread_t *th = &protected->thread;
435441

442+
// Initialize ALL fields to safe values before adding to list
443+
// This prevents GC from scanning uninitialized memory as pointers
444+
th->id = NULL;
445+
th->status = 0; // Will be set to MP_THREAD_STATUS_CREATED later
446+
th->alive = 0;
447+
th->slot = -1; // Will be set to actual slot later
448+
th->arg = NULL; // CRITICAL: GC scans this, must not be garbage
449+
th->stack = NULL;
450+
th->stack_len = 0; // GC will skip stack scan until this is set
451+
th->protected = protected; // Back-reference for GC (CRITICAL)
452+
th->next = NULL;
453+
436454
mp_thread_mutex_lock(&thread_mutex, 1);
437455

456+
// Add to list IMMEDIATELY after allocation and before full initialization
457+
// This protects the thread from being collected as garbage if gc_collect()
458+
// is triggered during the rest of initialization (e.g., in k_thread_create)
459+
th->next = MP_STATE_VM(mp_thread_list_head);
460+
MP_STATE_VM(mp_thread_list_head) = th;
461+
438462
int32_t _slot = mp_thread_find_stack_slot();
439463
if (_slot < 0) {
464+
// Remove from list before failing
465+
MP_STATE_VM(mp_thread_list_head) = th->next;
440466
mp_thread_mutex_unlock(&thread_mutex);
441467
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("maximum number of threads reached"));
442468
}
@@ -456,6 +482,8 @@ mp_uint_t mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_s
456482
);
457483

458484
if (th->id == NULL) {
485+
// Remove from list before failing
486+
MP_STATE_VM(mp_thread_list_head) = th->next;
459487
mp_thread_mutex_unlock(&thread_mutex);
460488
// Memory will be reclaimed by GC
461489
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("can't create thread"));
@@ -474,16 +502,15 @@ mp_uint_t mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_s
474502
name, allocated_stack_size, th->z_thread.stack_info.size);
475503
}
476504

477-
// Add to linked list
505+
// Complete initialization (thread already in list from earlier)
478506
th->status = MP_THREAD_STATUS_CREATED;
479507
th->alive = 0;
480508
th->slot = _slot;
481509
th->arg = arg;
482510
th->stack = (void *)th->z_thread.stack_info.start;
483511
// Use allocated size (validated against stack_info above)
484512
th->stack_len = allocated_stack_size / sizeof(uintptr_t);
485-
th->next = MP_STATE_VM(mp_thread_list_head);
486-
MP_STATE_VM(mp_thread_list_head) = th;
513+
// Note: th->next already set when added to list earlier
487514

488515
stack_slot[_slot].used = true;
489516
mp_thread_counter++;

0 commit comments

Comments
 (0)