Skip to content

loader: enable stack protection and null pointer detection - #692

Merged
pennam merged 8 commits into
arduino:mainfrom
pillo79:security
Oct 20, 2026
Merged

pennam merged 8 commits into
arduino:mainfrom
pillo79:security

Conversation

@pillo79

@pillo79 pillo79 commented Oct 13, 2026

Copy link
Copy Markdown

Enable Zephyr's runtime memory-safety checks in the loader (loader/prj.conf):

  • CONFIG_HW_STACK_PROTECTION=y – hardware stack overflow detection (PSPLIM/MSPLIM on ARMv8-M, MPU guard on ARMv7-M variants with the MPU on)
  • CONFIG_STACK_CANARIES=y – compiler stack canaries
  • CONFIG_NULL_POINTER_EXCEPTION_DETECTION_DWT=y – DWT watchpoint on the null page

Kconfig turns these off where the hardware can't support them. That gives a warning, not a build failure:

Variant Not available Reason
nano33ble, nicla_sense HW stack protection ARMv7-M needs the MPU; these keep CONFIG_ARM_MPU=n
nano_connect (RP2040) HW stack protection, canaries, DWT no MPU in use, no entropy source, no DWT
ek_ra8d1 DWT null pointer detection SoC does not declare a DWT

CONFIG_STACK_CANARIES_TLS was considered and left out: Cortex-M does not provide ARCH_HAS_STACK_CANARIES_TLS.

Dependency

On STM32U5/H5 (UNO Q, VENTUNO Q), the DWT option needs the Zephyr fix "arch: arm: cortex_m: fix DWT COMP1 clash with vendor macros", now on zephyr-arduino-v4.4.2. Without it, debug.c fails with expected identifier before '(' token.

Testing

extra/build_all.sh -f against zephyr-arduino-v4.4.2 @ 1050db55acd: all 14 variants build.

@github-actions

Copy link
Copy Markdown

Built 1.0.1-0.pr.692.261013t0934z+gcf018dca

CI run PASSED 🟢

ArtifactBoardCoreTestsRAMSketchesWarningsErrors
✅ zephyr_contrib ek_ra8d1 2 🏷️ ✅

12.0%

2--
frdm_mcxn947 7 🏷️ ✅

53.4%

2--
frdm_rw612 3 🏷️ ✅

76.5%

2--
✅* zephyr_main giga 10 🏷️ ✅*

45.9%

862-
nano33ble 6 🏷️ ✅

78.8%

44--
nano_connect 8 🏷️ ✅*

68.9%

386-
nano_matter 3 🏷️ ✅

⚠️ 86.5%

22--
nicla_sense 4 🏷️ ✅

⚠️ 88.5%

18--
nicla_vision 11 🏷️ ✅

40.3%

68--
opta 11 🏷️ ✅

42.3%

76--
portentac33 6 🏷️ ✅

⚠️ 100.0%

72--
portentah7 10 🏷️ ✅*

51.2%

1028-
✅ zephyr_unoq unoq 2 🏷️ ✅

43.5%

56--
ventunoq 5 🏷️ ✅

23.7%

48--
Legend

BoardTestStatus description
🔥 🔥 Test run failed to complete.
❌ 🔴 Test completed with unexpected errors.
⁉️ ⁉️ Test was expected to fail but passed; exception entry is outdated.
✔️* 🚫 Test completed with errors, but all are known/expected.
✅* 🟡 Test completed with some warnings; no errors detected.
✅ 🟢 Test passed successfully, with no warnings or errors.
🌑 🌑 Test was skipped.

@dani-clo
dani-clo force-pushed the security branch 2 times, most recently from d53b475 to ffd0a58 Compare October 13, 2026 15:36
@gibix

gibix commented Oct 14, 2026

Copy link
Copy Markdown
Member

I suggest to put some kind of warning in the changelog for this for:

  • expected increase of flash footprint
  • the canary protection that we implement with CONFIG_STACK_CANARIES is just fstack-protector, this can impact the performance of user sketch that have functions that calls locally allocated char array. For example ArduinoIoTCloud-Basic has 103 protected functions out of 1110 (9.3%).

Use "reset halt" instead of separate "reset" and "halt" commands in
the sketch and bootloader flashing scripts.

The separate commands allow Zephyr to start before the CPU is halted,
leaving CPU state that causes the RAM-resident flash algorithm to time
out. Halting during reset prevents this.
Use "reset halt" instead of separate "reset" and "halt" commands in
the sketch and bootloader flashing scripts.

The separate commands allow Zephyr to start before the CPU is halted,
leaving CPU state that causes the RAM-resident flash algorithm to time
out. Halting during reset prevents this.
dani-clo and others added 4 commits October 15, 2026 16:41
…e size

Avoid accessing address 0 by comparing memory starting at 0x400. This
avoids the DWT null-pointer detection region.

Also remove the memory flush logic, because the code was never compiled
(the C33 did not have DCACHE, ICACHE or MPU in its final .config).
Enable HW_STACK_PROTECTION and NULL_POINTER_EXCEPTION_DETECTION_DWT in
the loader Kconfig.

Kconfig drops them where unsupported: hardware stack protection needs
the built-in stack guard (ARMv8-M) or the MPU, so it stays off on the
M4/M0+ variants that keep CONFIG_ARM_MPU=n; DWT null pointer detection
is unavailable on cores without DWT (RP2040, RA8D1).

Building the DWT option on STM32U5/H5 requires the Zephyr fix
"arch: arm: cortex_m: fix DWT COMP1 clash with vendor macros".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
These variants kept the loader default CONFIG_ARM_MPU=n, so
CONFIG_HW_STACK_PROTECTION was dropped by Kconfig: on ARMv7-M and
ARMv6-M it needs the MPU stack guard. Enable the MPU so they get
MPU_STACK_GUARD like the H7 variants. disable_mpu_rasr_xn() already
keeps RAM executable for LLEXT sketches when the MPU is on.

