Skip to content

Commit 05c9cd9

Browse files
committed
Ensure temporary pointers allocated in G_io_apdu_buffer are memory aligned
Memory access of pointers to data types larger than one byte need to be aligned to 32bit words (or 16bit for shorts), as required by Ledger Nano S's hardware, and for other devices by the compiler flags (presumably, as other devices do in fact support non-aligned memory access, however without memory alignment the app froze on the unaligned memset in review_entries_launch_use_case_review since building with the SDK's Makefile.standard_app as of 5542f4b, but not before that change. I did not further investigate, which exact flag introduced the issue, or whether maybe the accessed pointer was 32bit aligned before that change by coincidence). Note that speculos does not enforce memory alignment, which is why the issue did not show on the emulator. Two instances of unaligned memory access existed: - in review_entries_launch_use_case_review for a nbgl_contentTagValueList_t * pointer on NBGL devices Flex and Stax, which caused these devices to freeze as of 5542f4b on memset and memcpy to the unaligned pointers. - in on_transaction_approved for a cx_ecfp_256_public_key_t * pointer on all devices, which was not harmful though as only uint8_t data was accessed, which does not need to be aligned.
1 parent 1f8177e commit 05c9cd9

File tree

3 files changed

+43
-16
lines changed

3 files changed

+43
-16
lines changed

