gui/camera: stop the gui task from possibly trying to render an image from a camera task stack that no longer exists
What changed, and why it matters
This commit fixes a race condition in the Blockstream Jade hardware wallet's camera and screen rendering code. When the camera task finished, its stack memory could be freed while the GUI task was still trying to draw the last camera image, potentially causing a crash or corrupted display. The fix adds a new function that safely clears the picture reference under a lock before the camera task's memory is released.
Treat as a stability and potential security fix. Review whether other tasks pass stack-allocated data to the GUI and ensure similar clearing is applied. Verify that `gui_clear_picture` is called in all camera exit paths, including error paths, and consider whether a NULL picture causes safe no-op rendering in `display_picture()`.
Security signals we found
Use-after-free / dangling pointer in concurrent GUI/camera task interaction
Missing synchronization when camera task memory is freed while GUI may still render it
Stack-allocated Picture object lifetime exceeds owning task lifetime
Crash or memory corruption risk in embedded firmware display path
Evidence from the diff
The patch addresses a use-after-free / dangling-pointer race between jade_camera_task and the GUI task. The camera task stack-allocated a Picture (pic) pointing to an image_buffer, and passed it to the GUI via gui_update_picture(). After the camera loop exited, the task popped and freed image_buffer, then awaited death. Meanwhile, the GUI task could still call display_picture() using node->picture->picture, which now pointed to freed stack memory. The fix introduces gui_clear_picture(), which takes gui_mutex and sets node->picture->picture = NULL without repainting, called before the camera task’s stack is reclaimed. This prevents the GUI from rendering from invalid memory.
Changed components
main/camera.cmain/gui.cmain/gui.hjade_camera_taskgui_update_picture / display_picturegui_mutexInspect captured patch +16 / −0
diff --git a/main/camera.c b/main/camera.c
index e2e826c..6a19be2 100644
--- a/main/camera.c
+++ b/main/camera.c
@@ -497,6 +497,9 @@ static void jade_camera_task(void* data)
if (camera_config->show_ui) {
SENSITIVE_POP(image_buffer);
free(image_buffer);
+ // Null picture under gui_mutex so the GUI task cannot call display_picture()
+ // with our stack-allocated 'pic' after this task's stack is freed.
+ gui_clear_picture(image_node);
}
camera_post_exit_event_and_await_death();
}
diff --git a/main/gui.c b/main/gui.c
index b44afa5..5dd2f35 100644
--- a/main/gui.c
+++ b/main/gui.c
@@ -1722,6 +1722,18 @@ void gui_update_icon(gui_view_node_t* node, const Icon icon, const bool repaint_
}
}
+// Takes the gui_mutex and nulls the picture pointer without repainting.
+// Use this to safely detach a Picture whose backing data is about to be freed.
+void gui_clear_picture(gui_view_node_t* node)
+{
+ JADE_ASSERT(node);
+ JADE_ASSERT(node->kind == PICTURE);
+
+ JADE_SEMAPHORE_TAKE(gui_mutex);
+ node->picture->picture = NULL;
+ JADE_SEMAPHORE_GIVE(gui_mutex);
+}
+
// Takes the gui_mutex, updates the picture, and then only draws the
// updated item if it is part of the 'current activity'.
void gui_update_picture(gui_view_node_t* node, const Picture* picture, const bool repaint_parent)
diff --git a/main/gui.h b/main/gui.h
index 6ec2e75..bfa40de 100644
--- a/main/gui.h
+++ b/main/gui.h
@@ -458,6 +458,7 @@ void gui_set_text_default_font(gui_view_node_t* node);
void gui_update_text(gui_view_node_t* node, const char* text);
void gui_update_icon(gui_view_node_t* node, Icon icon, bool repaint_parent);
void gui_update_picture(gui_view_node_t* node, const Picture* picture, bool repaint_parent);
+void gui_clear_picture(gui_view_node_t* node);
void gui_repaint(gui_view_node_t* node);
void gui_set_current_activity_ex(gui_activity_t* new_current, bool free_managed_activities);
Why this scored 59/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.