libjade: create gui task if so configured
What changed, and why it matters
This commit refactors how Blockstream Jade's firmware simulator (libjade) handles tasks when a GUI is enabled. It moves FreeRTOS task emulation into a separate file and uses real POSIX threads instead of running everything synchronously. There is no direct evidence in the commit or supplied references that this fixes a security vulnerability; it appears to be a feature/enablement change for the simulator's GUI mode.
No security action required. Treat as normal code review for simulator build correctness, thread safety, and resource cleanup in the new pthread shims.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch extracts FreeRTOS task shims from libjade.c into a new task.c, adds pthread-based implementations of xTaskCreatePinnedToCore, vTaskDelay, vTaskDelete, etc., and removes the CONFIG_LIBJADE_NO_GUI guard around GUI task creation in gui.c. It also adds a direct esp_lcd_init(NULL) call in display_hw.c for the libjade build. The changes enable the GUI task to run as a real thread in the simulator when CONFIG_LIBJADE_NO_GUI is not set. No security bug, buffer overflow, use-after-free, cryptographic flaw, or privilege issue is visible in the diff.
Changed components
libjade simulator task emulationmain/gui.c GUI task creationmain/display_hw.c LCD initializationInspect captured patch +215 / −76
diff --git a/libjade/include/esp_log.h b/libjade/include/esp_log.h
index 13595ae..03ca45d 100644
--- a/libjade/include/esp_log.h
+++ b/libjade/include/esp_log.h
@@ -1,6 +1,7 @@
#ifndef __LIBJADE_ESP_LOG__
#define __LIBJADE_ESP_LOG__ 1
+#include "sdkconfig.h"
#include <stdio.h>
typedef enum {
diff --git a/libjade/include/freertos/task.h b/libjade/include/freertos/task.h
index 9283734..ffd29ae 100644
--- a/libjade/include/freertos/task.h
+++ b/libjade/include/freertos/task.h
@@ -2,10 +2,13 @@
#define _LIBJADE_FREERTOS_TASK_H 1
#include <freertos/projdefs.h>
+#include <pthread.h>
#include <stddef.h>
#include <stdint.h>
-typedef uint32_t BaseType_t; // Probably belongs elsewhere
+// Probably belongs elsewhere
+typedef uint32_t BaseType_t;
+typedef uint32_t UBaseType_t;
typedef void* TaskHandle_t;
typedef void (*TaskFunction_t)(void* arg);
@@ -22,21 +25,25 @@ void vTaskSetThreadLocalStoragePointerAndDelCallback(void* task, size_t idx, voi
const char* pcTaskGetName(void* task);
-TaskHandle_t xTaskGetCurrentTaskHandle(void) { return NULL; }
+TaskHandle_t xTaskGetCurrentTaskHandle(void);
BaseType_t xTaskCreatePinnedToCore(TaskFunction_t func, const char* name, uint32_t stack_size, void* params,
uint32_t ux_prio, TaskHandle_t* output, uint32_t xCoreID);
+BaseType_t xTaskCreatePinnedToCoreWithCaps(TaskFunction_t func, const char* const name, uint32_t stack_size,
+ void* const params, UBaseType_t ux_prio, TaskHandle_t* const output, const BaseType_t xCoreID,
+ UBaseType_t uxMemoryCaps);
-unsigned int uxTaskPriorityGet(void* task) { return 0; }
+unsigned int uxTaskPriorityGet(void* task);
unsigned int uxTaskGetStackHighWaterMark(void* task);
-unsigned int xPortGetCoreID(void) { return 0; }
+unsigned int xPortGetCoreID(void);
unsigned int xPortGetFreeHeapSize(void);
#define portTICK_PERIOD_MS 1
void vTaskDelay(TickType_t delay);
void vTaskDelayUntil(TickType_t* prev_wake_time, const TickType_t delay);
void vTaskDelete(void* task);
+void vTaskDeleteWithCaps(void* task);
TickType_t xTaskGetTickCount(void);
#define eNoAction 0
diff --git a/libjade/libjade.c b/libjade/libjade.c
index a8e8e0c..9ef709c 100644
--- a/libjade/libjade.c
+++ b/libjade/libjade.c
@@ -18,7 +18,6 @@
#undef _GNU_SOURCE
#include "sdkconfig.h"
-#include "freertos/timecvt.h"
#include "libjade.h"
#include "icons.inc"
@@ -52,6 +51,8 @@
// https://github.com/richgel999/miniz with a couple of additional
// patches for memory safety.
#include "miniz.c"
+// Include the emulation of the o/s task functions
+#include "task.c"
// Include the esp32_deflate component
#define ESP_PLATFORM 1
#define ESP_IDF_VERSION 1
@@ -212,75 +213,6 @@ bool run_in_temporary_task(const size_t stack_size, temporary_stack_function_t f
void temp_stack_init(void) {}
-// HW: TLS/Sensitive
-static void* _tls_ptrs[3];
-
-void* pvTaskGetThreadLocalStoragePointer(void* task, size_t idx)
-{
- assert(idx <= sizeof(_tls_ptrs) / sizeof(_tls_ptrs[0]));
- return _tls_ptrs[idx];
-}
-
-void vTaskSetThreadLocalStoragePointerAndDelCallback(void* task, size_t idx, void* p, TlsDeleteCallbackFunction_t cb)
-{
- assert(idx <= sizeof(_tls_ptrs) / sizeof(_tls_ptrs[0]));
- _tls_ptrs[idx] = p;
- // FIXME: call cb atexit()/thread exit?
-}
-
-const char* pcTaskGetName(void* task) { return "shim_task"; }
-
-BaseType_t xTaskCreatePinnedToCore(TaskFunction_t func, const char* name, uint32_t stack_size, void* params,
- uint32_t ux_prio, TaskHandle_t* output, uint32_t xCoreID)
-{
- *output = NULL;
- func(params);
- return pdTRUE;
-}
-
-unsigned int uxTaskGetStackHighWaterMark(void* task) { return 0xffffff; }
-
-unsigned int xPortGetFreeHeapSize(void) { return 0xffffff; }
-
-void vTaskDelay(TickType_t delay)
-{
-#ifdef CONFIG_LIBJADE_NO_GUI
- // Don't delay, since we don't have multiple threads running
- // in the firmware to wait on.
-#else
- struct timespec ts = timespec_from_ticktype(delay);
- nanosleep(&ts, NULL);
-#endif
-}
-
-void vTaskDelayUntil(TickType_t* prev_wake_time, const TickType_t delay)
-{
-#ifndef CONFIG_LIBJADE_NO_GUI
- // Only used by the GUI main loop
- TickType_t current_time = xTaskGetTickCount();
- if (*prev_wake_time + delay > current_time) {
- vTaskDelay(*prev_wake_time + delay - current_time);
- }
- *prev_wake_time += delay;
-#endif
-}
-
-void vTaskDelete(void* task)
-{
- // Don't delete, since we didn't create any task
-}
-
-TickType_t xTaskGetTickCount(void)
-{
- struct timespec ts;
- if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
- abort();
- }
- return ((TickType_t)ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
-}
-
-int xTaskNotify(TaskHandle_t task, unsigned int v, int action) { return pdTRUE; }
-
void sensitive_init(void) {}
void sensitive_push(const char* file, int line, void* addr, size_t size) {}
diff --git a/libjade/task.c b/libjade/task.c
new file mode 100644
index 0000000..dc50d4c
--- /dev/null
+++ b/libjade/task.c
@@ -0,0 +1,199 @@
+#define _GNU_SOURCE 1 // For extra pthread functions
+#include "freertos/timecvt.h"
+#include "jade_assert.h"
+#include "sdkconfig.h"
+#include <limits.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <time.h>
+
+#ifndef CONFIG_LIBJADE_NO_GUI
+// variables to help implement vTaskDelete
+// TODO: move to thread local storage so less chance of interference between threads
+static pthread_mutex_t _task_delay_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t _task_delay_cond = PTHREAD_COND_INITIALIZER;
+#endif
+
+// HW: TLS/Sensitive
+static void* _tls_ptrs[3];
+
+void* pvTaskGetThreadLocalStoragePointer(void* task, size_t idx)
+{
+ JADE_ASSERT(idx <= sizeof(_tls_ptrs) / sizeof(_tls_ptrs[0]));
+ return _tls_ptrs[idx];
+}
+
+void vTaskSetThreadLocalStoragePointerAndDelCallback(void* task, size_t idx, void* p, TlsDeleteCallbackFunction_t cb)
+{
+ JADE_ASSERT(idx <= sizeof(_tls_ptrs) / sizeof(_tls_ptrs[0]));
+ _tls_ptrs[idx] = p;
+ // FIXME: call cb atexit()/thread exit?
+}
+
+const char* pcTaskGetName(void* task)
+{
+ JADE_ASSERT(task == NULL); // Only ever called for the current task
+#if 0
+ // TODO: Implement if desired - only used for logging ATM
+ char name[32];
+ pthread_t thread_id = (pthread_t)task;
+ JADE_ASSERT(pthread_getname_np(thread_id, name, sizeof(name)) == 0);
+ return name;
+#endif
+ return "dummy";
+}
+
+TaskHandle_t xTaskGetCurrentTaskHandle(void) { return (TaskHandle_t)pthread_self(); }
+
+typedef struct {
+ TaskFunction_t func;
+ void* arg;
+} pthread_shim_args_t;
+
+void* pthread_shim_func(void* arg)
+{
+ pthread_shim_args_t* args = (pthread_shim_args_t*)arg;
+ TaskFunction_t func = args->func;
+ void* func_arg = args->arg;
+ free(args);
+ func(func_arg);
+ return NULL;
+}
+
+BaseType_t xTaskCreatePinnedToCore(TaskFunction_t func, const char* name, uint32_t stack_size, void* params,
+ uint32_t ux_prio, TaskHandle_t* output, uint32_t xCoreID)
+{
+#ifdef CONFIG_LIBJADE_NO_GUI
+ func(params);
+ return pdTRUE;
+#else
+ BaseType_t result = pdTRUE;
+ pthread_attr_t attr = { 0 };
+ pthread_t thread_id = 0;
+ *output = NULL;
+ if (pthread_attr_init(&attr) != 0) {
+ JADE_LOGE("pthread_attr_init failed for task %s", name);
+ return pdFALSE;
+ }
+ if (stack_size < PTHREAD_STACK_MIN) {
+ stack_size = PTHREAD_STACK_MIN;
+ }
+ if (pthread_attr_setstacksize(&attr, stack_size) != 0) {
+ JADE_LOGE("pthread_attr_setstacksize failed for task %s", name);
+ result = pdFALSE;
+ goto cleanup;
+ }
+ JADE_LOGI("creating pthread shim args");
+ pthread_shim_args_t* shim_args = malloc(sizeof(pthread_shim_args_t));
+ JADE_ASSERT(shim_args);
+ shim_args->func = func;
+ shim_args->arg = params;
+ JADE_LOGI("calling pthread_create");
+ if (pthread_create(&thread_id, &attr, pthread_shim_func, shim_args) != 0) {
+ free(shim_args);
+ JADE_LOGE("pthread_create failed for task %s", name);
+ result = pdFALSE;
+ goto cleanup;
+ }
+ *output = (TaskHandle_t)thread_id;
+ if (pthread_setname_np(thread_id, name) != 0) {
+ JADE_LOGE("pthread_setname_np failed for task %s", name);
+ result = pdFALSE;
+ goto cleanup;
+ }
+cleanup:
+ if (thread_id != 0) {
+ pthread_attr_destroy(&attr);
+ }
+ if (result != pdTRUE && thread_id != 0) {
+ pthread_kill(thread_id, SIGTERM);
+ *output = NULL;
+ }
+ return result;
+#endif
+}
+
+BaseType_t xTaskCreatePinnedToCoreWithCaps(TaskFunction_t func, const char* const name, uint32_t stack_size,
+ void* const params, UBaseType_t ux_prio, TaskHandle_t* const output, const BaseType_t xCoreID,
+ UBaseType_t uxMemoryCaps)
+{
+ // We ignore the memory caps
+ return xTaskCreatePinnedToCore(func, name, stack_size, params, ux_prio, output, xCoreID);
+}
+
+unsigned int uxTaskPriorityGet(void* task) { return 0; }
+
+unsigned int uxTaskGetStackHighWaterMark(void* task) { return 0xffffff; }
+
+unsigned int xPortGetCoreID(void) { return 0; }
+
+unsigned int xPortGetFreeHeapSize(void) { return 0xffffff; }
+
+void vTaskDelay(TickType_t delay)
+{
+#ifdef CONFIG_LIBJADE_NO_GUI
+ // Don't delay, since we don't have multiple threads running
+ // in the firmware to wait on.
+#else
+ // if portMAX_DELAY we will make the thread listen for a signal to exit instead of sleeping,
+ if (delay == portMAX_DELAY) {
+ pthread_mutex_lock(&_task_delay_mutex);
+ pthread_cond_wait(&_task_delay_cond, &_task_delay_mutex);
+ pthread_mutex_unlock(&_task_delay_mutex);
+ // jade often uses vTaskDelay(portMAX_DELAY) in a loop so we need to exit the thread here
+ pthread_exit(NULL);
+ return;
+ }
+ // otherwise sleep as normal
+ struct timespec ts = timespec_from_ticktype(delay);
+ nanosleep(&ts, NULL);
+#endif
+}
+
+void vTaskDelayUntil(TickType_t* prev_wake_time, const TickType_t delay)
+{
+#ifndef CONFIG_LIBJADE_NO_GUI
+ // Only used by the GUI main loop
+ TickType_t current_time = xTaskGetTickCount();
+ if (*prev_wake_time + delay > current_time) {
+ vTaskDelay(*prev_wake_time + delay - current_time);
+ }
+ *prev_wake_time += delay;
+#endif
+}
+
+void vTaskDelete(void* task)
+{
+#ifdef CONFIG_LIBJADE_NO_GUI
+ // Don't delete, since we didn't create any tasks
+#else
+ if (task == NULL) {
+ pthread_exit(NULL);
+ } else {
+ // use pthread_cond_signal
+ pthread_mutex_lock(&_task_delay_mutex);
+ pthread_cond_signal(&_task_delay_cond);
+ pthread_mutex_unlock(&_task_delay_mutex);
+ }
+#endif
+}
+
+void vTaskDeleteWithCaps(void* task) { vTaskDelete(task); }
+
+TickType_t xTaskGetTickCount(void)
+{
+ struct timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
+ abort();
+ }
+ return ((TickType_t)ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
+}
+
+int xTaskNotify(TaskHandle_t task, unsigned int v, int action)
+{
+ // FIXME: Implement?
+ JADE_ASSERT(action == eNoAction);
+ return pdTRUE;
+}
diff --git a/main/display_hw.c b/main/display_hw.c
index 83a91cb..69559f8 100644
--- a/main/display_hw.c
+++ b/main/display_hw.c
@@ -266,6 +266,8 @@ void display_hw_init(TaskHandle_t* gui_handle)
xSemaphoreTake(init_done, portMAX_DELAY);
vTaskDelete(lcdInitTaskHandle);
vSemaphoreDelete(init_done);
+#else
+ esp_lcd_init(NULL); // Call directly
#endif // CONFIG_LIBJADE
}
diff --git a/main/gui.c b/main/gui.c
index 194714a..2be7fd0 100644
--- a/main/gui.c
+++ b/main/gui.c
@@ -287,13 +287,11 @@ void gui_init(TaskHandle_t* gui_h)
// Create status-bar
make_status_bar();
-#ifndef CONFIG_LIBJADE
// Create (high priority) gui task
BaseType_t retval
= xTaskCreatePinnedToCore(gui_task, "gui", 3 * 1024 + 128, NULL, JADE_TASK_PRIO_GUI, gui_h, JADE_CORE_GUI);
gui_task_handle = gui_h;
JADE_ASSERT_MSG(retval == pdPASS, "Failed to create GUI task, xTaskCreatePinnedToCore() returned %d", retval);
-#endif // CONFIG_LIBJADE
}
#ifdef CONFIG_LIBJADE
Why this scored 11/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.