Skip to content

Commit

Permalink
Merge pull request #190 from LedgerHQ/fbe/error_code_when_child_app_i…
Browse files Browse the repository at this point in the history
…s_missing

Error code when child app is missing
  • Loading branch information
fbeutin-ledger committed May 17, 2024
2 parents 12fb987 + e5a05db commit 3229c64
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 56 deletions.
43 changes: 22 additions & 21 deletions protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,24 +275,25 @@ For all other TYPES, the format of this nonce is a 32 bytes array.

Return code can be one of the following values:

| Bytes | Name | Description |
| ------ | ------------------------ | --------------------------------------------------------------------------------------- |
| 0x6A80 | INCORRECT_COMMAND_DATA | The DATA sent does not match the correct format for the COMMAND specified |
| 0x6A81 | DESERIALIZATION_FAILED | Can't parse partner transaction proposal |
| 0x6A82 | WRONG_TRANSACTION_ID | Transaction ID is not equal to one generated on the START_NEW_TRANSACTION step |
| 0x6A83 | INVALID_ADDRESS | Refund or payout address doesn't belong to us |
| 0x6A84 | USER_REFUSED | User refused the transaction proposal |
| 0x6A85 | INTERNAL_ERROR | Internal error of the application |
| 0x6A86 | WRONG_P1 | The P1 value is not a valid RATE |
| 0x6A87 | WRONG_P2_SUBCOMMAND | The P2 lower 4 bits of the P2 byte is not a valid SUBCOMMAND |
| 0x6A88 | WRONG_P2_EXTENSION | The P2 upper 4 bits of the P2 byte is not a valid EXTENSION |
| 0x6A89 | INVALID_P2_EXTENSION | The extension is a valid value but is refused in the current context |
| 0x6A8A | MEMORY_CORRUPTION | A child application started by Exchange has corrupted the Exchange application memory |
| 0x6A8B | AMOUNT_FORMATTING_FAILED | A child application failed to format an amount provided by the partner |
| 0x6E00 | CLASS_NOT_SUPPORTED | The CLASS is not 0xE0 |
| 0x6E01 | MALFORMED_APDU | The APDU header is malformed |
| 0x6E02 | INVALID_DATA_LENGTH | The length of the DATA is refused for this COMMAND |
| 0x6D00 | INVALID_INSTRUCTION | COMMAND is not in the "Possible commands" table |
| 0x6D01 | UNEXPECTED_INSTRUCTION | COMMAND is in the "Possible commands" table but is refused in the current context |
| 0x9D1A | SIGN_VERIFICATION_FAIL | The signature sent by this command does not match the data or the associated public key |
| 0x9000 | SUCCESS | Success code |
| Bytes | Name | Description |
| ------ | ------------------------- | --------------------------------------------------------------------------------------- |
| 0x6A80 | INCORRECT_COMMAND_DATA | The DATA sent does not match the correct format for the COMMAND specified |
| 0x6A81 | DESERIALIZATION_FAILED | Can't parse partner transaction proposal |
| 0x6A82 | WRONG_TRANSACTION_ID | Transaction ID is not equal to one generated on the START_NEW_TRANSACTION step |
| 0x6A83 | INVALID_ADDRESS | Refund or payout address doesn't belong to us |
| 0x6A84 | USER_REFUSED | User refused the transaction proposal |
| 0x6A85 | INTERNAL_ERROR | Internal error of the application |
| 0x6A86 | WRONG_P1 | The P1 value is not a valid RATE |
| 0x6A87 | WRONG_P2_SUBCOMMAND | The P2 lower 4 bits of the P2 byte is not a valid SUBCOMMAND |
| 0x6A88 | WRONG_P2_EXTENSION | The P2 upper 4 bits of the P2 byte is not a valid EXTENSION |
| 0x6A89 | INVALID_P2_EXTENSION | The extension is a valid value but is refused in the current context |
| 0x6A8A | MEMORY_CORRUPTION | A child application started by Exchange has corrupted the Exchange application memory |
| 0x6A8B | AMOUNT_FORMATTING_FAILED | A child application failed to format an amount provided by the partner |
| 0x6A8C | APPLICATION_NOT_INSTALLED | The requested child application is not installed on the device |
| 0x6E00 | CLASS_NOT_SUPPORTED | The CLASS is not 0xE0 |
| 0x6E01 | MALFORMED_APDU | The APDU header is malformed |
| 0x6E02 | INVALID_DATA_LENGTH | The length of the DATA is refused for this COMMAND |
| 0x6D00 | INVALID_INSTRUCTION | COMMAND is not in the "Possible commands" table |
| 0x6D01 | UNEXPECTED_INSTRUCTION | COMMAND is in the "Possible commands" table but is refused in the current context |
| 0x9D1A | SIGN_VERIFICATION_FAIL | The signature sent by this command does not match the data or the associated public key |
| 0x9000 | SUCCESS | Success code |
8 changes: 8 additions & 0 deletions src/currency_lib_calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ static int os_lib_call_bss_safe(unsigned int libcall_params[5]) {
PRINTF("Check BSS from %p to %p\n", &_bss, &_ebss);
volatile uint16_t os_lib_call_canary = OS_LIB_CALL_CANARY_VALUE;
volatile uint16_t bss_before = cx_crc16(&_bss, ((uintptr_t) &_ebss) - ((uintptr_t) &_bss));
// os_lib_call will throw SWO_SEC_APP_14 if the called application is not installed.
// We DON'T define a local TRY / CATCH context because it costs a lot of stack and we are short
// for some exchanged coins
// This means we will fallback to the main function that will handle the error (error RAPDU)
// TODO: once LNS is deprecated, handle the error properly here
os_lib_call(libcall_params);
volatile uint16_t bss_after = cx_crc16(&_bss, ((uintptr_t) &_ebss) - ((uintptr_t) &_bss));
if (bss_before != bss_after) {
Expand Down Expand Up @@ -187,6 +192,9 @@ int create_payin_transaction(create_transaction_parameters_t *lib_in_out_params)
strlcpy(appname, G_swap_ctx.payin_binary_name, sizeof(appname));
#endif

// This os_lib_call may not throw SWO_SEC_APP_14 (missing library), as the existence of the
// application has been enforced through an earlier call to os_lib_call for CHECK_ADDRESS and
// GET_PRINTABLE_AMOUNT
os_lib_call(libcall_params);

// From now on our BSS is corrupted and unusable. Return to main loop to start a new cycle ASAP
Expand Down
12 changes: 9 additions & 3 deletions src/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ void io_seproxyhal_display(const bagl_element_t *element) {
}
#endif // HAVE_BAGL

uint8_t G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B];

uint8_t io_event(uint8_t channel) {
(void) channel;

Expand Down Expand Up @@ -174,12 +176,16 @@ int reply_error(swap_error_e error) {
return send_apdu(output_buffer, 2);
}

int instant_reply_error(swap_error_e error) {
G_io_apdu_buffer[0] = (error >> 8) & 0xFF;
G_io_apdu_buffer[1] = error & 0xFF;
return io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
}

int reply_success(void) {
return reply_error(SUCCESS);
}

int instant_reply_success(void) {
G_io_apdu_buffer[0] = 0x90;
G_io_apdu_buffer[1] = 0x00;
return io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
return instant_reply_error(SUCCESS);
}
2 changes: 1 addition & 1 deletion src/io.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ int recv_apdu(void);
int send_apdu(uint8_t *buffer, size_t buffer_length);

int reply_error(swap_error_e error);
int instant_reply_error(swap_error_e error);

int reply_success(void);

int instant_reply_success(void);
30 changes: 16 additions & 14 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@

#include "usbd_core.h"

// Error code thrown by os_lib_call when the requested application is not installed
// Defined in SDK for old API_LEVELs, but not on recent API_LEVELs
#ifndef SWO_SEC_APP_14
#define SWO_SEC_APP_14 0x5114 // (ERR_SEC_APP + ERR_GEN_ID_14)
#endif

ux_state_t G_ux;
bolos_ux_params_t G_ux_params;

uint8_t G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B];

swap_app_context_t G_swap_ctx;

void app_main(void) {
Expand Down Expand Up @@ -75,17 +79,6 @@ void app_main(void) {
}
}

void app_exit(void) {
BEGIN_TRY_L(exit) {
TRY_L(exit) {
os_sched_exit(-1);
}
FINALLY_L(exit) {
}
}
END_TRY_L(exit);
}

// On Stax, remember some data from the previous cycle if applicable to display a status screen
#ifdef HAVE_NBGL
previous_cycle_data_t G_previous_cycle_data;
Expand Down Expand Up @@ -166,6 +159,15 @@ __attribute__((section(".boot"))) int main(__attribute__((unused)) int arg0) {

app_main();
}
CATCH(SWO_SEC_APP_14) {
// We have called os_lib_call for an application that is not installed.
// Inform the caller of this failure and fully reset the context
// We don't try to handle this kind of error
PRINTF("Fatal: os_lib_call has thrown SWO_SEC_APP_14\n");
instant_reply_error(APPLICATION_NOT_INSTALLED);
CLOSE_TRY;
continue;
}
CATCH(EXCEPTION_IO_RESET) {
// reset IO and UX before continuing
CLOSE_TRY;
Expand All @@ -180,6 +182,6 @@ __attribute__((section(".boot"))) int main(__attribute__((unused)) int arg0) {
}
END_TRY;
}
app_exit();
os_sched_exit(-1);
return 0;
}
1 change: 1 addition & 0 deletions src/swap_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ typedef enum {
INVALID_P2_EXTENSION = 0x6A89,
MEMORY_CORRUPTION = 0x6A8A,
AMOUNT_FORMATTING_FAILED = 0x6A8B,
APPLICATION_NOT_INSTALLED = 0x6A8C,
CLASS_NOT_SUPPORTED = 0x6E00,
MALFORMED_APDU = 0x6E01,
INVALID_DATA_LENGTH = 0x6E02,
Expand Down
37 changes: 20 additions & 17 deletions test/python/apps/exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,26 @@ class Rate(IntEnum):


class Errors(IntEnum):
INCORRECT_COMMAND_DATA = 0x6A80
DESERIALIZATION_FAILED = 0x6A81
WRONG_TRANSACTION_ID = 0x6A82
INVALID_ADDRESS = 0x6A83
USER_REFUSED = 0x6A84
INTERNAL_ERROR = 0x6A85
WRONG_P1 = 0x6A86
WRONG_P2_SUBCOMMAND = 0x6A87
WRONG_P2_EXTENSION = 0x6A88
INVALID_P2_EXTENSION = 0x6A89
CLASS_NOT_SUPPORTED = 0x6E00
MALFORMED_APDU = 0x6E01
INVALID_DATA_LENGTH = 0x6E02
INVALID_INSTRUCTION = 0x6D00
UNEXPECTED_INSTRUCTION = 0x6D01
SIGN_VERIFICATION_FAIL = 0x9D1A
SUCCESS = 0x9000
INCORRECT_COMMAND_DATA = 0x6A80
DESERIALIZATION_FAILED = 0x6A81
WRONG_TRANSACTION_ID = 0x6A82
INVALID_ADDRESS = 0x6A83
USER_REFUSED = 0x6A84
INTERNAL_ERROR = 0x6A85
WRONG_P1 = 0x6A86
WRONG_P2_SUBCOMMAND = 0x6A87
WRONG_P2_EXTENSION = 0x6A88
INVALID_P2_EXTENSION = 0x6A89
MEMORY_CORRUPTION = 0x6A8A
AMOUNT_FORMATTING_FAILED = 0x6A8B
APPLICATION_NOT_INSTALLED = 0x6A8C
CLASS_NOT_SUPPORTED = 0x6E00
MALFORMED_APDU = 0x6E01
INVALID_DATA_LENGTH = 0x6E02
INVALID_INSTRUCTION = 0x6D00
UNEXPECTED_INSTRUCTION = 0x6D01
SIGN_VERIFICATION_FAIL = 0x9D1A
SUCCESS = 0x9000


EXCHANGE_CLASS = 0xE0
Expand Down
48 changes: 48 additions & 0 deletions test/python/test_missing_application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import pytest

from ragger.utils import RAPDU, prefix_with_len, create_currency_config
from ragger.error import ExceptionRAPDU

from .apps.exchange import ExchangeClient, Rate, SubCommand, Errors, Command, P2_EXTEND, P2_MORE, EXCHANGE_CLASS
from .apps.exchange_transaction_builder import get_partner_curve, LEGACY_SUBCOMMANDS, ALL_SUBCOMMANDS, NEW_SUBCOMMANDS, get_credentials, craft_and_sign_tx
from .apps.signing_authority import SigningAuthority, LEDGER_SIGNER
from .apps import cal as cal
from .apps.ethereum import ETC_PACKED_DERIVATION_PATH

# This does not exist
CURRENCY_TO = cal.CurrencyConfiguration(ticker="PSM",
conf=create_currency_config("PSM", "PSM"),
packed_derivation_path=ETC_PACKED_DERIVATION_PATH)
CURRENCY_FROM = cal.ETH_CURRENCY_CONFIGURATION

# Some valid infos for TX. Content is irrelevant for the test

SWAP_TX_INFOS = {
"payin_address": b"0xd692Cb1346262F584D17B4B470954501f6715a82",
"payin_extra_id": b"",
"refund_address": b"0xDad77910DbDFdE764fC21FCD4E74D71bBACA6D8D",
"refund_extra_id": b"",
"payout_address": b"bc1qqtl9jlrwcr3fsfcjj2du7pu6fcgaxl5dsw2vyg",
"payout_extra_id": b"",
"currency_from": CURRENCY_FROM.ticker,
"currency_to": CURRENCY_TO.ticker,
"amount_to_provider": bytes.fromhex("013fc3a717fb5000"),
"amount_to_wallet": b"\x0b\xeb\xc2\x00",
}
FEES = 100

@pytest.mark.use_on_backend("ledgerwallet")
def test_missing_application(backend):
ex = ExchangeClient(backend, Rate.FIXED, SubCommand.SWAP_NG)
partner = SigningAuthority(curve=get_partner_curve(SubCommand.SWAP_NG), name="Name")
transaction_id = ex.init_transaction().data
credentials = get_credentials(SubCommand.SWAP_NG, partner)
ex.set_partner_key(credentials)
ex.check_partner_key(LEDGER_SIGNER.sign(credentials))
tx, tx_signature = craft_and_sign_tx(SubCommand.SWAP_NG, SWAP_TX_INFOS, transaction_id, FEES, partner)
ex.process_transaction(tx)
ex.check_transaction_signature(tx_signature)
to_configuration = CURRENCY_TO.get_conf_for_ticker()
with pytest.raises(ExceptionRAPDU) as e:
ex.check_payout_address(to_configuration)
assert e.value.status == Errors.APPLICATION_NOT_INSTALLED

0 comments on commit 3229c64

Please sign in to comment.