Skip to content

LVGL terminal rendering, elf memleak fixes and posix support - #669

Merged
KenVanHoeylandt merged 3 commits into
mainfrom
develop
Oct 3, 2026
Merged

KenVanHoeylandt merged 3 commits into
mainfrom
develop

Conversation

@KenVanHoeylandt

@KenVanHoeylandt KenVanHoeylandt commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Apps now support POSIX-style signals, including signal handling, interrupted reads and waits, and process ID lookups.
    • The terminal can run in an LVGL window or fullscreen, with keyboard input focused while the window is shown.
    • App memory allocations are tracked, with outstanding allocations reported when an app exits.
  • Bug Fixes
    • Shell-launched apps receive a hangup signal when shell input closes.
    • App installation documentation now uses the updated tactility.py install syntax.
    • ELF loading errors now note that insufficient memory may be the cause.
    • T-Deck key input now correctly reports the Enter key.

@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

📝 Walkthrough

Walkthrough

The app module adds signal delivery, signal-aware libc calls, and per-app allocation accounting. POSIX and ESP32 wrappers connect these features to app binaries, and tests cover signal handling and interrupted calls. The terminal gains an LVGL canvas renderer, renderer visibility handling, and window-mode selection. The ideas document updates a TODO item and an app installation example. Other changes update shell shutdown handling, a T-Deck key mapping, and an ESP32 relocation-failure log.

Priority: ➖ Normal

Merge Risk: 🟡 Moderate · up to 4a3fc

Apps may not respond promptly to signals, and terminal shutdown can race with window updates. Correct the signal paths, renderer cleanup, API documentation, and test synchronization before merging.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 4a3fc

The new windowed terminal has a shutdown/rebuild race that can access freed memory and disrupt the shared UI. Signal delivery also changes cross-app behavior, while some blocking calls remain outside its promised interruption coverage. Remote exploitation or privilege escalation has not been established.

Retained concerns

  • Medium · reliability · inferred: The new renderer releases the LVGL lock before freeing and clearing canvasBuffer_, while its window remains registered for rebuild callbacks. Concurrent removal of an upper window can call attachCanvas() on another thread and dereference the buffer during or after its release. This breaks shared-buffer ownership and can disrupt the shared UI through a memory-safety failure. The I/O-task completion wait does not serialize these callbacks; exploitability beyond disruption is unproven.
Security review details

Security Blast Radius

  • inferred — The inspected signal API can affect unrelated live app instances within the runtime, including default termination of unsubscribed targets. The renderer concern reaches shared UI memory through local window transitions. Neither path establishes remote, cross-tenant, credential, or cross-environment exposure.

Security Findings and Attack Paths

  • inferred — A hidden terminal can finish shutdown while another thread removes the window above it. Because renderer teardown precedes unregistering the terminal window, the resulting rebuild can dereference the released PixelBuffer. This is a statically supported memory-lifecycle concern; a successful exploit, controlled memory corruption, or privilege gain was not demonstrated.

Trust Boundaries and Controls

  • observed — Signal handlers remain target-owned and execute during target-task delivery. Ledger locking prevents pending-state access after ledger removal. These identity and synchronization controls do not constitute sender authorization, but the existing exported stop and close APIs are counterevidence to treating numeric app IDs as a previously enforced isolation boundary.

Resilience and Maintainability Implications

  • observed — The terminal waits for its rendering task before freeing renderer resources, which contains that task's access but not window rebuild callbacks. Separately, native-only poll and ignored close-event emission failures existed before this PR; the new signal mechanism should not be interpreted as proving forced or universally bounded shutdown.

Hardening Proposals

  • proposed — Make buffer publication and retirement participate in the same synchronization and lifecycle protocol as canvas callbacks, and prevent reattachment once retirement begins. Define app-to-app authority and blocking-call interruption scope explicitly before treating signals as an isolation or bounded-cleanup guarantee.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.62% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 145 functions across 33 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly names the main changes: LVGL terminal rendering, ELF memory-leak fixes, and POSIX support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dbc50197-5480-4b15-a8c3-9491e1b67d62
📥 Commits

