Skip to content

Commit bcee122

Browse files
Merge pull request #473 from keepkey/fix/7142-consolidated-audit-remediation
fix(security): consolidate 7.14.2 audit remediation
2 parents 2a430da + d91aa93 commit bcee122

25 files changed

Lines changed: 551 additions & 155 deletions

include/keepkey/firmware/app_confirm.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
#define CONFIRM_SIGN_IDENTITY_TITLE 32
3030
#define CONFIRM_SIGN_IDENTITY_BODY 416
31+
#define CONFIRM_SIGN_IDENTITY_KEY 96
3132

3233
bool confirm_cipher(bool encrypt, const char* key);
3334
bool confirm_encrypt_msg(const char* msg, bool signing);
@@ -46,7 +47,11 @@ bool confirm_transaction(const char* total_amount, const char* fee);
4647
bool confirm_load_device(bool is_node);
4748
bool confirm_address(const char* desc, const char* address);
4849
bool confirm_xpub(const char* node_str, const char* xpub);
49-
bool confirm_sign_identity(const IdentityType* identity, const char* challenge);
50+
bool format_sign_identity_key_selection(const IdentityType* identity,
51+
const char* curve, char* out,
52+
size_t out_len);
53+
bool confirm_sign_identity(const IdentityType* identity, const char* challenge,
54+
const char* curve);
5055

