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:
@@ -55,24 +55,29 @@ class Decoder {
|
|||||||
size_t pos_{0};
|
size_t pos_{0};
|
||||||
|
|
||||||
void need(size_t n) {
|
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() {
|
uint8_t read_byte() {
|
||||||
need(1);
|
need(1);
|
||||||
return buf_[pos_++];
|
return buf_[pos_++];
|
||||||
}
|
}
|
||||||
Bytes read_bytes(int n) {
|
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);
|
Bytes b(buf_.begin() + pos_, buf_.begin() + pos_ + n);
|
||||||
pos_ += n;
|
pos_ += n;
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
int read_int(int n) {
|
int read_int(int n) {
|
||||||
need(n);
|
need(static_cast<size_t>(n));
|
||||||
int v = 0;
|
// 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];
|
for (int i = 0; i < n; ++i) v = (v << 8) | buf_[pos_ + i];
|
||||||
pos_ += n;
|
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() {
|
int read_int20() {
|
||||||
need(3);
|
need(3);
|
||||||
|
|||||||
@@ -119,6 +119,15 @@ void WaClient::send_ack(const WaNode& node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WaClient::dispatch(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.tag == "iq") {
|
||||||
if (node.child("pair-device")) {
|
if (node.child("pair-device")) {
|
||||||
handle_pair_device(node);
|
handle_pair_device(node);
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ class WaClient {
|
|||||||
// send a binary node (framed + noise transport encrypted)
|
// send a binary node (framed + noise transport encrypted)
|
||||||
void send_node(const WaNode& node);
|
void send_node(const WaNode& node);
|
||||||
std::optional<WaNode> recv_node(int timeout_seconds);
|
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_device(const WaNode& iq);
|
||||||
void handle_pair_success(const WaNode& iq);
|
void handle_pair_success(const WaNode& iq);
|
||||||
void send_ack(const WaNode& node);
|
void send_ack(const WaNode& node);
|
||||||
|
|||||||
@@ -76,8 +76,10 @@ bool ProtoReader::next(Field& out) {
|
|||||||
len |= static_cast<uint64_t>(b & 0x7f) << s;
|
len |= static_cast<uint64_t>(b & 0x7f) << s;
|
||||||
if (!(b & 0x80)) break;
|
if (!(b & 0x80)) break;
|
||||||
s += 7;
|
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);
|
out.bytes.assign(buf_.begin() + pos_, buf_.begin() + pos_ + len);
|
||||||
pos_ += len;
|
pos_ += len;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user