Skip to content

Commit f773108

Browse files
authored
Merge pull request #219 from Zondax/fix/improvements
App improvements: input validation, dispatcher hardening, and parser robustness
2 parents af9e92b + da53176 commit f773108

21 files changed

Lines changed: 971 additions & 903 deletions

File tree

app/Makefile.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
APPVERSION_M=0
22
APPVERSION_N=26
3-
APPVERSION_P=4
3+
APPVERSION_P=5

app/rust/src/parser/ffi.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ pub unsafe extern "C" fn _read(
1717
context: *const parser_context_t,
1818
parser_state: *mut parse_tx_t,
1919
) -> u32 {
20+
if context.is_null() || parser_state.is_null() {
21+
return ParserError::NoData as u32;
22+
}
23+
if (*context).buffer.is_null() || (*context).bufferLen == 0 {
24+
return ParserError::NoData as u32;
25+
}
2026
let data = core::slice::from_raw_parts((*context).buffer, (*context).bufferLen as _);
2127

2228
if let Some(obj) = parsed_obj_from_state(parser_state) {
@@ -63,13 +69,21 @@ pub unsafe extern "C" fn _getItem(
6369
pageCount: *mut u8,
6470
tx_t: *const parse_tx_t,
6571
) -> u32 {
72+
if pageCount.is_null()
73+
|| outKey.is_null()
74+
|| outValue.is_null()
75+
|| outKeyLen == 0
76+
|| outValueLen == 0
77+
{
78+
return ParserError::NoData as _;
79+
}
80+
if tx_t.is_null() || (*tx_t).state.is_null() {
81+
return ParserError::ContextMismatch as _;
82+
}
6683
*pageCount = 0u8;
6784
let page_count = &mut *pageCount;
6885
let key = core::slice::from_raw_parts_mut(outKey as *mut u8, outKeyLen as usize);
6986
let value = core::slice::from_raw_parts_mut(outValue as *mut u8, outValueLen as usize);
70-
if tx_t.is_null() || (*tx_t).state.is_null() {
71-
return ParserError::ContextMismatch as _;
72-
}
7387
if let Some(obj) = parsed_obj_from_state(tx_t as _) {
7488
match obj.get_item(displayIdx, key, value, pageIdx) {
7589
Ok(page) => {
@@ -154,6 +168,9 @@ pub unsafe extern "C" fn _presig_hash_data(
154168
buf: *mut u8,
155169
bufLen: u16,
156170
) -> u16 {
171+
if tx_t.is_null() || buf.is_null() || bufLen == 0 {
172+
return 0;
173+
}
157174
let buffer = core::slice::from_raw_parts_mut(buf, bufLen as usize);
158175

159176
if let Some(tx) = parsed_obj_from_state(tx_t as _).and_then(|obj| obj.transaction()) {
@@ -273,6 +290,9 @@ pub unsafe extern "C" fn _structured_msg_hash(
273290
out: *mut u8,
274291
out_len: u16,
275292
) -> u32 {
293+
if tx_t.is_null() || out.is_null() || out_len == 0 {
294+
return ParserError::NoData as _;
295+
}
276296
if let Some(tx) = parsed_obj_from_state(tx_t as _).and_then(|obj| obj.structured_msg()) {
277297
let output = core::slice::from_raw_parts_mut(out, out_len as _);
278298
if tx.get_hash(output).is_ok() {

app/rust/src/parser/message.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,13 @@ impl<'a> ByteString<'a> {
7979

8080
let (rem, len) = read_varint(data).map_err(|_| ParserError::InvalidBytestrMessage)?;
8181

82-
let (_, message_content) = take::<_, _, ParserError>(len as usize)(rem)
82+
let (tail, message_content) = take::<_, _, ParserError>(len as usize)(rem)
8383
.map_err(|_| ParserError::InvalidBytestrMessage)?;
8484

85+
if !tail.is_empty() {
86+
return Err(ParserError::InvalidBytestrMessage);
87+
}
88+
8589
if !message_content.is_ascii() {
8690
return Err(ParserError::InvalidBytestrMessage);
8791
}
@@ -227,4 +231,23 @@ mod test {
227231
let msg = ByteString::from_bytes("\x17Stacks Signed Message:\n".as_bytes());
228232
assert!(msg.is_err());
229233
}
234+
235+
#[test]
236+
fn test_reject_trailing_bytes_after_message() {
237+
let visible = "benign";
238+
let hidden = b"\xDEADBEEF-hidden-suffix";
239+
let mut m = built_message(visible.len(), visible);
240+
m.extend_from_slice(hidden);
241+
let msg = ByteString::from_bytes(&m);
242+
assert!(msg.is_err());
243+
}
244+
245+
#[test]
246+
fn test_reject_single_trailing_byte() {
247+
let visible = "abc";
248+
let mut m = built_message(visible.len(), visible);
249+
m.push(0x00);
250+
let msg = ByteString::from_bytes(&m);
251+
assert!(msg.is_err());
252+
}
230253
}

app/rust/src/parser/parsed_obj.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,4 +846,32 @@ mod test {
846846

847847
std::println!("tx: {:?}", msg);
848848
}
849+
850+
const MODE_FIXTURE_HEX: &str = "000000000104009ef3889fd070159edcd8ef88a0ec87cea1592c83000000000000000000000000000f42400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000302000000060002169ef3889fd070159edcd8ef88a0ec87cea1592c830100000000000027100003167c5f674a8fd08efa61dd9b11121e046dd2c892730a756e6976322d636f72650300000000000000000103167c5f674a8fd08efa61dd9b11121e046dd2c892730a756e6976322d636f7265168c5e2f8d25627d6edebeb6d10fa3300f5acc8441086c6f6e67636f696e086c6f6e67636f696e0300000000000000000103167c5f674a8fd08efa61dd9b11121e046dd2c892730a756e6976322d636f7265168c5e2f8d25627d6edebeb6d10fa3300f5acc8441086c6f6e67636f696e086c6f6e67636f696e0300000000000000000102169ef3889fd070159edcd8ef88a0ec87cea1592c83168c5e2f8d25627d6edebeb6d10fa3300f5acc8441086c6f6e67636f696e086c6f6e67636f696e030000000000000000010316402da2c079e5d31d58b9cfc7286d1b1eb2f7834e0f616d6d2d7661756c742d76322d303116402da2c079e5d31d58b9cfc7286d1b1eb2f7834e0a746f6b656e2d616c657804616c65780300000000011c908a02162ec1a2dc2904ebc8b408598116c75e42c51afa2617726f757465722d76656c61722d616c65782d762d312d320d737761702d68656c7065722d6100000007010000000000000000000000000000271001000000000000000000000000011c908a040c00000002016106167c5f674a8fd08efa61dd9b11121e046dd2c892730477737478016206168c5e2f8d25627d6edebeb6d10fa3300f5acc8441086c6f6e67636f696e06167c5f674a8fd08efa61dd9b11121e046dd2c8927312756e6976322d73686172652d6665652d746f0c0000000201610616402da2c079e5d31d58b9cfc7286d1b1eb2f7834e0b746f6b656e2d776c6f6e6701620616402da2c079e5d31d58b9cfc7286d1b1eb2f7834e0a746f6b656e2d616c65780c0000000101610100000000000000000000000005f5e100";
851+
852+
#[test]
853+
fn test_anchor_mode_invalid_rejected() {
854+
let mut bytes = hex::decode(MODE_FIXTURE_HEX).unwrap();
855+
assert_eq!(bytes[109], 0x03);
856+
bytes[109] = 0xFF;
857+
let mut msg = ParsedObj::from_bytes(&bytes).unwrap();
858+
assert!(msg.read(&bytes).is_err());
859+
}
860+
861+
#[test]
862+
fn test_postcondition_mode_invalid_rejected() {
863+
let mut bytes = hex::decode(MODE_FIXTURE_HEX).unwrap();
864+
assert_eq!(bytes[110], 0x02);
865+
bytes[110] = 0xFF;
866+
let mut msg = ParsedObj::from_bytes(&bytes).unwrap();
867+
assert!(msg.read(&bytes).is_err());
868+
}
869+
870+
#[test]
871+
fn test_postcondition_mode_zero_rejected() {
872+
let mut bytes = hex::decode(MODE_FIXTURE_HEX).unwrap();
873+
bytes[110] = 0x00;
874+
let mut msg = ParsedObj::from_bytes(&bytes).unwrap();
875+
assert!(msg.read(&bytes).is_err());
876+
}
849877
}

app/rust/src/parser/transaction.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::{
1717

1818
use crate::{check_canary, zxformat};
1919

20-
use super::PostConditions;
20+
use super::{PostConditions, TransactionPostConditionMode};
2121

2222
// In multisig transactions the remainder should contain:
2323
// 32-byte previous signer post_sig_hash
@@ -44,7 +44,7 @@ pub enum TransactionAnchorMode {
4444

4545
impl TransactionAnchorMode {
4646
#[inline(never)]
47-
fn from_u8(v: u8) -> Option<Self> {
47+
pub fn from_u8(v: u8) -> Option<Self> {
4848
match v {
4949
1 => Some(Self::OnChainOnly),
5050
2 => Some(Self::OffChainOnly),
@@ -181,10 +181,12 @@ impl<'a> Transaction<'a> {
181181
fn read_transaction_modes(&mut self, data: &'a [u8]) -> Result<&'a [u8], ParserError> {
182182
c_zemu_log_stack("Transaction::read_transaction_modes\x00");
183183
// two modes are included here,
184-
// anchor mode and postcondition mode
184+
// anchor mode and postcondition mode — both must be valid enum variants
185185
let (rem, _) = take::<_, _, ParserError>(2usize)(data)
186186
.map_err(|_| ParserError::UnexpectedBufferEnd)?;
187187
let modes = arrayref::array_ref!(data, 0, 2);
188+
TransactionAnchorMode::from_u8(modes[0]).ok_or(ParserError::UnexpectedValue)?;
189+
TransactionPostConditionMode::from_u8(modes[1]).ok_or(ParserError::UnexpectedValue)?;
188190
self.transaction_modes = modes;
189191
check_canary!();
190192
Ok(rem)

app/rust/src/parser/transaction_payload/contract_call.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,11 @@ impl<'a> TransactionContractCall<'a> {
636636
pub fn num_items(&self, hide_sip10_details: bool) -> Result<u8, ParserError> {
637637
// contract-address, contract-name, function-name
638638
// + the number of arguments
639-
let num_args = self.num_args()? as u8;
639+
let raw_args = self.num_args()?;
640+
if raw_args > u8::MAX as u32 {
641+
return Err(ParserError::ValueOutOfRange);
642+
}
643+
let num_args = raw_args as u8;
640644
if hide_sip10_details {
641645
Ok(num_args)
642646
} else {

app/rust/src/parser/tx_post_conditions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub enum TransactionPostConditionMode {
1515

1616
impl TransactionPostConditionMode {
1717
#[inline(never)]
18-
fn from_u8(v: u8) -> Option<Self> {
18+
pub fn from_u8(v: u8) -> Option<Self> {
1919
match v {
2020
1 => Some(Self::Allow),
2121
2 => Some(Self::Deny),

app/src/apdu_handler.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
static bool tx_initialized = false;
3333

34+
bool review_pending = false;
35+
3436
__Z_INLINE void extractHDPath(uint32_t rx, uint32_t offset, uint32_t path_len) {
3537
if ((rx - offset) < sizeof(uint32_t) * path_len) {
3638
THROW(APDU_CODE_WRONG_LENGTH);
@@ -54,12 +56,12 @@ __Z_INLINE bool process_chunk(uint32_t rx) {
5456

5557
uint32_t added = 0;
5658
switch (payloadType) {
57-
case 0:
59+
case P1_INIT:
5860
tx_initialize();
5961
tx_reset();
6062
tx_initialized = true;
6163
return false;
62-
case 1:
64+
case P1_ADD:
6365
if (!tx_initialized) {
6466
THROW(APDU_CODE_TX_NOT_INITIALIZED);
6567
}
@@ -69,7 +71,7 @@ __Z_INLINE bool process_chunk(uint32_t rx) {
6971
THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL);
7072
}
7173
return false;
72-
case 2:
74+
case P1_LAST:
7375
if (!tx_initialized) {
7476
THROW(APDU_CODE_TX_NOT_INITIALIZED);
7577
}
@@ -78,6 +80,7 @@ __Z_INLINE bool process_chunk(uint32_t rx) {
7880
tx_initialized = false;
7981
THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL);
8082
}
83+
tx_initialized = false;
8184
return true;
8285
default:
8386
tx_initialized = false;
@@ -148,6 +151,7 @@ __Z_INLINE void handleGetAddrSecp256K1(volatile uint32_t *flags, volatile uint32
148151
}
149152

150153
if (requireConfirmation) {
154+
review_pending = true;
151155
app_fill_address(addr_secp256k1);
152156

153157
view_review_init(addr_getItem, addr_getNumItems, app_reply_address);
@@ -186,6 +190,7 @@ __Z_INLINE void SignSecp256K1(volatile uint32_t *flags, volatile uint32_t *tx, u
186190
zemu_log_stack("tx_parse done\n");
187191

188192
CHECK_APP_CANARY()
193+
review_pending = true;
189194
view_review_init(tx_getItem, tx_getNumItems, app_sign);
190195
view_review_show(REVIEW_TXN);
191196
*flags |= IO_ASYNCH_REPLY;
@@ -236,6 +241,10 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) {
236241
THROW(APDU_CODE_WRONG_LENGTH);
237242
}
238243

244+
if (review_pending) {
245+
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
246+
}
247+
239248
switch (G_io_apdu_buffer[OFFSET_INS]) {
240249
case INS_GET_VERSION: {
241250
handle_getversion(flags, tx, rx);
@@ -287,6 +296,7 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) {
287296
}
288297
}
289298
CATCH(EXCEPTION_IO_RESET) {
299+
review_pending = false;
290300
THROW(EXCEPTION_IO_RESET);
291301
}
292302
CATCH_OTHER(err) {

app/src/common/actions.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#pragma once
1717

1818
#include <os_io_seproxyhal.h>
19+
#include <stdbool.h>
1920
#include <stdint.h>
2021

2122
#include "apdu_codes.h"
@@ -67,6 +68,16 @@
6768

6869
extern uint8_t action_addr_len;
6970

71+
extern bool review_pending;
72+
73+
__Z_INLINE void set_review_pending(bool val) {
74+
review_pending = val;
75+
}
76+
77+
__Z_INLINE bool is_review_pending(void) {
78+
return review_pending;
79+
}
80+
7081
// helper function to get the presig_hash of the transaction being signed
7182
__Z_INLINE zxerr_t get_presig_hash(uint8_t *hash, uint16_t hashLen);
7283

@@ -128,6 +139,7 @@ __Z_INLINE void app_sign() {
128139
}
129140

130141
if (err != zxerr_ok) {
142+
set_review_pending(false);
131143
set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR);
132144
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
133145
return;
@@ -139,6 +151,7 @@ __Z_INLINE void app_sign() {
139151
uint16_t replyLen = 0;
140152
err = crypto_sign(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3, presig_hash, CX_SHA256_SIZE, &replyLen);
141153
if (err != zxerr_ok) {
154+
set_review_pending(false);
142155
set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR);
143156
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
144157
return;
@@ -180,23 +193,27 @@ __Z_INLINE void app_sign() {
180193
break;
181194
}
182195
default: {
196+
set_review_pending(false);
183197
set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR);
184198
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
185199
return;
186200
}
187201
}
188202

189203
if (replyLen == 0) {
204+
set_review_pending(false);
190205
set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR);
191206
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
192207
return;
193208
}
194209

210+
set_review_pending(false);
195211
set_code(G_io_apdu_buffer, replyLen, APDU_CODE_OK);
196212
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, replyLen + 2);
197213
}
198214

199215
__Z_INLINE void app_reject() {
216+
set_review_pending(false);
200217
tx_reset_state();
201218

202219
set_code(G_io_apdu_buffer, 0, APDU_CODE_COMMAND_NOT_ALLOWED);
@@ -236,11 +253,13 @@ __Z_INLINE uint8_t app_fill_auth_pubkey(address_kind_e kind) {
236253
}
237254

238255
__Z_INLINE void app_reply_address() {
256+
set_review_pending(false);
239257
set_code(G_io_apdu_buffer, action_addr_len, APDU_CODE_OK);
240258
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, action_addr_len + 2);
241259
}
242260

243261
__Z_INLINE void app_reply_error() {
262+
set_review_pending(false);
244263
set_code(G_io_apdu_buffer, 0, APDU_CODE_DATA_INVALID);
245264
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
246265
}

app/src/crypto.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ zxerr_t crypto_sign(uint8_t *buffer, uint16_t signatureMaxlen, const uint8_t *me
240240
err_convert_e err = convertDERtoRSV(signature->der_signature, info, signature->r, signature->s, &signature->v);
241241

242242
if (err != no_error) {
243-
return zxerr_encoding_failed;
243+
zxerr = zxerr_encoding_failed;
244+
goto catch_cx_error;
244245
}
245246

246247
// return actual size using value from signatureLength

0 commit comments

Comments
 (0)