Reviewing files that changed from the base of the PR and between 5bac1ad and b489e99.

📒 Files selected for processing (33)
  • Documentation/ideas.md
  • Modules/app-esp32-module/source/app_esp32_loader_service.cpp
  • Modules/app-esp32-module/source/app_symbols.cpp
  • Modules/app-esp32-module/source/module.cpp
  • Modules/app-module/include/app/event.h
  • Modules/app-module/include/app/libc.h
  • Modules/app-module/include/app/memory.h
  • Modules/app-module/include/app/signal.h
  • Modules/app-module/private/app/private/ledger.h
  • Modules/app-module/source/libc.cpp
  • Modules/app-module/source/module.cpp
  • Modules/app-module/source/scheduler.cpp
  • Modules/app-module/source/signal.cpp
  • Modules/app-module/source/stream.cpp
  • Modules/app-posix-module/private/app_posix/malloc_wrap.h
  • Modules/app-posix-module/private/app_posix/stdio_wrap.h
  • Modules/app-posix-module/source/app_posix_loader_service.cpp
  • Modules/app-posix-module/source/malloc_wrap.cpp
  • Modules/app-posix-module/source/stdio_wrap.cpp
  • Modules/app-posix-module/source/stdio_wrap_apple.cpp
  • Modules/app-posix-module/source/stdio_wrap_elf.cpp
  • Modules/app-posix-module/tests/source/libc_test.cpp
  • Tactility/Private/Tactility/app/terminal/KeyboardInput.h
  • Tactility/Private/Tactility/app/terminal/Terminal.h
  • Tactility/Private/Tactility/app/terminal/TerminalRenderer.h
  • Tactility/Private/Tactility/app/terminal/TerminalRendererLvgl.h
  • Tactility/Source/app/shell/Run.cpp
  • Tactility/Source/app/terminal/KeyboardInput.cpp
  • Tactility/Source/app/terminal/Shell.cpp
  • Tactility/Source/app/terminal/Terminal.cpp
  • Tactility/Source/app/terminal/TerminalRenderer.cpp
  • Tactility/Source/app/terminal/TerminalRendererLvgl.cpp
  • Tactility/Source/app/terminal/main.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread Modules/app-module/source/libc.cpp
Comment thread Modules/app-module/source/signal.cpp
Comment thread Tactility/Source/app/terminal/TerminalRendererLvgl.cpp Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (6)

🟡 Minor · Deliver pending signals in getpid and getppid. · stdio_wrap.cpp:223-235

Modules/app-posix-module/source/stdio_wrap.cpp:223-235
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Deliver pending signals in getpid and getppid.

When another app sends SIGTERM to a non-event app, the signal is queued. The target’s next getpid() or getppid() can return without delivering it, so the default termination action is delayed past the documented next libc call. Call app_signal_deliver_pending() at the entry of both wrappers.

