fix(net): cap the length of a server line
A hostile server could stream an endless line with no newline and exhaust the client's memory. A single reply is now capped at 16 MiB (configurable via maxLineBytes in ConnectAsync); on excess the connection fails with InvalidDataException. See ADR 0035.
This commit is contained in:
1 parent
3be761d97e
commit
65c515bf7a
7 files changed
+121
-3
No files matched your search
@@ -0,0 +1,27 @@
|
||||
# 0035. Cap the length of a server line
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Area:** Network
|
||||
- **Recorded:** 2026-11-03
|
||||
|
||||
## Context
|
||||
|
||||
`ElectrumClient.ReadLoopAsync` accumulated bytes until it found a newline, with no maximum. A malicious, misconfigured or compromised server could send an endless line and drive memory use up until the process died, which is cheap to do and fatal on Android. `SECURITY.md` treats denial of service against the indexing server as out of scope, but here it is the client that dies, and the fix is small.
|
||||
|
||||
## Decision
|
||||
|
||||
- A single JSON-RPC line is capped at `ElectrumClient.DefaultMaxLineBytes` (16 MiB), a parameter of `ConnectAsync` (`maxLineBytes`). The default is well above the largest legitimate replies: header batches (a few hundred KB), a large transaction (up to about 2 MB of hex) and the history of a very active address (a few MB).
|
||||
- The check runs as the buffer grows, **before** a newline arrives, and also on complete lines, so neither an unterminated stream nor one huge terminated line is buffered past the cap (apart from what a single read delivers).
|
||||
- On excess the read loop fails with `InvalidDataException`. This reuses the protocol-error path: pending requests fail with that exception, the connection is marked dead, `Disconnected(ex)` is raised and later requests fail fast.
|
||||
|
||||
## Consequences
|
||||
|
||||
- One hostile server can no longer exhaust the memory of the client with an unbounded line.
|
||||
- A legitimate reply larger than 16 MiB would be refused; none is expected on this chain, and the cap is a parameter.
|
||||
- No new parser is introduced, so no new fuzz target.
|
||||
|
||||
## References
|
||||
|
||||
- `src/Core/Net/ElectrumClient.cs`
|
||||
- `tests/PalladiumWallet.Tests/Net/ElectrumClientTests.cs`
|
||||
- Related: 0009, 0034
|
||||
@@ -38,6 +38,7 @@ One ADR per feature or significant design decision. Format: `template.md`. Workf
|
||||
| 0032 | [Run tests, fuzz smoke and docs checks in GitHub Actions](0032-github-actions-ci.md) | Testing | Accepted |
|
||||
| 0033 | [Test the UI layer and CLI headlessly, gate coverage](0033-test-strategy-beyond-core.md) | Testing | Accepted |
|
||||
| 0034 | [Bound every network request with a timeout](0034-bound-every-network-request.md) | Network | Accepted |
|
||||
| 0035 | [Cap the length of a server line](0035-cap-server-line-length.md) | Network | Accepted |
|
||||
|
||||
ADRs `0002` onwards were written retroactively on 2026-11-03 from the code, comments and changelog.
|
||||
If one misstates the original reasoning, fix it in a pull request.
|
||||
Reference in new issue
Block a user