Skip to content

TFTView_320x240: infinite reboot loop on boot due to channelGroup out-of-bounds access #304

@bouob

Description

@bouob

Summary

Devices using TFTView_320x240 enter an infinite reboot loop. On boot,
restoreTextMessages() reads a persisted message with ch=31 from flash
without bounds checking, causing channelGroup[31] out-of-bounds access on a
std::array<lv_obj_t*, 8> → crash → immediate reboot → crash again.

Device & Firmware

Field Value
Device Heltec V4 TFT (heltec-v4-tft)
Firmware 2.7.21.1370b23 (also on 2.7.20)
Crash Core 0 panic'ed (StoreProhibited), EXCVADDR 0x00000C20

Serial Log

Original firmware crash (StoreProhibited on boot):

[DeviceUI] LogRotate: found 18 log files using 67480 bytes (65%).
[DeviceUI] newMessage: from:0x50edb985, to:0xffffffff, ch:2, ...
... (dozens of valid ch:2 messages restored, then silent crash)
Guru Meditation Error: Core  0 panic'ed (StoreProhibited). Exception was unhandled.
EXCVADDR: 0x00000c20
rst:0xc (RTC_SW_CPU_RST)

Diagnostic build confirming the infinite reboot loop (NVS reboot counter):

Number of Device Reboots: 347
... Guru Meditation Error: Core  0 panic'ed (LoadProhibited). EXCVADDR: 0x0012003e
rst:0xc (RTC_SW_CPU_RST)

Number of Device Reboots: 348
... Guru Meditation Error: Core  0 panic'ed (LoadProhibited). EXCVADDR: 0x0012003e
rst:0xc (RTC_SW_CPU_RST)

Number of Device Reboots: 349
... (continues indefinitely)

Both crashes originate from the same channelGroup[ch=31] OOB — different
exception types reflect different LVGL code paths hit on each build.

Root Cause

restoreTextMessages() [ViewController.cpp:647]
  → restoreMessage(msg.ch=31)
  → newMessageContainer(ch=31)
      channelGroup[31]  ← OOB on std::array<lv_obj_t*, 8>
  → newMessage(container=NULL)
      lv_obj_create(NULL)  ← StoreProhibited at 0x00000C20

EXCVADDR = NULL + 0xC20 = lv_obj_t.parent, written by lv_obj_create(NULL).
The ch:31 entry is never printed in the log — crash occurs inside
newMessageContainer(ch=31) before any output.

newMessageContainer, newMessage (6-arg), restoreMessage, and showMessages
have no c_max_channels bounds check — even though the pattern is used in 10+
other places in TFTView_320x240.cpp.

How ch=31 Got Into Flash

Older firmware passed the raw MQTT PSK hash as p.channel without remapping to
slot index. MediumFast default PSK (AQ==) hashes to 0x1f = 31. device-ui
stored this in LittleFS.

Firmware 2.7.21 correctly remaps hash → slot index (confirmed via serial:
"Use channel 0 (hash 0x1f)"), so new messages are stored with ch 0–7. However,
historical ch=31 entries persist across firmware upgrades and crash on every boot.

Any ch >= 8 triggers OOB. With custom PSKs, ~97% of hash values fall outside 0–7.

Workaround

Admin → Reset NodeDB (clears the LittleFS message log).

Notes

I have a fix and will open a PR. May also be related to #68.
Related: meshtastic/firmware#9932

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions