Skip to content

fix(tee): harden entry_std parameter handling - #255

Merged
tdrozdovsky merged 1 commit into
Samsung:masterfrom
bmolodan:security/entry-std-hardening
Aug 28, 2026
Merged

tdrozdovsky merged 1 commit into
Samsung:masterfrom
bmolodan:security/entry-std-hardening

Conversation

@bmolodan

Copy link
Copy Markdown
Contributor

Description

Three independent defects on the entry_std.c non-secure entry path, found while
investigating the HOTP key-size issue (#253). None is HOTP-specific; they are split out
from that fix (#254) so each can be reviewed on its own merits.

1. pt[] is used uninitialised. copy_in_params() declares
uint8_t pt[TEE_NUM_PARAMS]; and assigns only pt[0..num_params-1], but all four
entries are read when building ta_param->types. num_params is non-secure
controlled, so a caller sending num_params < 4 hands the TA stale secure-stack bytes
as parameter types — while ta_param->u[] is zeroed, so a TA can observe e.g.
MEMREF_INOUT with {buffer = NULL, size = 0}. Not reachable from the stock client,
which always sends 4, but it is exactly the hostile-caller model #250 addressed. This
touches code introduced in #250. Fixed by initialising to TEE_PARAM_TYPE_NONE (0).

2. Zero-length validation bypass in nsec_check(). The function returned its
pointer unvalidated when len == 0, so a memref of
{buffer = <secure address>, size = 0} reached a TA with a secure pointer intact. The
pointer is not dereferenced by the kernel, but it is handed to the TA, and a TA that
does not consult the size would dereference it. Non-NULL pointers are now
attribution-checked one byte at a time; NULL and legitimate zero-length Non-Secure
memrefs still pass.

3. Dead func snapshot. tee_ioctl_invoke() snapshots func = arg->func but then
passes arg->func, re-reading non-secure memory after validation. Harmless today, but
it defeats the single-fetch discipline established by #250 and leaves an unused
variable. Now passes the snapshot.

Relates to #253.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

Same QEMU harness as the HOTP fix.

Note on coverage: only change 2 is exercised by a test, and then only indirectly via
the AES positive path. Changes 1 and 3 are not directly observable from the non-secure
menu and rest on code review.

Test Configuration:

  • Firmware version: master @ 102d3dc (mTower v0.6.0)
  • Hardware: QEMU mps2-an505 (Cortex-M33); build-only check on numaker_pfm_m2351 (Cortex-M23)
  • Toolchain: gcc-arm-none-eabi-8-2018-q4-major (M33), gcc-arm-none-eabi-6-2017-q2-update (M23)
  • SDK: n/a

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation — N/A
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective (see coverage note above)
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules — N/A

Three independent defects on the Non-Secure entry path, found while
investigating the HOTP key-size overflow. None is HOTP-specific, so they
are split out from that fix.

1. pt[] is used uninitialised. copy_in_params() declares
   uint8_t pt[TEE_NUM_PARAMS] and assigns only pt[0..num_params-1], but
   all four entries are read when building ta_param->types. num_params is
   Non-Secure controlled, so a caller sending num_params < 4 hands the TA
   stale secure-stack bytes as parameter types; ta_param->u[] is zeroed,
   so a TA can observe e.g. MEMREF_INOUT with {buffer = NULL, size = 0}.
   Not reachable from the stock client, which always sends 4, but it is
   the hostile-caller model 27e314c addressed. Initialise to
   TEE_PARAM_TYPE_NONE (0).

2. Zero-length validation bypass in nsec_check(). The function returned
   its pointer unvalidated when len == 0, so a memref of
   {buffer = <secure address>, size = 0} reached a TA with a secure
   pointer intact. The kernel does not dereference it, but the TA might,
   if it does not consult the size. Attribution-check non-NULL pointers
   one byte at a time; NULL and legitimate zero-length Non-Secure memrefs
   still pass.

3. Dead func snapshot. tee_ioctl_invoke() snapshots func = arg->func then
   passes arg->func, re-reading Non-Secure memory after validation.
   Harmless today, but it defeats the single-fetch discipline of 27e314c
   and leaves an unused variable. Pass the snapshot.

Verified under QEMU (mps2-an505, cortex-m33): the NS->S attribution
negative test still rejects all five crafted secure-pointer cases with
ACCESS_DENIED, and the AES example still completes ("Clear text and
decoded text match"), which is the guard against 2 over-tightening
nsec_check. numaker_pfm_m2351 (armv8-m.base) builds clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bmolodan
bmolodan requested a review from tdrozdovsky as a code owner August 25, 2026 14:37
@tdrozdovsky
tdrozdovsky merged commit 7d41b40 into Samsung:master Aug 28, 2026
8 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.

2 participants