Skip to content

[CI] next-release#81

Draft
overcat wants to merge 20 commits into
developfrom
next-release
Draft

[CI] next-release#81
overcat wants to merge 20 commits into
developfrom
next-release

Conversation

@overcat

@overcat overcat commented Jul 7, 2026

Copy link
Copy Markdown
Member

Checklist

  • App update process has been followed
  • Target branch is develop
  • Application version has been bumped

overcat and others added 20 commits March 24, 2026 11:56
# Conflicts:
#	Cargo.lock
The handler had no limit on accumulated message length: a ~5 KB message
of non-printable bytes escaped to a ~20 KB display string on a 16 KB
heap, aborting the app inside the allocator. Cap messages at 2 KB so
the escaped form (worst case 4x expansion) stays within budget, and
reject oversized input early with RequestDataTooLarge.

Also stop copying prefix + message into a temporary Vec just to hash
it — feed both parts to SHA-256 incrementally via the new hash_parts —
and build the escaped string with a single exact-size allocation
instead of collect's realloc growth.
…xt after terminal results

A transfer opening with P1_MORE skipped path parsing and proceeded with
the default path [0,0,0]; sign_tx then derives the public key before any
user interaction, and BOLOS rejects the out-of-manifest path with an
app-terminating exception — a one-APDU, no-interaction crash. Stale
raw_data also survived completed or errored signs, so a later
continuation chunk concatenated onto the previous transaction's bytes
under the previous path.

Track a chunk_started flag in AppContext and fail continuation chunks
with DataParsingFail while it is unset (covers both normal and swap
modes via the shared handle_chunk). Reset the context in the main loop
after every terminal result — after show_status_and_home_if_needed,
which still needs review_finished — keeping it only across successful
mid-stream chunks.

Well-behaved hosts are unaffected; out-of-order chunks now get a clean
0xB005 instead of state confusion or an app kill.
The "Pool Fee Rate" display used `fee as f64 / 100.0` inside format! —
the sole float in the app — pulling soft-float division and the float
shortest-roundtrip printer into the opt-level='z' LTO'd binary. Format
the percentage with integer math instead, reproducing the f64 Display
output exactly (trailing zeros trimmed, sign handled via unsigned_abs).

Equivalence is locked by a unit test sweeping -1M..=1M plus i32 edges
against the old expression byte-for-byte; on-screen output is unchanged
for every possible fee value.

Size (flex, release): .text 133,040 -> 123,824 bytes (-9,216, -6.9%).
op_count was an unvalidated attacker-controlled u32; ui_sign_tx renders
several heap strings per operation, so a forged count could exhaust the
16 KB heap and abort the app. Reject counts above 100 (the Stellar
protocol MAX_OPS_PER_TX) in Transaction::parse, covering both the
normal and swap paths. Real-world mainnet transactions in the dataset
go up to 49 operations, so no network-valid transaction is refused.
…iation

format_decimal, format_native_amount, format_token_amount, and
format_amount_with_asset were generic over T: ToString + Copy but
immediately stringify, so each caller type (u32, i64, u64, i128)
duplicated the full decimal/comma logic in the binary. Take a concrete
i128 (i64 for the private asset helper) instead — every amount type in
the app and every on-chain Stellar amount type converts losslessly, and
the exhaustive i128 tests now cover the one production instantiation.

Output is unchanged for all values. Size (flex, release): .text
123,824 -> 123,312 bytes (-512).
…ting

The manual timestamp algorithm was fuzz-verified against Python datetime
during review, but only 3 values were locked in tests. Add leap day,
Feb 28/Mar 1 transitions in leap and non-leap years, day 366, the
Gregorian century rule (2000 leap, 2100 not), and both sides of the
253402300799 cutoff, so regressions in the leap logic can't land
silently. Expected values computed with Python datetime (UTC).
…Fail

The three call sites are currently infallible (input is always the
internal 32-byte get_public_key output), but in a panic-aborts-the-app
codebase an .expect() is a latent crash point for any future refactor
that passes externally-sized data. Return AppSW::KeyDeriveFail instead;
behavior today is unchanged.
…hange contract

The fallback returned "0", which Exchange would display as a real
amount. The documented contract (and every other app: the C version of
this app, app-solana, app-xrp) sets an empty printable_amount to signal
failure. The path is unreachable today, so behavior is unchanged.
The swap validator parsed exactly one operation and returned, while the
whole raw buffer gets hashed and signed — bytes after the operation
(a non-void ext carrying SorobanTransactionData, or arbitrary trailing
garbage) were covered by the signature but never validated. Require a
void ext (0) after the operation and full buffer consumption, restoring
the sign-only-what-was-validated invariant on the no-UI swap path.

Well-formed Exchange payments end with exactly the 4-byte void ext, so
the legitimate flow is unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant