Skip to content

Commit 988f21c

Browse files
committed
Merge branch 'dev' into feat/identify-script-type
* dev: feat: codeql triggers (#78) fix: stop confirming outputs before parsing them (#70) chore: changelog updates and utils (#72)
2 parents f33c94c + ec13449 commit 988f21c

File tree

13 files changed

+326
-77
lines changed

13 files changed

+326
-77
lines changed

.github/workflows/codeql.yml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
name: "CodeQL"
22

3-
on:
4-
push:
5-
branches: [ "master", "dev", "main", "develop" ]
6-
pull_request:
7-
branches: [ "master", "dev", "main", "develop" ]
8-
schedule:
9-
- cron: '16 19 * * 3'
3+
on: [push, pull_request]
104

115
jobs:
126
analyze:

CHANGELOG.md

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,34 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [1.0.0] - 2021-08-02
8+
## [Unreleased]
99

1010
### Added
1111

12-
- Inital commit with the brand new Hathor application
13-
- Sign TX command
14-
- Get XPUB command
15-
- Confirm address command
16-
- HTR + Version command
12+
- Support for authority outputs
13+
- Support for mint/melt/delegate/destroy operations
1714

18-
## [1.0.1] - 2021-09-22
15+
## Changed
16+
17+
- Signal bits and version parsed separetely, version is now uint8_t instead of uint16_t.
18+
- In sign tx command we check that change output index is valid (inside the output array) and not repeated.
19+
20+
## Fixed
21+
22+
- Auto confirmation of unreceived change outputs would fail tx signing
23+
- Some parsing errors would not clean the global context, it could fail the next command.
24+
25+
## [1.1.1] - 2024-03-12
1926

2027
### Added
2128

22-
- Support for Nano X
29+
- CodeQL security check in CI
30+
31+
### Security
32+
33+
- Code improvements to prevent null pointer exception and buffer overflow.
34+
- Update deprecated OS calls
35+
- Clean dead code
2336

2437
## [1.1.0] - 2022-02-4
2538

@@ -34,14 +47,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3447

3548
- Sign TX command now supports transactions with custom tokens
3649

37-
## [1.1.1] - 2024-03-12
50+
## [1.0.1] - 2021-09-22
3851

3952
### Added
4053

41-
- CodeQL security check
54+
- Support for Nano X
55+
56+
## [1.0.0] - 2021-08-02
57+
58+
### Added
59+
60+
- Inital commit with the brand new Hathor application
61+
- Sign TX command
62+
- Get XPUB command
63+
- Confirm address command
64+
- HTR + Version command
65+
4266

43-
### Changed
4467

45-
- Code improvements to prevent null pointer exception and buffer overflow.
46-
- Update deprecated OS calls
47-
- Clean dead code

doc/BUILD.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,29 @@ All dev commands are configured on a separate `.dev.Makefile`.
88
## Dev-env
99

1010
This command will start a docker container with the configured dev-env where you can run any commands on the `Makefile` or the SDK, like [loading](https://developers.ledger.com/docs/nano-app/load/) the app on Nano S.
11-
`make -f .dev.Makefile builder`
11+
12+
13+
```bash
14+
make -f .dev.Makefile builder
15+
```
1216

1317
## Compilation
1418

15-
`make -f .dev.Makefile build`
19+
```bash
20+
make -f .dev.Makefile build
21+
```
1622

1723
You can add the flag `DEBUG=1` at the end to compile on debug mode.
1824

1925
To remove all files generated from the build process run:
20-
`make -f .dev.Makefile clean`
26+
```bash
27+
make -f .dev.Makefile clean
28+
```
2129

2230
## Linter
2331

2432
Build linter with `make -f .dev.Makefile lint-build` and you can use the same linter (and linter configurations) as the CI using the commands:
33+
2534
- `make -f .dev.Makefile lint`
2635
- `make -f .dev.Makefile lint-fix`
2736

src/handler/sign_tx.c

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -72,30 +72,49 @@ bool verify_change(change_output_info_t *info, tx_output_t output) {
7272
* - read the bip32 path
7373
*
7474
* The outputs will be on the global context for sign tx (`tx_info`)
75+
*
76+
* returns a 16 bit error code if it fails and 0 on success
7577
**/
76-
void read_change_info_v1(buffer_t *cdata) {
78+
uint16_t read_change_info_v1(buffer_t *cdata) {
7779
if (!buffer_read_u8(cdata, &G_context.tx_info.change_len)) {
78-
THROW(SW_WRONG_DATA_LENGTH);
80+
return SW_WRONG_DATA_LENGTH;
7981
}
8082

8183
if (G_context.tx_info.change_len > TX_MAX_TOKENS + 1) {
8284
// Some token change is duplicated or we have more tokens than allowed
83-
THROW(SW_WRONG_DATA_LENGTH);
85+
return SW_WRONG_DATA_LENGTH;
8486
}
8587

88+
// TX_MAX_TOKENS + 1 because we allow 1 change for each token + HTR
89+
uint8_t known_indexes[TX_MAX_TOKENS + 1] = {0};
90+
uint8_t indexes_len = 0;
91+
8692
for (uint8_t i = 0; i < G_context.tx_info.change_len; i++) {
8793
change_output_info_t *info = &G_context.tx_info.change_info[i];
8894

8995
// 1 byte for change output index
9096
if (!buffer_read_u8(cdata, &info->index)) {
91-
THROW(SW_WRONG_DATA_LENGTH);
97+
return SW_WRONG_DATA_LENGTH;
9298
}
9399

100+
// check for duplicates
101+
for (uint8_t j = 0; j < indexes_len; j++) {
102+
if (info->index == known_indexes[j]) {
103+
// This output index was already read
104+
// This can happen if the change list is malformed.
105+
PRINTF("[-] [sign_tx] Duplicate change output index %d\n", info->index);
106+
return SW_INVALID_TX;
107+
}
108+
}
109+
known_indexes[indexes_len++] = info->index;
110+
94111
// bip32 path (path length + path data)
95112
if (!buffer_read_bip32_path(cdata, &info->path)) {
96-
THROW(SW_WRONG_DATA_LENGTH);
113+
return SW_WRONG_DATA_LENGTH;
97114
}
98115
}
116+
117+
return 0;
99118
}
100119

101120
/**
@@ -108,27 +127,29 @@ void read_change_info_v1(buffer_t *cdata) {
108127
* - read the bip32 path
109128
*
110129
* The outputs will be on the global context for sign tx (`tx_info`)
130+
*
131+
* returns a 16 bit error code if it fails and 0 on success
111132
**/
112-
void read_change_info_old_protocol(uint8_t change_byte, buffer_t *cdata) {
133+
uint16_t read_change_info_old_protocol(uint8_t change_byte, buffer_t *cdata) {
113134
uint8_t buffer[1 + 4 * MAX_BIP32_PATH] = {0};
114135
// Old protocol only allow 1 change output
115136
// and we already checked that there is an output present
116137
G_context.tx_info.change_len = 1;
117138
change_output_info_t *info = &G_context.tx_info.change_info[0];
118139

119140
if (!buffer_read_u8(cdata, &info->index)) {
120-
THROW(SW_WRONG_DATA_LENGTH);
141+
return SW_WRONG_DATA_LENGTH;
121142
}
122143

123144
buffer[0] = change_byte & 0x0F;
124145

125146
// validate that the path length is valid
126147
if (buffer[0] > MAX_BIP32_PATH) {
127-
THROW(SW_WRONG_DATA_LENGTH);
148+
return SW_WRONG_DATA_LENGTH;
128149
}
129150
// check we have enough data to read the path
130151
if (cdata->size - cdata->offset < 4 * buffer[0]) {
131-
THROW(SW_WRONG_DATA_LENGTH);
152+
return SW_WRONG_DATA_LENGTH;
132153
}
133154

134155
// buffer+1 has 4*MAX_BIP32_PATH capacity
@@ -139,20 +160,24 @@ void read_change_info_old_protocol(uint8_t change_byte, buffer_t *cdata) {
139160

140161
// bip32 path (path length + path data)
141162
if (!buffer_read_bip32_path(&bufdata, &info->path)) {
142-
THROW(SW_WRONG_DATA_LENGTH);
163+
return SW_WRONG_DATA_LENGTH;
143164
}
165+
166+
return 0;
144167
}
145168

146169
/**
147170
* Read change information
148171
*
149172
* First byte is the version byte for the protocol (for transactions)
150173
* It will be used to differentiate the old and new protocols
174+
*
175+
* returns a 16 bit error code if it fails and 0 on success
151176
**/
152-
void read_change_info(buffer_t *cdata) {
177+
uint16_t read_change_info(buffer_t *cdata) {
153178
uint8_t proto_version;
154179
if (!buffer_read_u8(cdata, &proto_version)) {
155-
THROW(SW_WRONG_DATA_LENGTH);
180+
return SW_WRONG_DATA_LENGTH;
156181
}
157182

158183
// check first byte for backwards compatibility
@@ -166,19 +191,22 @@ void read_change_info(buffer_t *cdata) {
166191
if (proto_version == 0) {
167192
// old protocol, no change
168193
G_context.tx_info.change_len = 0;
169-
return;
194+
return 0;
170195
}
171196

197+
// Old protocol with change
172198
if (proto_version & 0x80) {
173-
read_change_info_old_protocol(proto_version, cdata);
174-
} else {
175-
if (proto_version == 1) {
176-
read_change_info_v1(cdata);
177-
} else {
178-
// error
179-
THROW(SW_INVALID_TX);
180-
}
199+
return read_change_info_old_protocol(proto_version, cdata);
200+
}
201+
202+
// Protocol v1
203+
if (proto_version == 1) {
204+
return read_change_info_v1(cdata);
181205
}
206+
207+
// error, unknown change protocol
208+
PRINTF("[-] [sign_tx] Unknown change protocol version %d\n", proto_version);
209+
return SW_INVALID_TX;
182210
}
183211

184212
/**
@@ -194,8 +222,10 @@ void read_change_info(buffer_t *cdata) {
194222
* Obs: This is only on the first call with chunk=0, so it should never fail for lack of data
195223
*
196224
* The output will be on the global context for sign tx (`tx_info`)
225+
*
226+
* returns a 16 bit error code if it fails and 0 on success
197227
**/
198-
void read_tx_data(buffer_t *cdata) {
228+
uint16_t read_tx_data(buffer_t *cdata) {
199229
if (!(buffer_read_u16(cdata,
200230
&G_context.tx_info.tx_version,
201231
BE) && // read version bytes (Big Endian)
@@ -205,8 +235,27 @@ void read_tx_data(buffer_t *cdata) {
205235
buffer_read_u8(cdata, &G_context.tx_info.remaining_inputs) &&
206236
buffer_read_u8(cdata, &G_context.tx_info.outputs_len))) {
207237
// if an error occurs reading
208-
THROW(SW_WRONG_DATA_LENGTH);
238+
return SW_WRONG_DATA_LENGTH;
239+
}
240+
241+
for (uint8_t i = 0; i < G_context.tx_info.change_len; i++) {
242+
// check that all change indexes are inside the outputs array
243+
if (G_context.tx_info.change_info[i].index >= G_context.tx_info.outputs_len) {
244+
PRINTF("[-] [sign_tx] Change output index %d cannot be an output since length is %d\n",
245+
G_context.tx_info.change_info[i].index,
246+
G_context.tx_info.outputs_len);
247+
return SW_INVALID_TX;
248+
}
209249
}
250+
if (G_context.tx_info.change_len > G_context.tx_info.outputs_len) {
251+
// We have more change indexes than outputs, meaning the change list is malformed
252+
// This is unreachable since we do not allow duplicates and no index is higher than the
253+
// number of outputs even so we should check for this.
254+
PRINTF("[-] [sign_tx] More change indexes than outputs\n");
255+
return SW_INVALID_TX;
256+
}
257+
258+
return 0;
210259
}
211260

212261
/**
@@ -482,13 +531,23 @@ bool receive_data(buffer_t *cdata, uint8_t chunk) {
482531
}
483532

484533
init_sign_tx_ctx();
534+
uint16_t error_code = 0;
485535
// read change output and sighash
486-
read_change_info(cdata);
536+
error_code = read_change_info(cdata);
537+
if (error_code != 0) {
538+
explicit_bzero(&G_context, sizeof(G_context));
539+
THROW(error_code);
540+
}
487541
// sighash_all_hash won't move cdata.offset
488542
sighash_all_hash(cdata);
489-
read_tx_data(cdata);
543+
error_code = read_tx_data(cdata);
544+
if (error_code != 0) {
545+
explicit_bzero(&G_context, sizeof(G_context));
546+
THROW(error_code);
547+
}
490548

491549
if (!buffer_copy(cdata, G_context.tx_info.buffer, 300 - G_context.tx_info.buffer_len)) {
550+
explicit_bzero(&G_context, sizeof(G_context));
492551
THROW(SW_WRONG_DATA_LENGTH);
493552
}
494553

@@ -502,6 +561,7 @@ bool receive_data(buffer_t *cdata, uint8_t chunk) {
502561
if (!buffer_copy(cdata,
503562
G_context.tx_info.buffer + G_context.tx_info.buffer_len,
504563
300 - G_context.tx_info.buffer_len)) {
564+
explicit_bzero(&G_context, sizeof(G_context));
505565
THROW(SW_WRONG_DATA_LENGTH);
506566
}
507567
G_context.tx_info.buffer_len += cdata->size - cdata->offset;

src/token/token_parser.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@ int8_t find_token_registry_index(uint8_t *uid) {
4545
for (uint8_t i = 0; i < G_token_symbols.len; i++) {
4646
if (memcmp(uid, G_token_symbols.tokens[i].uid, TOKEN_UID_LEN) == 0) return i;
4747
}
48+
PRINTF("[*] Registry length = %d\n", G_token_symbols.len);
4849
return -1;
4950
}

src/ui/display.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,10 @@ bool skip_change_outputs() {
275275
}
276276
inplace_selection_sort((size_t) G_context.tx_info.change_len, change_indices);
277277

278+
// We may have reached the end of the parsed outputs
279+
// we need to check that before confirming any change outputs.
280+
if (check_output_index_state()) return true;
281+
278282
for (uint8_t i = 0; i < G_context.tx_info.change_len; i++) {
279283
if (G_context.tx_info.confirmed_outputs == change_indices[i]) {
280284
// we are on a change index
@@ -394,6 +398,7 @@ void ui_confirm_output(bool choice) {
394398
G_context.tx_info.confirmed_outputs++;
395399
// return if we are requesting more data or we have confirmed all outputs
396400
if (skip_change_outputs()) return;
401+
397402
// Show next output from buffer
398403
ui_display_tx_outputs();
399404
} else {

0 commit comments

Comments
 (0)