CVE-2019-18672 (published 2019-12-06T18:15Z)

Insufficient checks in the finite state machine of the ShapeShift KeepKey hardware wallet before firmware 6.2.2 allow a partial reset of cryptographic secrets to known values via crafted messages. Notably, this breaks the security of U2F for new server registrations and invalidates existing registrations. This vulnerability can be exploited by unauthenticated attackers and the interface is reachable via WebUSB.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-354
cwe_name: Improper Validation of Integrity Check Value
description: The product does not validate or incorrectly validates the integrity check values or "checksums" of a message. This may prevent it from detecting if the data has been modified or corrupted in transmission.
extended_description: Improper validation of checksums before use results in an unnecessary risk that can easily be mitigated. The protocol specification describes the algorithm used for calculating the checksum. It is then a simple matter of implementing the calculation and verifying that the calculated checksum and the received checksum match. Improper verification of the calculated checksum and the received checksum can lead to far greater consequences.
         

Fix information:
repo_url: https://github.com/keepkey/keepkey-firmware
commit_details: repo_url: https://github.com/keepkey/keepkey-firmware
author: keepkeyjon
author_date: 2019-09-11 13:30:33-06:00
msg: firmware: stronger recovery state machine checks
repository: repo_url: https://github.com/keepkey/keepkey-firmware
repo_name: keepkey/keepkey-firmware
description: KeepKey Device Firmware
repo_language: C
owner: keepkey
file_changes: filename: recovery_cipher.c
change_type: ModificationType.MODIFY
diff: @@ -36,6 +36,7 @@
 
 #define MAX_UNCYPHERED_WORDS (3)
 
+static bool recovery_started = false;
 static bool enforce_wordlist;
 static bool dry_run;
 static bool awaiting_character;
@@ -56,6 +57,7 @@ static void recovery_abort(void) {
         storage_reset();
     }
 
+    recovery_started = false;
     awaiting_character = false;
     memzero(mnemonic, sizeof(mnemonic));
     memzero(cipher, sizeof(cipher));
@@ -270,6 +272,7 @@ void recovery_cipher_init(bool passphrase_protection, bool pin_protection,
 
     /* Set to recovery cipher mode and generate and show next cipher */
     awaiting_character = true;
+    recovery_started = true;
     next_character();
 }
 
@@ -283,6 +286,13 @@ void recovery_cipher_init(bool passphrase_protection, bool pin_protection,
  */
 void next_character(void)
 {
+    if (!recovery_started) {
+        recovery_abort();
+        fsm_sendFailure(FailureType_Failure_UnexpectedMessage, "Not in Recovery mode");
+        layoutHome();
+        return;
+    }
+
     /* Scramble cipher */
     strlcpy(cipher, english_alphabet, ENGLISH_ALPHABET_BUF);
     random_permute_char(cipher, strlen(cipher));
@@ -341,7 +351,7 @@ void next_character(void)
  */
 void recovery_character(const char *character)
 {
-    if (!awaiting_character) {
+    if (!awaiting_character || !recovery_started) {
         recovery_abort();
         fsm_sendFailure(FailureType_Failure_UnexpectedMessage, "Not in Recovery mode");
         layoutHome();
@@ -430,6 +440,13 @@ void recovery_character(const char *character)
  */
 void recovery_delete_character(void)
 {
+    if (!recovery_started) {
+        recovery_abort();
+        fsm_sendFailure(FailureType_Failure_UnexpectedMessage, "Not in Recovery mode");
+        layoutHome();
+        return;
+    }
+
     if(strlen(mnemonic) > 0)
     {
         mnemonic[strlen(mnemonic) - 1] = '\0';
@@ -448,6 +465,13 @@ void recovery_delete_character(void)
  */
 void recovery_cipher_finalize(void)
 {
+    if (!recovery_started) {
+        recovery_abort();
+        fsm_sendFailure(FailureType_Failure_UnexpectedMessage, "Not in Recovery mode");
+        layoutHome();
+        return;
+    }
+
     static char CONFIDENTIAL new_mnemonic[MNEMONIC_BUF] = "";
     static char CONFIDENTIAL temp_word[CURRENT_WORD_BUF];
     volatile bool auto_completed = true;
@@ -479,7 +503,7 @@ void recovery_cipher_finalize(void)
     }
 
     /* Truncate additional space at the end */
-    new_mnemonic[strlen(new_mnemonic) - 1] = '\0';
+    new_mnemonic[MAX(0u, strnlen(new_mnemonic, sizeof(new_mnemonic)) - 1)] = '\0';
 
     if (!dry_run && (!enforce_wordlist || mnemonic_check(new_mnemonic))) {
         storage_setMnemonic(new_mnemonic);
@@ -532,6 +556,8 @@ void recovery_cipher_finalize(void)
  */
 bool recovery_cipher_abort(void)
 {
+    recovery_started = false;
+
     if (awaiting_character) {
         awaiting_character = false;
         return true;

programming_language: C