src/main.c

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,8 @@ void on_transaction_approved() {
179179
created_staker_signature = true;
180180

181181
// Similarly, overwrite the public key in the signature proof with the ledger account public key as staker, with
182-
// G_io_apdu_buffer as temporary buffer again. Check with a compile time assertion that it can fit the temp data
183-
_Static_assert(
184-
sizeof(cx_ecfp_256_public_key_t) <= sizeof(G_io_apdu_buffer),
185-
"G_io_apdu_buffer does not fit public key\n"
186-
);
187-
cx_ecfp_256_public_key_t *temporary_public_key_pointer = (cx_ecfp_256_public_key_t*) G_io_apdu_buffer;
182+
// G_io_apdu_buffer as temporary buffer again.
183+
DECLARE_TEMPORARY_G_IO_APDU_BUFFER_POINTER(cx_ecfp_256_public_key_t *, temporary_public_key_pointer);
188184
GOTO_ON_ERROR(
189185
cx_ecfp_generate_pair_no_throw(
190186
/* curve */ CX_CURVE_Ed25519,

src/nimiq_ux_nbgl.c

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,11 @@ static void review_entries_launch_use_case_review(
141141
bool use_small_font
142142
) {
143143
// The tagValueList gets copied in nbgl_useCaseReview, therefore it's sufficient to initialize it only in temporary
144-
// memory. We use G_io_apdu buffer as temporary buffer, to save some stack space, and check that it can fit the data
145-
// with a compile time assertion. Note that the entries are not copied, and we therefore have them in global memory.
146-
_Static_assert(
147-
sizeof(nbgl_contentTagValueList_t) <= sizeof(G_io_apdu_buffer),
148-
"G_io_apdu_buffer can't fit review list\n"
149-
);
150-
nbgl_contentTagValueList_t *temporary_tag_value_list_pointer = (nbgl_contentTagValueList_t*) G_io_apdu_buffer;
144+
// memory. We use G_io_apdu buffer as temporary buffer, to save some stack space. Note that the review entries are
145+
// not copied, and we therefore have them in global memory.
146+
DECLARE_TEMPORARY_G_IO_APDU_BUFFER_POINTER(nbgl_contentTagValueList_t *, temporary_tag_value_list_pointer);
151147
// Initialize list with zeroes, including .nbMaxLinesForValue (no limit of lines to display).
152-
memset(temporary_tag_value_list_pointer, 0, sizeof(nbgl_contentTagValueList_t));
148+
memset(temporary_tag_value_list_pointer, 0, sizeof(*temporary_tag_value_list_pointer));
153149
temporary_tag_value_list_pointer->wrapping = true; // Prefer wrapping on spaces, e.g. for nicer address formatting.
154150
temporary_tag_value_list_pointer->smallCaseForValue = use_small_font;
155151
temporary_tag_value_list_pointer->pairs = review_entries.entries;
@@ -170,7 +166,7 @@ static void review_entries_launch_use_case_review(
170166
// static variable in nbgl_use_case.c and there's also no non-static method that exposes pointers to it. Instead, we
171167
// reset the temporary buffer, to make it immediately noticeable via Ragger end-to-end tests if the data was not
172168
// copied. Use explicit_bzero instead of memset to avoid this being optimized away.
173-
explicit_bzero(temporary_tag_value_list_pointer, sizeof(nbgl_contentTagValueList_t));
169+
explicit_bzero(temporary_tag_value_list_pointer, sizeof(*temporary_tag_value_list_pointer));
174170
}
175171

176172
//////////////////////////////////////////////////////////////////////

src/utility_macros.h

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656

5757
#define STRING_LENGTH_WITH_SUFFIX(length_a, suffix) (length_a - /* string terminator of string a */ 1 + sizeof(suffix))
5858

59-
#define STRUCT_MEMBER_SIZE(struct_type, member) (sizeof(((struct_type *) 0)->member))
59+
#define STRUCT_MEMBER_SIZE(struct_type, member) (sizeof(((struct_type *) NULL)->member))
6060

6161
/**
6262
* Copy data of fixed size to a destination of fixed size. This macro performs a static assertion at compile time, that
@@ -73,4 +73,39 @@
7373
memcpy(destination, source, sizeof(source)); \
7474
})
7575

76+
#define POINTER_SIZE (sizeof(void *))
77+
/**
78+
* Get the offset of a pointer from 32bit based memory alignment. Memory access of pointers to data types larger than
79+
* one byte need to be aligned to 32bit words / POINTER_SIZE (or 16bit for shorts, which we don't handle separately here
80+
* and always use 32bit alignment), as required by Ledger Nano S's hardware, and for other devices by the compiler flags
81+
* (presumably, as other devices do in fact support non-aligned memory access, however without memory alignment, the app
82+
* froze on the unaligned memset in review_entries_launch_use_case_review since using the SDK's Makefile.standard_app
83+
* for building as of 5542f4b6, but not before that change. I did not further investigate, which exact flag introduced
84+
* the issue, or whether maybe the accessed pointer was 32bit aligned before that change by coincidence). Note that the
85+
* speculos emulator does not enforce any memory alignment, which is why related issues do not show there.
86+
* In any case, it's a good idea to conform to memory alignment.
87+
*/
88+
#define POINTER_MEMORY_ALIGNMENT_OFFSET(pointer) (((uintptr_t) pointer) % POINTER_SIZE)
89+
90+
/**
91+
* Declare a pointer of given type within G_io_apdu_buffer for temporary use, while G_io_apdu_buffer is not otherwise
92+
* used or accessed. The pointer is ensured to be memory aligned, see POINTER_MEMORY_ALIGNMENT_OFFSET, and its type is
93+
* checked to fit G_io_apdu_buffer.
94+
*/
95+
#define DECLARE_TEMPORARY_G_IO_APDU_BUFFER_POINTER(pointer_type, variable_name) \
96+
_Static_assert( \
97+
/* The memory alignment offset can not be statically computed at compile time, therefore we assume for the */ \
98+
/* static assertion that the largest padding POINTER_SIZE - 1 has to be applied. */ \
99+
sizeof(*((pointer_type) NULL)) <= sizeof(G_io_apdu_buffer) - (POINTER_SIZE - 1), \
100+
"Pointer type does not fit G_io_apdu_buffer\n" \
101+
); \
102+
pointer_type variable_name = (pointer_type) ( \
103+
G_io_apdu_buffer + \
104+
( \
105+
POINTER_MEMORY_ALIGNMENT_OFFSET(G_io_apdu_buffer) \
106+
? POINTER_SIZE - POINTER_MEMORY_ALIGNMENT_OFFSET(G_io_apdu_buffer) \
107+
: 0 \
108+
) \
109+
);
110+
76111
#endif // _NIMIQ_UTILITY_MACROS_H_

0 commit comments

Comments
 (0)