improve validation of legacy multipart qr part numbers
What changed, and why it matters
This commit tightens how Sparrow Wallet handles multi-part QR codes used to import older wallet data. It limits part numbers to four digits and rejects impossible values (like part 0 of 0, or part 5 of 2). The change prevents the app from allocating huge arrays or getting confused by malformed QR frames, which could otherwise crash or freeze the wallet during a scan.
Treat this as a low-to-moderate hardening fix. Users importing wallets via animated QR should upgrade, especially if they scan untrusted QR sources. Review whether other QR parsers in the codebase have similar unbounded regex or allocation patterns.
Security signals we found
Input validation added to QR multipart parser
Regex quantifier tightened from unbounded to {1,4}
Bounds check prevents m > n and zero/negative counts
Sequence reset on mismatched total part count
Potential denial-of-service via malformed QR payload reduced
Evidence from the diff
QRScanDialog parses legacy multipart QR frames with the regex p(\d+)of(\d+) (.+). The patch caps both counters at four digits and adds validation: n < 1 || m < 1 || m > n. It also resets the parts buffer if the total part count n changes mid-sequence. These fixes close a path where a malicious or malformed QR code could cause excessive memory allocation, an ArrayIndexOutOfBoundsException, or incorrect reassembly of the scanned payload.
Changed components
src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.javaLegacy multipart QR scanning / animated QR importInspect captured patch +10 / −6
diff --git a/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java
index 7130b89..9e06c74 100644
--- a/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java
+++ b/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java
@@ -75,7 +75,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
private QRScanDialog.Result result;
- private static final Pattern PART_PATTERN = Pattern.compile("p(\\d+)of(\\d+) (.+)");
+ private static final Pattern PART_PATTERN = Pattern.compile("p(\\d{1,4})of(\\d{1,4}) (.+)");
private static final int SCAN_PERIOD_MILLIS = 100;
private final ObjectProperty<CaptureDevice> webcamDeviceProperty = new SimpleObjectProperty<>();
@@ -275,15 +275,19 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
int n = Integer.parseInt(partMatcher.group(2));
String payload = partMatcher.group(3);
- if(parts == null) {
+ if(n < 1 || m < 1 || m > n) {
+ log.warn("Ignoring invalid QR part " + m + " of " + n);
+ return;
+ }
+
+ //A different number of parts indicates a different sequence, so start over
+ if(parts == null || parts.size() != n) {
parts = new ArrayList<>(n);
IntStream.range(0, n).forEach(i -> parts.add(null));
}
parts.set(m - 1, payload);
- if(n > 0) {
- Platform.runLater(() -> percentComplete.setValue((double)parts.stream().filter(Objects::nonNull).count() / n));
- }
+ Platform.runLater(() -> percentComplete.setValue((double)parts.stream().filter(Objects::nonNull).count() / n));
if(parts.stream().filter(Objects::nonNull).count() == n) {
String complete = String.join("", parts);
Why this scored 46/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.