Files
pip/libs/io_utils/pistreampacker.cpp
andrey 407c4bb141 fix(pistreampacker): use declared mutexes and cap payload size read from stream
prog_s_mutex/prog_r_mutex were declared but never locked, so
concurrent send()/received()/clear()/receivePacketProgress() raced on
shared buffers and fragments of different senders interleaved on the
wire. Lock the receive state (stream/packet) with prog_r_mutex in
received()/clear()/receivePacketProgress() and the whole emission of
one packet with prog_s_mutex in send().

Additionally the size field of a packet taken from the stream was
only checked for negativity: a remote peer could declare INT_MAX and
the packer accumulated memory unboundedly, swallowing all following
data as phantom payload (permanent desync, no resync). Add a
max_payload_size cap (config, default 64MB) and resynchronize byte by
byte when the declared size exceeds it, so valid packets after the
corrupted one survive.

Also drop frames whose size does not fit the 32-bit wire format
instead of wrapping the length field.
2026-08-23 17:17:27 +03:00

196 lines
5.5 KiB
C++

/*
PIP - Platform Independent Primitives
Simple packet wrap aroud any PIIODevice
Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wnonnull"
#endif
#include "pistreampacker.h"
#include "piiodevice.h"
#include "pitranslator.h"
#include <climits>
#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif
/** \class PIStreamPacker
* \brief Simple packet wrap aroud any PIIODevice
*
* \section PIStreamPacker_synopsis Synopsis
* %PIStreamPacker provides simple pack/unpack logic for any data packets.
*
* When you call \a send() function data splited into several
* parts, \a packetSign() prepended to first part and \a sendRequest()
* event raised several times.
*
* When your device receive some data, call \a received() function.
* \a packetReceiveEvent() event will be raised when packet will be
* collected.
*
* Use \a assignDevice() to connect device to this %PIStreamPacker.
*
*/
PIStreamPacker::PIStreamPacker(PIIODevice * dev): PIObject() {
packet_size = -1;
if (dev) assignDevice(dev);
}
void PIStreamPacker::clear() {
PIMutexLocker locker(prog_r_mutex);
packet.clear();
packet_size = -1;
stream.clear();
}
void PIStreamPacker::send(const PIByteArray & data) {
if (data.isEmpty()) return;
// all fragments of one packet must be emitted as a single section,
// otherwise concurrent send() calls interleave fragments on the wire
PIMutexLocker locker(prog_s_mutex);
PIByteArray cd = cryptData(data);
// piCout << "crypt" << data.size() << "->" << cd.size() << key().size();
if (cd.size_s() > INT_MAX) {
piCoutObj << "Error: packet too large for the wire format, dropped"_tr("PIStreamPacker");
return;
}
PIByteArray hdr, part;
hdr << packet_sign;
if (crypt_size) {
PIByteArray crsz;
crsz << int(cd.size_s());
hdr.append(cryptData(crsz));
} else
hdr << int(cd.size_s());
cd.insert(0, hdr);
if (max_packet_size <= 0) return;
int pcnt = (cd.size_s() - 1) / max_packet_size + 1, pst = 0;
for (int i = 0; i < pcnt; ++i) {
if (i == pcnt - 1)
part = PIByteArray(cd.data(pst), cd.size_s() - pst);
else
part = PIByteArray(cd.data(pst), max_packet_size);
// piCout << "send" << part.size();
sendRequest(part);
pst += max_packet_size;
}
}
void PIStreamPacker::received(const uchar * readed, ssize_t size) {
if (size <= 0) return;
received(PIByteArray(readed, size));
}
void PIStreamPacker::received(const PIByteArray & data) {
PIMutexLocker locker(prog_r_mutex);
stream.append(data);
// piCout << "rec" << data.size();
while (!stream.isEmpty()) {
int hdr_size = sizeof(packet_sign) + sizeCryptedSize();
if (packet_size < 0) {
if (stream.size_s() < hdr_size) return;
ushort sign(0);
memcpy(&sign, stream.data(), 2);
if (sign != packet_sign) {
if (aggressive_optimization)
stream.clear();
else
stream.pop_front();
continue;
}
int sz = -1;
if (crypt_size) {
PIByteArray crsz(sizeCryptedSize());
memcpy(crsz.data(), stream.data(2), crsz.size());
crsz = decryptData(crsz);
if (crsz.size() < sizeof(sz)) {
if (aggressive_optimization)
stream.clear();
else
stream.pop_front();
continue;
}
crsz >> sz;
} else {
memcpy(&sz, stream.data(2), sizeCryptedSize());
}
if (sz < 0) {
if (aggressive_optimization)
stream.clear();
else
stream.pop_front();
continue;
}
if (sz > max_payload_size) {
// untrusted size: trusting it would accumulate memory unboundedly
// and swallow all following data as phantom packet payload;
// resync byte by byte so valid packets after the corrupted one survive
stream.pop_front();
continue;
}
stream.remove(0, hdr_size);
packet.clear();
packet_size = sz;
if (packet_size == 0)
packet_size = -1;
else
startPacketReceive(packet_size);
continue;
} else {
int ps = piMini(stream.size_s(), packet_size - packet.size_s());
packet.append(stream.data(), ps);
stream.remove(0, ps);
if (packet.size_s() == packet_size) {
PIByteArray cd = decryptData(packet);
// piCout << "decrypt" << packet.size() << "->" << cd.size() << key().size();
if (!cd.isEmpty()) {
endPacketReceive();
packetReceived(cd);
packetReceiveEvent(cd);
}
packet.clear();
packet_size = -1;
}
}
}
}
void PIStreamPacker::assignDevice(PIIODevice * dev) {
if (!dev) {
piCoutObj << "Error! device is NULL";
return;
}
if (!dev->infoFlags()[PIIODevice::Reliable]) {
piCoutObj << "Warning! Not recommended to use with non-reliable device"_tr("PIStreamPacker") << dev;
}
CONNECT2(void, const uchar *, ssize_t, dev, threadedReadEvent, this, received);
CONNECT1(void, PIByteArray, this, sendRequest, dev, write);
}
uint PIStreamPacker::sizeCryptedSize() {
return sizeof(int) + (crypt_size ? cryptSizeAddition() : 0);
}