whatsapp: fix crash on malformed node (length_error abort)

A protobuf/binary length field could be read as a huge value, and the
`pos + len > size` bounds check overflowed size_t and slipped past, so
vector::assign threw std::length_error. Node dispatch (pairing, etc.) was
not wrapped in a catch, so this aborted the whole process (core dump) during
--whatsapp-link after the QR refreshed.

- ProtoReader::next and the binary Decoder now use overflow-safe length
  checks (len > remaining) and the decoder reads 4-byte lengths unsigned and
  rejects anything larger than the frame; read_bytes rejects negatives.
- WaClient::dispatch wraps all node routing in try/catch: a bad node is
  logged and skipped instead of crashing the client.

Verified: build/whatsapp_test 16/16, make lint clean, and a live
--whatsapp-link run survives the QR-refresh window with no abort.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 18:32:49 +01:00
co-authored by Claude Opus 4.8
parent 115e403e67
commit f16c5c6ede
4 changed files with 24 additions and 7 deletions
+10 -5
View File
@@ -55,24 +55,29 @@ class Decoder {
size_t pos_{0};
void need(size_t n) {
if (pos_ + n > buf_.size()) throw WireError("end of stream");
// overflow-safe: pos_ <= buf_.size() always holds here.
if (n > buf_.size() - pos_) throw WireError("end of stream");
}
uint8_t read_byte() {
need(1);
return buf_[pos_++];
}
Bytes read_bytes(int n) {
need(n);
if (n < 0) throw WireError("negative length");
need(static_cast<size_t>(n));
Bytes b(buf_.begin() + pos_, buf_.begin() + pos_ + n);
pos_ += n;
return b;
}
int read_int(int n) {
need(n);
int v = 0;
need(static_cast<size_t>(n));
// accumulate unsigned so a 4-byte length with the high bit set does not become
// a negative int that later underflows a size; reject anything a node cannot hold.
uint32_t v = 0;
for (int i = 0; i < n; ++i) v = (v << 8) | buf_[pos_ + i];
pos_ += n;
return v;
if (v > static_cast<uint32_t>(buf_.size())) throw WireError("length exceeds frame");
return static_cast<int>(v);
}
int read_int20() {
need(3);
+9
View File
@@ -119,6 +119,15 @@ void WaClient::send_ack(const WaNode& node) {
}
void WaClient::dispatch(const WaNode& node) {
// A malformed or unexpected node must never abort the process: log and move on.
try {
dispatch_inner(node);
} catch (const std::exception& e) {
log(std::string("dropped node <") + node.tag + ">: " + e.what());
}
}
void WaClient::dispatch_inner(const WaNode& node) {
if (node.tag == "iq") {
if (node.child("pair-device")) {
handle_pair_device(node);
+2 -1
View File
@@ -82,7 +82,8 @@ class WaClient {
// send a binary node (framed + noise transport encrypted)
void send_node(const WaNode& node);
std::optional<WaNode> recv_node(int timeout_seconds);
void dispatch(const WaNode& node);
void dispatch(const WaNode& node); // exception-safe wrapper
void dispatch_inner(const WaNode& node); // the actual routing
void handle_pair_device(const WaNode& iq);
void handle_pair_success(const WaNode& iq);
void send_ack(const WaNode& node);
+3 -1
View File
@@ -76,8 +76,10 @@ bool ProtoReader::next(Field& out) {
len |= static_cast<uint64_t>(b & 0x7f) << s;
if (!(b & 0x80)) break;
s += 7;
if (s > 63) throw std::runtime_error("wa proto: len varint too long");
}
if (pos_ + len > buf_.size()) throw std::runtime_error("wa proto: truncated bytes");
// overflow-safe bounds check: pos_ <= buf_.size() here, so this cannot wrap.
if (len > buf_.size() - pos_) throw std::runtime_error("wa proto: truncated bytes");
out.bytes.assign(buf_.begin() + pos_, buf_.begin() + pos_ + len);
pos_ += len;
return true;