5156
/**
5257
* Render the largest screen-sized prefix of a byte string.

include/keepkey/firmware/signing.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
void signing_init(const SignTx* msg, const CoinType* _coin,
2929
const HDNode* _root);
3030
void signing_abort(void);
31+
bool signing_is_active(void);
3132
void signing_txack(TransactionType* tx);
3233
void send_fsm_co_error_message(int co_error);
3334

lib/board/confirm_sm.c

Lines changed: 68 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,18 @@ extern bool reset_msg_stack;
5252

5353
static CONFIDENTIAL char strbuf[BODY_CHAR_MAX];
5454

55-
/* Set by format_body() when the formatted body did not fit strbuf, i.e. when
56-
* characters were lost before any screen existed to show them. Read and
57-
* cleared by confirm_helper(). Truncation here is invisible to every later
58-
* check: what reaches the renderer is a complete, well-formed, shorter string,
59-
* so the screen looks correct and is not. */
60-
static bool body_truncated = false;
61-
62-
/* The single place a host-supplied body is formatted. vsnprintf() returns the
63-
* length it WOULD have written, which is the only chance to notice that
64-
* strbuf was too small -- after this, the evidence is gone. */
65-
static void format_body(const char* request_body, va_list vl) {
66-
const int needed = vsnprintf(strbuf, sizeof(strbuf), request_body, vl);
67-
body_truncated = (needed < 0) || ((size_t)needed >= sizeof(strbuf));
55+
/* vsnprintf() returns the length it WOULD have written. Treat anything that
56+
* did not fit as a refusal: once characters are lost, no renderer or pager can
57+
* recover them and there is no complete body the user can approve. */
58+
static bool format_body_into(char* out, size_t out_len,
59+
const char* request_body, va_list vl) {
60+
if (!out || out_len == 0 || !request_body) return false;
61+
const int needed = vsnprintf(out, out_len, request_body, vl);
62+
return needed >= 0 && (size_t)needed < out_len;
63+
}
64+
65+
static bool format_body(const char* request_body, va_list vl) {
66+
return format_body_into(strbuf, sizeof(strbuf), request_body, vl);
6867
}
6968

7069
/// Handler for push button being pressed.
@@ -511,19 +510,14 @@ static bool page_body_confirm(const char* request_title, const char* body,
511510
return ok;
512511
}
513512

514-
/// Show a confirmation, warning first when its body will not fit the screen.
513+
/// Show a confirmation, paging when its complete body will not fit the screen.
515514
///
516515
/// draw_string() draws until a glyph no longer fits the canvas and then simply
517516
/// stops: a body taller than BODY_ROWS is drawn in part, with no ellipsis and
518517
/// nothing to tell the user that the tail of an address, an amount or a
519-
/// warning was dropped. The vsnprintf() into strbuf[BODY_CHAR_MAX] below cuts
520-
/// long host strings a second time, just as quietly.
521-
///
522-
/// So when the body will not fit, put an explicit screen in front of it. That
523-
/// screen costs its own hold, and the hold is a real consent signal: a host
524-
/// Cancel breaks it and the caller reports ActionCancelled, exactly as it
525-
/// would for the body screen. A body that is only partly shown is now never
526-
/// shown without saying so.
518+
/// warning was dropped. Complete formatted bodies are therefore paged here.
519+
/// Source formatting overflow is refused by every public entry point before a
520+
/// ButtonRequest is emitted, because lost source cannot be paged.
527521
///
528522
/// Bodies that fit take exactly the path they took before: one screen, one
529523
/// ButtonRequest, one hold.
@@ -534,70 +528,15 @@ static bool confirm_helper(const char* request_title, const char* request_body,
534528
const uint16_t body_width =
535529
(uint16_t)((iconNum == NO_ICON) ? BODY_WIDTH : BODY_WIDTH_WITH_ICON);
536530

537-
/* Consume the source-completeness latch exactly once, whatever happens
538-
* below: leaving it set would make the NEXT confirmation warn for this
539-
* one's reason. */
540-
const bool truncated = body_truncated;
541-
body_truncated = false;
542-
543-
/* Two independent ways the user can be shown less than what is being
544-
* approved, and they need separate measurements because they happen at
545-
* different times:
546-
*
547-
* SOURCE the formatted body did not fit strbuf. Characters were lost
548-
* before the renderer ever saw them, so no amount of looking
549-
* at the screen can detect it -- only vsnprintf()'s return
550-
* value could, and format_body() kept it.
551-
* RENDER the body reached the renderer intact but did not fit the
552-
* canvas. draw_string_fits() replays the real placement and
553-
* reports whether the last character landed.
554-
*
555-
* Only layout_standard_notification is known to wrap the body at BODY_WIDTH
531+
/* Only layout_standard_notification is known to wrap the body at BODY_WIDTH
556532
* over BODY_ROWS rows. Custom layouts place and size their own body, and
557533
* layout_constant_power_notification draws from x = 128 + LEFT_MARGIN where
558534
* the canvas edge, not BODY_WIDTH, is the limit. Measuring either of those
559-
* against BODY_WIDTH would be wrong, so leave them exactly as they were --
560-
* but a SOURCE truncation is layout-independent and must warn regardless. */
535+
* against BODY_WIDTH would be wrong, so leave them exactly as they were. */
561536
const bool render_incomplete =
562537
(layout_notification_func == &layout_standard_notification) &&
563538
!confirm_body_fits(request_body, body_width);
564539

565-
if (truncated) {
566-
/* SOURCE truncation: characters were lost in vsnprintf() before the
567-
* renderer ever saw them. They cannot be paged, because they do not
568-
* exist any more. Say exactly that -- the old copy promised to show the
569-
* rest on the next hold and then redrew the same clipped body, which is
570-
* worse than not warning at all: a user who read it carefully was
571-
* misled about what they had seen. */
572-
if (!confirm_screen("Cut Off",
573-
"This text is too long to show in full. The rest "
574-
"cannot be displayed. Hold to continue anyway.",
575-
&layout_standard_notification, constant_power, NO_ICON,
576-
immediate)) {
577-
return false;
578-
}
579-
/* The warning CONSUMED the caller's ButtonRequest, so the body screen that
580-
* follows needs one of its own -- otherwise it is a required press the
581-
* host was never told about, and the invariant above is broken on exactly
582-
* the path that added a screen. An interactive user would get through it
583-
* (button_request_acked is still set from the warning's ack), but a
584-
* ButtonRequest-driven client waits forever for a message that never
585-
* comes. Reachable today: confirm_sign_identity() formats into a 416-byte
586-
* body and strbuf is BODY_CHAR_MAX == 352, so a long identity truncates
587-
* and lands here. See #482. */
588-
if (notify_host) {
589-
ButtonRequest body_req;
590-
memset(&body_req, 0, sizeof(body_req));
591-
body_req.has_code = true;
592-
body_req.code = ButtonRequestType_ButtonRequest_Other;
593-
button_request_acked = false;
594-
msg_write(MessageType_MessageType_ButtonRequest, &body_req);
595-
}
596-
return page_body_confirm(request_title, request_body,
597-
layout_notification_func, constant_power, iconNum,
598-
immediate, body_width, notify_host);
599-
}
600-
601540
if (render_incomplete) {
602541
/* RENDER overflow: the body reached the renderer intact, so every
603542
* character is still in hand and can be shown -- on more than one screen.
@@ -617,8 +556,12 @@ bool confirm(ButtonRequestType type, const char* request_title,
617556

618557
va_list vl;
619558
va_start(vl, request_body);
620-
format_body(request_body, vl);
559+
const bool formatted = format_body(request_body, vl);
621560
va_end(vl);
561+
if (!formatted) {
562+
memzero(strbuf, sizeof(strbuf));
563+
return false;
564+
}
622565

623566
/* Send button request */
624567
ButtonRequest resp;
@@ -716,8 +659,12 @@ bool confirm_constant_power(ButtonRequestType type, const char* request_title,
716659

717660
va_list vl;
718661
va_start(vl, request_body);
719-
format_body(request_body, vl);
662+
const bool formatted = format_body(request_body, vl);
720663
va_end(vl);
664+
if (!formatted) {
665+
memzero(strbuf, sizeof(strbuf));
666+
return false;
667+
}
721668

722669
/* Send button request */
723670
ButtonRequest resp;
@@ -740,8 +687,12 @@ bool confirm_with_custom_button_request(const ButtonRequest* button_request,
740687

741688
va_list vl;
742689
va_start(vl, request_body);
743-
format_body(request_body, vl);
690+
const bool formatted = format_body(request_body, vl);
744691
va_end(vl);
692+
if (!formatted) {
693+
memzero(strbuf, sizeof(strbuf));
694+
return false;
695+
}
745696

746697
/* Send button request */
747698
msg_write(MessageType_MessageType_ButtonRequest, button_request);
@@ -766,8 +717,12 @@ bool confirm_with_custom_layout(layout_notification_t layout_notification_func,
766717

767718
va_list vl;
768719
va_start(vl, request_body);
769-
format_body(request_body, vl);
720+
const bool formatted = format_body(request_body, vl);
770721
va_end(vl);
722+
if (!formatted) {
723+
memzero(strbuf, sizeof(strbuf));
724+
return false;
725+
}
771726

772727
/* Send button request */
773728
ButtonRequest resp;
@@ -789,8 +744,12 @@ bool confirm_without_button_request(const char* request_title,
789744

790745
va_list vl;
791746
va_start(vl, request_body);
792-
format_body(request_body, vl);
747+
const bool formatted = format_body(request_body, vl);
793748
va_end(vl);
749+
if (!formatted) {
750+
memzero(strbuf, sizeof(strbuf));
751+
return false;
752+
}
794753

795754
bool ret =
796755
confirm_helper(request_title, strbuf, &layout_standard_notification,
@@ -806,8 +765,12 @@ bool confirm_with_icon(ButtonRequestType type, IconType iconNum,
806765

807766
va_list vl;
808767
va_start(vl, request_body);
809-
format_body(request_body, vl);
768+
const bool formatted = format_body(request_body, vl);
810769
va_end(vl);
770+
if (!formatted) {
771+
memzero(strbuf, sizeof(strbuf));
772+
return false;
773+
}
811774

812775
/* Send button request */
813776
ButtonRequest resp;
@@ -829,8 +792,12 @@ bool review(ButtonRequestType type, const char* request_title,
829792

830793
va_list vl;
831794
va_start(vl, request_body);
832-
format_body(request_body, vl);
795+
const bool formatted = format_body(request_body, vl);
833796
va_end(vl);
797+
if (!formatted) {
798+
memzero(strbuf, sizeof(strbuf));
799+
return false;
800+
}
834801

835802
/* Send button request */
836803
ButtonRequest resp;
@@ -852,8 +819,12 @@ bool review_without_button_request(const char* request_title,
852819

853820
va_list vl;
854821
va_start(vl, request_body);
855-
format_body(request_body, vl);
822+
const bool formatted = format_body(request_body, vl);
856823
va_end(vl);
824+
if (!formatted) {
825+
memzero(strbuf, sizeof(strbuf));
826+
return false;
827+
}
857828

858829
const bool shown =
859830
confirm_helper(request_title, strbuf, &layout_standard_notification,
@@ -869,8 +840,12 @@ bool review_with_icon(ButtonRequestType type, IconType iconNum,
869840

870841
va_list vl;
871842
va_start(vl, request_body);
872-
format_body(request_body, vl);
843+
const bool formatted = format_body(request_body, vl);
873844
va_end(vl);
845+
if (!formatted) {
846+
memzero(strbuf, sizeof(strbuf));
847+
return false;
848+
}
874849

875850
/* Send button request */
876851
ButtonRequest resp;
@@ -892,8 +867,12 @@ bool review_immediate(ButtonRequestType type, const char* request_title,
892867

893868
va_list vl;
894869
va_start(vl, request_body);
895-
format_body(request_body, vl);
870+
const bool formatted = format_body(request_body, vl);
896871
va_end(vl);
872+
if (!formatted) {
873+
memzero(strbuf, sizeof(strbuf));
874+
return false;
875+
}
897876

898877
/* Send button request */
899878
ButtonRequest resp;

lib/firmware/app_confirm.c

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -349,10 +349,42 @@ bool confirm_address(const char* desc, const char* address) {
349349
* true/false of confirmation
350350
*
351351
*/
352-
bool confirm_sign_identity(const IdentityType* identity,
353-
const char* challenge) {
352+
bool format_sign_identity_key_selection(const IdentityType* identity,
353+
const char* curve, char* out,
354+
size_t out_len) {
355+
if (!identity || !curve || !out || out_len == 0) return false;
356+
const int needed = snprintf(
357+
out, out_len, "Index: %" PRIu32 "\nCurve: %s\nPath: %s", identity->index,
358+
curve, (identity->has_path && identity->path[0]) ? "shown next" : "none");
359+
return needed >= 0 && (size_t)needed < out_len;
360+
}
361+
362+
bool confirm_sign_identity(const IdentityType* identity, const char* challenge,
363+
const char* curve) {
354364
char title[CONFIRM_SIGN_IDENTITY_TITLE], body[CONFIRM_SIGN_IDENTITY_BODY];
355365

366+
if (!identity || !curve) return false;
367+
368+
/* These values select the key and signing algorithm. Keep them out of the
369+
* free-form identity body so the maximum-size path can be reviewed by the
370+
* exact-byte pager instead of being shortened by a printf buffer. */
371+
char key_selection[CONFIRM_SIGN_IDENTITY_KEY];
372+
if (!format_sign_identity_key_selection(identity, curve, key_selection,
373+
sizeof(key_selection)) ||
374+
!confirm(ButtonRequestType_ButtonRequest_SignIdentity, "Identity Key",
375+
"%s", key_selection)) {
376+
memzero(key_selection, sizeof(key_selection));
377+
return false;
378+
}
379+
memzero(key_selection, sizeof(key_selection));
380+
381+
if (identity->has_path && identity->path[0] &&
382+
!confirm_bytes(ButtonRequestType_ButtonRequest_SignIdentity,
383+
"Identity Path", (const uint8_t*)identity->path,
384+
strlen(identity->path))) {
385+
return false;
386+
}
387+
356388
/* Format protocol */
357389
if (identity->has_proto && identity->proto[0]) {
358390
strlcpy(title, identity->proto, sizeof(title));
@@ -384,9 +416,26 @@ bool confirm_sign_identity(const IdentityType* identity,
384416
strlcat(body, "\n", sizeof(body));
385417
}
386418

387-
/* Format challenge */
388-
if (challenge && strlen(challenge) != 0) {
389-
strlcat(body, challenge, sizeof(body));
419+
/* Preserve the established single identity/challenge screen when it can be
420+
* formatted without loss. A maximum-size challenge does not fit the shared
421+
* confirmation buffer after host and user metadata; in that case confirm the
422+
* metadata first and page every challenge byte separately. */
423+
if (challenge && challenge[0]) {
424+
const size_t body_len = strlen(body);
425+
const size_t challenge_len = strlen(challenge);
426+
if (body_len + challenge_len < BODY_CHAR_MAX) {
427+
strlcat(body, challenge, sizeof(body));
428+
return confirm(ButtonRequestType_ButtonRequest_SignIdentity, title, "%s",
429+
body);
430+
}
431+
432+
if (body_len != 0 && !confirm(ButtonRequestType_ButtonRequest_SignIdentity,
433+
title, "%s", body)) {
434+
return false;
435+
}
436+
return confirm_bytes(ButtonRequestType_ButtonRequest_SignIdentity,
437+
"Visual Challenge", (const uint8_t*)challenge,
438+
challenge_len);
390439
}
391440

392441
return confirm(ButtonRequestType_ButtonRequest_SignIdentity, title, "%s",

0 commit comments

Comments
 (0)