Suggested fix
 pid_t __wrap_getpid() {
+    app_signal_deliver_pending();
     int result;
     if (app_libc_try_getpid(&result)) {
         return result;
@@
 pid_t __wrap_getppid() {
+    app_signal_deliver_pending();
     int result;
     if (app_libc_try_getppid(&result)) {
         return result;
🟡 Minor · Check pending app signals during the real-poll fallback. · stdio_wrap.cpp:175-185

Modules/app-posix-module/source/stdio_wrap.cpp:175-185
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Check pending app signals during the real-poll fallback.

When app_libc_try_poll() finds no app-owned descriptor, it returns false, and this branch calls __real_poll() with the full timeout. app_signal_send() only sets the unsubscribed app’s pending bit; it does not wake this wait. With an infinite timeout, the app can remain blocked after a signal arrives instead of returning -1/EINTR within the documented ~100 ms. Bound this fallback’s waits and check and deliver pending signals between them.

🟡 Minor · Document sleep()'s remaining-seconds return value. · signal.h:20-22

Modules/app-module/include/app/signal.h:20-22
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document sleep()'s remaining-seconds return value.

When a pending signal interrupts sleep(), the app libc returns the whole seconds not slept, not -1 with errno set to EINTR. Replace sleep with usleep in the -1/EINTR statement and document sleep()'s separate return value.

Suggested fix
- * app_signal_deliver_pending()). A blocking read, write, poll or sleep it is waiting in returns -1
+ * app_signal_deliver_pending()). A blocking read, write, poll or usleep call returns -1
  * with errno EINTR within about 100 ms.
+ * A blocked sleep() call returns the number of whole seconds not slept.
🟡 Minor · Clear canvasBuffer_ before releasing the LVGL lock. · TerminalRendererLvgl.cpp:54-74

Tactility/Source/app/terminal/TerminalRendererLvgl.cpp:54-74
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Clear canvasBuffer_ before releasing the LVGL lock.

If app_manager_stop() ends the terminal while an upper window is being removed, the window manager can rebuild the hidden terminal’s widgets on the remover thread and call attachCanvas(). end() releases the LVGL lock before freeing and nulling the plain canvasBuffer_, while attachCanvas() reads it. These accesses can race. Capture and clear the pointer under the lock, then free the captured buffer after unlocking.

Suggested fix
         lvgl_lock();
+        auto* buffer = canvasBuffer_;
+        canvasBuffer_ = nullptr;
         lv_obj_t* canvas = canvas_;
         if (canvas != nullptr) {
             lv_obj_delete(canvas);
             canvas_ = nullptr;
         }
         lvgl_unlock();

-        pixel_buffer_free(canvasBuffer_);
-        canvasBuffer_ = nullptr;
+        pixel_buffer_free(buffer);
🟡 Minor · Synchronize on entry to the blocking wait. · libc_test.cpp:543-557

Modules/app-posix-module/tests/source/libc_test.cpp:543-557
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Synchronize on entry to the blocking wait.

The app sets g_signal_app_blocked before calling read, usleep, or poll. The 50 ms delay does not prove that the call has started. If the task is delayed after setting the flag, the test can send SIGUSR1 before wrapper entry. The wrapper can then deliver and clear the handled signal before the underlying wait, which may block normally. The subsequent wait_for_state(..., APP_INSTANCE_STATE_STOPPED, 1000) can time out and fail the test. Replace the fixed delay with a handshake that observes entry into the underlying wait.

🟡 Minor · Deliver pending signals in both ESP32 PID bindings. · app_symbols.cpp:81-89

Modules/app-esp32-module/source/app_symbols.cpp:81-89
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Deliver pending signals in both ESP32 PID bindings.

When an unsubscribed app has SIGTERM pending, these bindings return without calling app_signal_deliver_pending(). getpid() and getppid() can therefore return without delivering SIGTERM, so the app can continue past its next libc call with SIGTERM still pending. Call app_signal_deliver_pending() at the entry of both ESP32 bindings.

Suggested fix
 pid_t app_getpid() {
+    app_signal_deliver_pending();
     int result;
     return app_libc_try_getpid(&result) ? result : 0;
 }
 
 pid_t app_getppid() {
+    app_signal_deliver_pending();
     int result;
     return app_libc_try_getppid(&result) ? result : 0;
 }

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9e9e0146-323c-4358-b9b1-b75a1523e1d9
📥 Commits

Reviewing files that changed from the base of the PR and between b489e99 and 4a3fcb7.

📒 Files selected for processing (5)
  • Drivers/lilygo-module/source/tdeck_keyboard.cpp
  • Modules/app-module/source/libc.cpp
  • Modules/app-module/source/signal.cpp
  • Modules/app-posix-module/tests/source/libc_test.cpp
  • Tactility/Source/app/terminal/TerminalRendererLvgl.cpp
🚧 Files skipped from review as they are similar to previous changes (4)
  • Tactility/Source/app/terminal/TerminalRendererLvgl.cpp
  • Modules/app-module/source/signal.cpp
  • Modules/app-posix-module/tests/source/libc_test.cpp
  • Modules/app-module/source/libc.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

@KenVanHoeylandt
KenVanHoeylandt merged commit 34ca63e into main Oct 3, 2026
65 checks passed
@KenVanHoeylandt
KenVanHoeylandt deleted the develop branch October 3, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant