Skip to content

fix(hotp): bound Non-Secure key size before copy into K[] - #254

Merged
tdrozdovsky merged 1 commit into
Samsung:masterfrom
bmolodan:security/hotp-key-overflow
Aug 28, 2026
Merged

tdrozdovsky merged 1 commit into
Samsung:masterfrom
bmolodan:security/hotp-key-overflow

Conversation

@bmolodan

Copy link
Copy Markdown
Contributor

Description

register_shared_key() in the HOTP TA copied params[0].memref.size bytes into the
fixed 64-byte secure buffer K[] without bounding the length. That size is supplied
verbatim by the non-secure caller, so any NS caller could overflow a secure .bss
buffer with attacker-controlled data.

This is not covered by the pointer-attribution hardening merged in #250:
nsec_check() proves the source range is Non-Secure resident, which is a different
property from "the source fits in the destination". A legitimate 4 KB Non-Secure
buffer passes that check exactly as designed and is then copied into 64 bytes.

The right bounds check already existed in this file (MIN_KEY_SIZE/MAX_KEY_SIZE in
hmac_sha1()), but on the GET_HOTP path — a later, separate invocation, long after
the corruption. This moves the identical rejection to before the copy. Rejecting rather
than truncating matches apps/aes/ta/aes_ta.c:251 and avoids silently accepting a
shortened HMAC key.

Two smaller defects in the same function are fixed alongside:

  • The DMSG traced K with %s — it printed the HMAC shared secret to the
    console, and a key of exactly 64 bytes leaves no NUL for %s to stop at, so it also
    over-read past the array. It now traces only the length.
  • get_hotp() called truncate_() without checking res from hmac_sha1(), so a
    failed HMAC truncated uninitialised stack.

Fixes #253

Type of change

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

Not a compatibility break: a key outside MIN_KEY_SIZE..MAX_KEY_SIZE already failed at
hmac_sha1() on the subsequent GET_HOTP, so no previously-working flow is rejected.

How Has This Been Tested?

Reproducible QEMU harness, run in a container so the build is deterministic.

  • Test A — negative. Invoke TA_HOTP_CMD_REGISTER_SHARED_KEY with a valid
    Non-Secure source buffer at sizes 4096, 65 (sizeof(K)+1) and 4 (< MIN_KEY_SIZE).
    All three now return 0xFFFF0006 (TEE_ERROR_BAD_PARAMETERS).
  • Test B — positive. The legitimate 20-byte RFC 4226 key still registers
    (0x00000000) and GET_HOTP still returns the specified vector value 755224.
    This also demonstrates the secure world survived the rejected attempts.

Control run with this patch reverted, same harness and same kernel: the 4096-byte
request returned 0x00000000 (TEE_SUCCESS) and the secure world then stopped
producing output — the remaining checks never executed. So the test genuinely detects
the defect rather than passing vacuously.

The temporary test app used for this is intentionally not included in this PR,
following the precedent of 102d3dc; it can be provided on request.

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 (temporary harness, not included per 102d3dc precedent)
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules — N/A

register_shared_key() copied params[0].memref.size bytes into the fixed
64-byte secure buffer K[] without bounding the length. That size is
supplied verbatim by the Non-Secure caller, so any NS caller could
overflow a secure .bss buffer with attacker-controlled data.

This is not covered by the pointer-attribution hardening in 27e314c:
nsec_check() proves the source range is Non-Secure resident, which is a
different property from "the source fits in the destination". A
legitimate 4 KB Non-Secure buffer passes that check exactly as designed
and is then copied into 64 bytes.

The correct bounds check already existed in this file (MIN_KEY_SIZE /
MAX_KEY_SIZE in hmac_sha1()) but ran on the GET_HOTP path, a later and
separate invocation, long after the corruption. Move the identical
rejection ahead of the copy. Reject rather than truncate, matching
apps/aes/ta/aes_ta.c: a silently shortened HMAC key would be a
cryptographic defect of its own. Not a compatibility break, since such a
key already failed later at hmac_sha1().

Two further defects in the same code:
 - DMSG traced K with "%s": it printed the HMAC shared secret to the
   console, and a key of exactly sizeof(K) leaves no NUL for %s to stop
   at, so it also over-read past the array. Trace only the length.
 - get_hotp() called truncate_() without checking res from hmac_sha1(),
   truncating uninitialised stack when the HMAC failed.

Verified under QEMU (mps2-an505, cortex-m33): sizes 4096, 65 and 4 are
now rejected with TEE_ERROR_BAD_PARAMETERS, the 20-byte RFC4226 key still
registers and GET_HOTP still yields the specified value 755224. With this
patch reverted the 4096-byte request returns TEE_SUCCESS and the secure
world then stops responding.

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 d71301d 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.

[SECURITY] Unbounded Non-Secure key size in HOTP TA -> secure-world buffer overflow (hotp_ta.c)

2 participants