Remove disable_bootloader_mpu() for nano33ble: it ran at PRE_KERNEL_1,
after Zephyr's own MPU init, and cleared MPU->CTRL, which would turn
the stack guard back off. Zephyr already disables and reprograms the
MPU left enabled by the bootloader.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Most variants now enable the MPU, so make CONFIG_ARM_MPU=y the loader
default and drop the per-variant CONFIG_ARM_MPU=y lines. Variants that
do not use the MPU yet (nano_matter, ek_ra8d1, frdm_mcxn947,
frdm_rw612; portentac33, unoq and ventunoq already did) now set
CONFIG_ARM_MPU=n explicitly, with a note on why: all are ARMv8-M and
get HW_STACK_PROTECTION from the built-in stack guard.

No functional change: the generated .config is identical for all
boards in boards.txt.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@pillo79

pillo79 commented Oct 16, 2026

Copy link
Copy Markdown
Author

v2:

  • removed STACK_CANARIES after internal discussion
  • enabled MPU on nano33ble, nicla_sense and nano_connect
  • make ARM_MPU=y the project default

@github-actions

Copy link
Copy Markdown

Built 1.0.1-0.pr.692.261016t1335z+gd5f8e406

CI run PASSED 🟢

ArtifactBoardCoreTestsRAMSketchesWarningsErrors
✅ zephyr_contrib ek_ra8d1 2 🏷️ ✅

12.0%

2--
frdm_mcxn947 7 🏷️ ✅

53.4%

2--
frdm_rw612 3 🏷️ ✅

76.5%

2--
✅* zephyr_main giga 10 🏷️ ✅*

45.9%

862-
nano33ble 5 🏷️ ✅

79.2%

44--
nano_connect 6 🏷️ ✅

69.5%

38--
nano_matter 3 🏷️ ✅

⚠️ 86.6%

22--
nicla_sense 3 🏷️ ✅

⚠️ 89.0%

18--
nicla_vision 11 🏷️ ✅

40.4%

68--
opta 11 🏷️ ✅

42.3%

76--
portentac33 6 🏷️ ✅

⚠️ 100.0%

72--
portentah7 10 🏷️ ✅*

51.2%

1022-
✅ zephyr_unoq unoq 2 🏷️ ✅

43.5%

56--
ventunoq 5 🏷️ ✅

23.7%

48--
Legend

BoardTestStatus description
🔥 🔥 Test run failed to complete.
❌ 🔴 Test completed with unexpected errors.
⁉️ ⁉️ Test was expected to fail but passed; exception entry is outdated.
✔️* 🚫 Test completed with errors, but all are known/expected.
✅* 🟡 Test completed with some warnings; no errors detected.
✅ 🟢 Test passed successfully, with no warnings or errors.
🌑 🌑 Test was skipped.

With CONFIG_ASSERT=y the k_current_get() inline asserts
!k_is_pre_kernel(), which reads the kernel-internal z_sys_post_kernel.
That symbol is not exported to sketches, so any sketch calling
k_current_get() failed to link with "undefined reference to
`z_sys_post_kernel'".
OpenOCD sets DHCSR.C_DEBUGEN when it connects and never clears it on
shutdown; it stays set until a power-on reset. With halting debug
enabled, a DWT watchpoint hit (NULL_POINTER_EXCEPTION_DETECTION_DWT)
halts the core instead of raising the DebugMonitor exception, so a null
pointer access freezes the board with no fault dump and no error LED.

Replace the final "reset"/"reset run" with "reset halt" + "resume", so
OpenOCD finishes its reset handling before the firmware runs, and clear
C_DEBUGEN before shutdown.
@pennam
pennam requested a review from facchinm October 19, 2026 15:55
@github-actions

Copy link
Copy Markdown

Built 1.0.1-0.pr.692.261019t1552z+g63c3c3ee

CI run PASSED 🟢

ArtifactBoardCoreTestsRAMSketchesWarningsErrors
✅ zephyr_contrib ek_ra8d1 2 🏷️ ✅

12.0%

2--
frdm_mcxn947 7 🏷️ ✅

53.4%

2--
frdm_rw612 3 🏷️ ✅

74.6%

2--
✅* zephyr_main giga 10 🏷️ ✅*

45.9%

862-
nano33ble 5 🏷️ ✅

79.2%

44--
nano_connect 6 🏷️ ✅

69.5%

38--
nano_matter 3 🏷️ ✅

⚠️ 86.5%

22--
nicla_sense 3 🏷️ ✅

⚠️ 89.0%

18--
nicla_vision 11 🏷️ ✅

40.4%

68--
opta 11 🏷️ ✅

42.3%

76--
portentac33 6 🏷️ ✅

⚠️ 100.0%

72--
portentah7 10 🏷️ ✅*

51.2%

1022-
✅ zephyr_unoq unoq 2 🏷️ ✅

43.5%

56--
ventunoq 5 🏷️ ✅

23.7%

48--
Legend

BoardTestStatus description
🔥 🔥 Test run failed to complete.
❌ 🔴 Test completed with unexpected errors.
⁉️ ⁉️ Test was expected to fail but passed; exception entry is outdated.
✔️* 🚫 Test completed with errors, but all are known/expected.
✅* 🟡 Test completed with some warnings; no errors detected.
✅ 🟢 Test passed successfully, with no warnings or errors.
🌑 🌑 Test was skipped.

@pennam
pennam merged commit fa9bfbe into arduino:main Oct 20, 2026
58 checks passed
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.

5 participants