/******************************************************************************* * Ledger Ethereum App * (c) 2016-2019 Ledger * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ #include #include #include "ethUstream.h" #include "ethUtils.h" #include "utils.h" #define MAX_INT256 32 #define MAX_ADDRESS 20 void initTx(txContext_t *context, cx_sha3_t *sha3, txContent_t *content, ustreamProcess_t customProcessor, void *extra) { memset(context, 0, sizeof(txContext_t)); context->sha3 = sha3; context->content = content; context->customProcessor = customProcessor; context->extra = extra; context->currentField = RLP_NONE + 1; cx_keccak_init(context->sha3, 256); } uint8_t readTxByte(txContext_t *context) { uint8_t data; if (context->commandLength < 1) { PRINTF("readTxByte Underflow\n"); THROW(EXCEPTION); } data = *context->workBuffer; context->workBuffer++; context->commandLength--; if (context->processingField) { context->currentFieldPos++; } if (!(context->processingField && context->fieldSingleByte)) { cx_hash((cx_hash_t *) context->sha3, 0, &data, 1, NULL, 0); } return data; } void copyTxData(txContext_t *context, uint8_t *out, uint32_t length) { if (context->commandLength < length) { PRINTF("copyTxData Underflow\n"); THROW(EXCEPTION); } if (out != NULL) { memmove(out, context->workBuffer, length); } if (!(context->processingField && context->fieldSingleByte)) { cx_hash((cx_hash_t *) context->sha3, 0, context->workBuffer, length, NULL, 0); } context->workBuffer += length; context->commandLength -= length; if (context->processingField) { context->currentFieldPos += length; } } static void processContent(txContext_t *context) { // Keep the full length for sanity checks, move to the next field if (!context->currentFieldIsList) { PRINTF("Invalid type for RLP_CONTENT\n"); THROW(EXCEPTION); } context->dataLength = context->currentFieldLength; context->currentField++; context->processingField = false; } static void processAccessList(txContext_t *context) { if (!context->currentFieldIsList) { PRINTF("Invalid type for RLP_ACCESS_LIST\n"); THROW(EXCEPTION); } if (context->currentFieldPos < context->currentFieldLength) { uint32_t copySize = MIN(context->commandLength, context->currentFieldLength - context->currentFieldPos); copyTxData(context, NULL, copySize); } if (context->currentFieldPos == context->currentFieldLength) { context->currentField++; context->processingField = false; } } static void processType(txContext_t *context) { if (context->currentFieldIsList) { PRINTF("Invalid type for RLP_TYPE\n"); THROW(EXCEPTION); } if (context->currentFieldLength > MAX_INT256) { PRINTF("Invalid length for RLP_TYPE\n"); THROW(EXCEPTION); } if (context->currentFieldPos < context->currentFieldLength) { uint32_t copySize = MIN(context->commandLength, context->currentFieldLength - context->currentFieldPos); copyTxData(context, NULL, copySize); } if (context->currentFieldPos == context->currentFieldLength) { context->currentField++; context->processingField = false; } } static void processChainID(txContext_t *context) { if (context->currentFieldIsList) { PRINTF("Invalid type for RLP_CHAINID\n"); THROW(EXCEPTION); } if (context->currentFieldLength > MAX_INT256) { PRINTF("Invalid length for RLP_CHAINID\n"); THROW(EXCEPTION); } if (context->currentFieldPos < context->currentFieldLength) { uint32_t copySize = MIN(context->commandLength, context->currentFieldLength - context->currentFieldPos); copyTxData(context, context->content->chainID.value, copySize); } if (context->currentFieldPos == context->currentFieldLength) { context->content->chainID.length = context->currentFieldLength; context->currentField++; context->processingField = false; } } static void processNonce(txContext_t *context) { if (context->currentFieldIsList) { PRINTF("Invalid type for RLP_NONCE\n"); THROW(EXCEPTION); } if (context->currentFieldLength > MAX_INT256) { PRINTF("Invalid length for RLP_NONCE\n"); THROW(EXCEPTION); } if (context->currentFieldPos < context->currentFieldLength) { uint32_t copySize = MIN(context->commandLength, context->currentFieldLength - context->currentFieldPos); copyTxData(context, context->content->nonce.value, copySize); } if (context->currentFieldPos == context->currentFieldLength) { context->content->nonce.length = context->currentFieldLength; context->currentField++; context->processingField = false; } } static void processStartGas(txContext_t *context) { if (context->currentFieldIsList) { PRINTF("Invalid type for RLP_STARTGAS\n"); THROW(EXCEPTION); } if (context->currentFieldLength > MAX_INT256) { PRINTF("Invalid length for RLP_STARTGAS %d\n", context->currentFieldLength); THROW(EXCEPTION); } if (context->currentFieldPos < context->currentFieldLength) { uint32_t copySize = MIN(context->commandLength, context->currentFieldLength - context->currentFieldPos); copyTxData(context, context->content->startgas.value + context->currentFieldPos, copySize); } if (context->currentFieldPos == context->currentFieldLength) { context->content->startgas.length = context->currentFieldLength; context->currentField++; context->processingField = false; } } // Alias over `processStartGas()`. static void processGasLimit(txContext_t *context) { processStartGas(context); } static void processGasprice(txContext_t *context) { if (context->currentFieldIsList) { PRINTF("Invalid type for RLP_GASPRICE\n"); THROW(EXCEPTION); } if (context->currentFieldLength > MAX_INT256) { PRINTF("Invalid length for RLP_GASPRICE\n"); THROW(EXCEPTION); } if (context->currentFieldPos < context->currentFieldLength) { uint32_t copySize = MIN(context->commandLength, context->currentFieldLength - context->currentFieldPos); copyTxData(context, context->content->gasprice.value + context->currentFieldPos, copySize); } if (context->currentFieldPos == context->currentFieldLength) { context->content->gasprice.length = context->currentFieldLength; context->currentField++; context->processingField = false; } } static void processValue(txContext_t *context) { if (context->currentFieldIsList) { PRINTF("Invalid type for RLP_VALUE\n"); THROW(EXCEPTION); } if (context->currentFieldLength > MAX_INT256) { PRINTF("Invalid length for RLP_VALUE\n"); THROW(EXCEPTION); } if (context->currentFieldPos < context->currentFieldLength) { uint32_t copySize = MIN(context->commandLength, context->currentFieldLength - context->currentFieldPos); copyTxData(context, context->content->value.value + context->currentFieldPos, copySize); } if (context->currentFieldPos == context->currentFieldLength) { context->content->value.length = context->currentFieldLength; context->currentField++; context->processingField = false; } } static void processTo(txContext_t *context) { if (context->currentFieldIsList) { PRINTF("Invalid type for RLP_TO\n"); THROW(EXCEPTION); } if (context->currentFieldLength > MAX_ADDRESS) { PRINTF("Invalid length for RLP_TO\n"); THROW(EXCEPTION); } if (context->currentFieldPos < context->currentFieldLength) { uint32_t copySize = MIN(context->commandLength, context->currentFieldLength - context->currentFieldPos); copyTxData(context, context->content->destination + context->currentFieldPos, copySize); } if (context->currentFieldPos == context->currentFieldLength) { context->content->destinationLength = context->currentFieldLength; context->currentField++; context->processingField = false; } } static void processData(txContext_t *context) { PRINTF("PROCESS DATA\n"); if (context->currentFieldIsList) { PRINTF("Invalid type for RLP_DATA\n"); THROW(EXCEPTION); } if (context->currentFieldPos < context->currentFieldLength) { uint32_t copySize = MIN(context->commandLength, context->currentFieldLength - context->currentFieldPos); // If there is no data, set dataPresent to false. if (copySize == 1 && *context->workBuffer == 0x00) { context->content->dataPresent = false; } copyTxData(context, NULL, copySize); } if (context->currentFieldPos == context->currentFieldLength) { PRINTF("incrementing field\n"); context->currentField++; context->processingField = false; } } static void processAndDiscard(txContext_t *context) { if (context->currentFieldIsList) { PRINTF("Invalid type for Discarded field\n"); THROW(EXCEPTION); } if (context->currentFieldPos < context->currentFieldLength) { uint32_t copySize = MIN(context->commandLength, context->currentFieldLength - context->currentFieldPos); copyTxData(context, NULL, copySize); } if (context->currentFieldPos == context->currentFieldLength) { context->currentField++; context->processingField = false; } } static void processV(txContext_t *context) { if (context->currentFieldIsList) { PRINTF("Invalid type for RLP_V\n"); THROW(EXCEPTION); } if (context->currentFieldLength > sizeof(context->content->v)) { PRINTF("Invalid length for RLP_V\n"); THROW(EXCEPTION); } if (context->currentFieldPos < context->currentFieldLength) { uint32_t copySize = MIN(context->commandLength, context->currentFieldLength - context->currentFieldPos); // Make sure we do not copy more than the size of v. copySize = MIN(copySize, sizeof(context->content->v)); copyTxData(context, context->content->v + context->currentFieldPos, copySize); } if (context->currentFieldPos == context->currentFieldLength) { context->content->vLength = context->currentFieldLength; context->currentField++; context->processingField = false; } } static bool processEIP1559Tx(txContext_t *context) { switch (context->currentField) { case EIP1559_RLP_CONTENT: { processContent(context); if ((context->processingFlags & TX_FLAG_TYPE) == 0) { context->currentField++; } break; } // This gets hit only by Wanchain case EIP1559_RLP_TYPE: { processType(context); break; } case EIP1559_RLP_CHAINID: { processChainID(context); break; } case EIP1559_RLP_NONCE: { processNonce(context); break; } case EIP1559_RLP_MAX_FEE_PER_GAS: { processGasprice(context); break; } case EIP1559_RLP_GASLIMIT: { processGasLimit(context); break; } case EIP1559_RLP_TO: { processTo(context); break; } case EIP1559_RLP_VALUE: { processValue(context); break; } case EIP1559_RLP_DATA: { processData(context); break; } case EIP1559_RLP_ACCESS_LIST: { processAccessList(context); break; } case EIP1559_RLP_MAX_PRIORITY_FEE_PER_GAS: processAndDiscard(context); break; default: PRINTF("Invalid RLP decoder context\n"); return true; } return false; } static bool processEIP2930Tx(txContext_t *context) { switch (context->currentField) { case EIP2930_RLP_CONTENT: processContent(context); if ((context->processingFlags & TX_FLAG_TYPE) == 0) { context->currentField++; } break; // This gets hit only by Wanchain case EIP2930_RLP_TYPE: processType(context); break; case EIP2930_RLP_CHAINID: processChainID(context); break; case EIP2930_RLP_NONCE: processNonce(context); break; case EIP2930_RLP_GASPRICE: processGasprice(context); break; case EIP2930_RLP_GASLIMIT: processGasLimit(context); break; case EIP2930_RLP_TO: processTo(context); break; case EIP2930_RLP_VALUE: processValue(context); break; case EIP2930_RLP_DATA: processData(context); break; case EIP2930_RLP_ACCESS_LIST: processAccessList(context); break; default: PRINTF("Invalid RLP decoder context\n"); return true; } return false; } static bool processLegacyTx(txContext_t *context) { switch (context->currentField) { case LEGACY_RLP_CONTENT: processContent(context); if ((context->processingFlags & TX_FLAG_TYPE) == 0) { context->currentField++; } break; // This gets hit only by Wanchain case LEGACY_RLP_TYPE: processType(context); break; case LEGACY_RLP_NONCE: processNonce(context); break; case LEGACY_RLP_GASPRICE: processGasprice(context); break; case LEGACY_RLP_STARTGAS: processStartGas(context); break; case LEGACY_RLP_TO: processTo(context); break; case LEGACY_RLP_VALUE: processValue(context); break; case LEGACY_RLP_DATA: processData(context); break; case LEGACY_RLP_R: case LEGACY_RLP_S: processAndDiscard(context); break; case LEGACY_RLP_V: processV(context); break; default: PRINTF("Invalid RLP decoder context\n"); return true; } return false; } static parserStatus_e parseRLP(txContext_t *context) { bool canDecode = false; uint32_t offset; while (context->commandLength != 0) { bool valid; // Feed the RLP buffer until the length can be decoded context->rlpBuffer[context->rlpBufferPos++] = readTxByte(context); if (rlpCanDecode(context->rlpBuffer, context->rlpBufferPos, &valid)) { // Can decode now, if valid if (!valid) { PRINTF("RLP pre-decode error\n"); return USTREAM_FAULT; } canDecode = true; break; } // Cannot decode yet // Sanity check if (context->rlpBufferPos == sizeof(context->rlpBuffer)) { PRINTF("RLP pre-decode logic error\n"); return USTREAM_FAULT; } } if (!canDecode) { PRINTF("Can't decode\n"); return USTREAM_PROCESSING; } // Ready to process this field if (!rlpDecodeLength(context->rlpBuffer, &context->currentFieldLength, &offset, &context->currentFieldIsList)) { PRINTF("RLP decode error\n"); return USTREAM_FAULT; } // Ready to process this field if (offset == 0) { // Hack for single byte, self encoded context->workBuffer--; context->commandLength++; context->fieldSingleByte = true; } else { context->fieldSingleByte = false; } context->currentFieldPos = 0; context->rlpBufferPos = 0; context->processingField = true; return USTREAM_CONTINUE; } static parserStatus_e processTxInternal(txContext_t *context) { for (;;) { customStatus_e customStatus = CUSTOM_NOT_HANDLED; // EIP 155 style transaction if (PARSING_IS_DONE(context)) { PRINTF("parsing is done\n"); return USTREAM_FINISHED; } // Old style transaction (pre EIP-155). Transations could just skip `v,r,s` so we needed to // cut parsing here. commandLength == 0 could happen in two cases : // 1. We are in an old style transaction : just return `USTREAM_FINISHED`. // 2. We are at the end of an APDU in a multi-apdu process. This would make us return // `USTREAM_FINISHED` preemptively. Case number 2 should NOT happen as it is up to // `ledgerjs` to correctly decrease the size of the APDU (`commandLength`) so that this // situation doesn't happen. if ((context->txType == LEGACY && context->currentField == LEGACY_RLP_V) && (context->commandLength == 0)) { context->content->vLength = 0; PRINTF("finished\n"); return USTREAM_FINISHED; } if (context->commandLength == 0) { PRINTF("Command length done\n"); return USTREAM_PROCESSING; } if (!context->processingField) { parserStatus_e status = parseRLP(context); if (status != USTREAM_CONTINUE) { return status; } } if (context->customProcessor != NULL) { customStatus = context->customProcessor(context); PRINTF("After customprocessor\n"); switch (customStatus) { case CUSTOM_NOT_HANDLED: case CUSTOM_HANDLED: break; case CUSTOM_SUSPENDED: return USTREAM_SUSPENDED; case CUSTOM_FAULT: PRINTF("Custom processor aborted\n"); return USTREAM_FAULT; default: PRINTF("Unhandled custom processor status\n"); return USTREAM_FAULT; } } if (customStatus == CUSTOM_NOT_HANDLED) { PRINTF("Current field: %d\n", context->currentField); switch (context->txType) { bool fault; case LEGACY: fault = processLegacyTx(context); if (fault) { return USTREAM_FAULT; } else { break; } case EIP2930: fault = processEIP2930Tx(context); if (fault) { return USTREAM_FAULT; } else { break; } case EIP1559: fault = processEIP1559Tx(context); if (fault) { return USTREAM_FAULT; } else { break; } default: PRINTF("Transaction type %d is not supported\n", context->txType); return USTREAM_FAULT; } } } PRINTF("end of here\n"); } parserStatus_e processTx(txContext_t *context, const uint8_t *buffer, uint32_t length, uint32_t processingFlags) { parserStatus_e result; BEGIN_TRY { TRY { context->workBuffer = buffer; context->commandLength = length; context->processingFlags = processingFlags; result = processTxInternal(context); PRINTF("result: %d\n", result); } CATCH_OTHER(e) { result = USTREAM_FAULT; } FINALLY { } } END_TRY; return result; } parserStatus_e continueTx(txContext_t *context) { parserStatus_e result; BEGIN_TRY { TRY { result = processTxInternal(context); } CATCH_OTHER(e) { result = USTREAM_FAULT; } FINALLY { } } END_TRY; return result; }