From 7dd3cb5ee43ebf4fdb2f0b8f5ef510aa4f58bc8a Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 22:19:34 +0300 Subject: [PATCH 01/36] small fixes --- libs/main/core/piinit.cpp | 4 ++-- libs/main/io_devices/pican.cpp | 4 ++-- libs/main/io_devices/pisharedmemory.cpp | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/libs/main/core/piinit.cpp b/libs/main/core/piinit.cpp index 266be335..5151e1b9 100644 --- a/libs/main/core/piinit.cpp +++ b/libs/main/core/piinit.cpp @@ -270,8 +270,8 @@ PIInit::~PIInit() { mach_port_deallocate(mach_task_self(), __pi_mac_clock); # endif if (PRIVATE->delete_locs) { - if (__syslocname__) delete __syslocname__; - if (__sysoemname__) delete __sysoemname__; + if (__syslocname__) delete[] __syslocname__; + if (__sysoemname__) delete[] __sysoemname__; } # ifdef PIP_ICU u_cleanup(); diff --git a/libs/main/io_devices/pican.cpp b/libs/main/io_devices/pican.cpp index 6d1e2295..57bdac55 100644 --- a/libs/main/io_devices/pican.cpp +++ b/libs/main/io_devices/pican.cpp @@ -124,8 +124,8 @@ ssize_t PICAN::readDevice(void * read_to, ssize_t max_size) { #ifdef PIP_CAN if (sock == -1) return -1; // piCout << "PICAN read"; - can_frame frame; - ssize_t ret = 0; + can_frame frame = {}; + ssize_t ret = 0; if (PRIVATE->event.wait(sock)) ret = ::read(sock, &frame, sizeof(can_frame)); if (ret < 0) { /*piCoutObj << "Error while read CAN frame " << ret;*/ return -1; diff --git a/libs/main/io_devices/pisharedmemory.cpp b/libs/main/io_devices/pisharedmemory.cpp index 8a4fc2ad..c50ea67d 100644 --- a/libs/main/io_devices/pisharedmemory.cpp +++ b/libs/main/io_devices/pisharedmemory.cpp @@ -251,6 +251,7 @@ int PISharedMemory::read(void * read_to, int max_size) { int PISharedMemory::read(void * read_to, int max_size, int offset) { + if (offset < 0 || max_size < 0 || offset > dsize - max_size) return -1; #ifdef WINDOWS if (!PRIVATE->data) return -1; CopyMemory(read_to, &(((char *)(PRIVATE->data))[offset]), max_size); @@ -271,6 +272,7 @@ int PISharedMemory::write(const void * data, int max_size) { int PISharedMemory::write(const void * data, int max_size, int offset) { + if (offset < 0 || max_size < 0 || offset > dsize - max_size) return -1; #ifdef WINDOWS if (!PRIVATE->data) return -1; CopyMemory(&(((char *)(PRIVATE->data))[offset]), data, max_size); -- 2.54.0 From 449b210d4e27f689ef49b83463d73c614de82f57 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 22:20:26 +0300 Subject: [PATCH 02/36] add test for pisharedmemory --- tests/io/testpisharedmemory.cpp | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/io/testpisharedmemory.cpp diff --git a/tests/io/testpisharedmemory.cpp b/tests/io/testpisharedmemory.cpp new file mode 100644 index 00000000..b898ff7f --- /dev/null +++ b/tests/io/testpisharedmemory.cpp @@ -0,0 +1,35 @@ +#include "pisharedmemory.h" + +#include "gtest/gtest.h" + +#ifdef WINDOWS +# include +#else +# include +#endif + +TEST(PISharedMemory_Test, ReadWriteBounds) { + PISharedMemory shm("pip_bughunt_shm_bounds_" + PIString::fromNumber(int(getpid())), 64); + ASSERT_TRUE(shm.open()); + + const char payload[32] = "0123456789abcdef"; + ASSERT_EQ(32, shm.write(payload, 32)); + ASSERT_EQ(16, shm.write(payload, 16, 10)); + + // Out-of-bounds writes must be rejected, not silently overflow the mapping. + EXPECT_EQ(-1, shm.write(payload, 100)); // exceeds size + EXPECT_EQ(-1, shm.write(payload, 32, 97)); // offset + size exceeds size + EXPECT_EQ(-1, shm.write(payload, 16, -5)); // negative offset + + // Out-of-bounds reads must be rejected. + char dst[128]; + EXPECT_EQ(-1, shm.read(dst, 200)); // exceeds size + EXPECT_EQ(-1, shm.read(dst, 32, 97)); // offset + size exceeds size + EXPECT_EQ(-1, shm.read(dst, 32, -1)); + + // In-bounds reads must still succeed. + EXPECT_EQ(16, shm.read(dst, 16, 10)); + ASSERT_EQ(0, memcmp(dst, "0123456789abcdef", 16)); + + ASSERT_TRUE(shm.close()); +} \ No newline at end of file -- 2.54.0 From 6d75d10ec8bfd59f217afaeab374553d096de8f1 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 23:20:58 +0300 Subject: [PATCH 03/36] fix: PICAN openDevice buffer overflow in ifr_name --- libs/main/io_devices/pican.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/main/io_devices/pican.cpp b/libs/main/io_devices/pican.cpp index 57bdac55..3a6f2708 100644 --- a/libs/main/io_devices/pican.cpp +++ b/libs/main/io_devices/pican.cpp @@ -73,7 +73,8 @@ bool PICAN::openDevice() { } fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK); ifreq ifr; - strcpy(ifr.ifr_name, path().dataAscii()); + piZeroMemory(ifr); + strncpy(ifr.ifr_name, path().dataAscii(), sizeof(ifr.ifr_name)); piCout << "PICAN try to get interface index..."; if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) { piCoutObj << "Error! while determin the interface ioctl"; -- 2.54.0 From 38253effc6927d001a504f035bf54244f5e3d20e Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 23:21:08 +0300 Subject: [PATCH 04/36] fix: SIOCGIFNETMASK reads ifr_addr instead of ifr_netmask --- libs/main/io_devices/piethernet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp index a1245cc1..a27d26ba 100644 --- a/libs/main/io_devices/piethernet.cpp +++ b/libs/main/io_devices/piethernet.cpp @@ -1206,7 +1206,7 @@ PIEthernet::InterfaceList PIEthernet::interfaces() { strcpy(ir.ifr_name, in.dataAscii()); if (ioctl(s, SIOCGIFHWADDR, &ir) == 0) ci.mac = macFromBytes(PIByteArray(ir.ifr_hwaddr.sa_data, 6)); if (ioctl(s, SIOCGIFADDR, &ir) >= 0) ci.address = getSockAddr(&ir.ifr_addr); - if (ioctl(s, SIOCGIFNETMASK, &ir) >= 0) ci.netmask = getSockAddr(&ir.ifr_addr); + if (ioctl(s, SIOCGIFNETMASK, &ir) >= 0) ci.netmask = getSockAddr(&ir.ifr_netmask); ioctl(s, SIOCGIFMTU, &ci.mtu); if (ci.address == "127.0.0.1") ci.flags |= PIEthernet::ifLoopback; il << ci; -- 2.54.0 From 27ed1bc9a7805de84ba6677231e056d174a6389a Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 23:21:17 +0300 Subject: [PATCH 05/36] fix: SIOCGIFMTU passes int* instead of ifreq* (Android) --- libs/main/io_devices/piethernet.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp index a27d26ba..0cd89f3e 100644 --- a/libs/main/io_devices/piethernet.cpp +++ b/libs/main/io_devices/piethernet.cpp @@ -1207,7 +1207,9 @@ PIEthernet::InterfaceList PIEthernet::interfaces() { if (ioctl(s, SIOCGIFHWADDR, &ir) == 0) ci.mac = macFromBytes(PIByteArray(ir.ifr_hwaddr.sa_data, 6)); if (ioctl(s, SIOCGIFADDR, &ir) >= 0) ci.address = getSockAddr(&ir.ifr_addr); if (ioctl(s, SIOCGIFNETMASK, &ir) >= 0) ci.netmask = getSockAddr(&ir.ifr_netmask); - ioctl(s, SIOCGIFMTU, &ci.mtu); + if (ioctl(s, SIOCGIFMTU, &ir) == 0) { + ci.mtu = ir.ifr_mtu; + } if (ci.address == "127.0.0.1") ci.flags |= PIEthernet::ifLoopback; il << ci; } -- 2.54.0 From 736acbd41947b08d86ff480a9d0bcd73567f580c Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 23:21:26 +0300 Subject: [PATCH 06/36] fix: interfaceAddress buffer overflow on long interface names --- libs/main/io_devices/piethernet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp index 0cd89f3e..9f628a70 100644 --- a/libs/main/io_devices/piethernet.cpp +++ b/libs/main/io_devices/piethernet.cpp @@ -1303,7 +1303,7 @@ PINetworkAddress PIEthernet::interfaceAddress(const PIString & interface_) { #else struct ifreq ifr; piZeroMemory(ifr); - strcpy(ifr.ifr_name, interface_.dataAscii()); + strncpy(ifr.ifr_name, interface_.dataAscii(), sizeof(ifr.ifr_name)); int s = ::socket(AF_INET, SOCK_DGRAM, 0); if (s != -1) { ioctl(s, SIOCGIFADDR, &ifr); -- 2.54.0 From cb5530c864d89f7f4be5ff05c3288777d3db9e4a Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 23:21:36 +0300 Subject: [PATCH 07/36] fix: writePipe loses data on partial write > PIPE_BUF --- libs/main/system/piprocess.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index 115b9a69..005b56cc 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -242,7 +242,16 @@ PRIVATE_DEFINITION_START(PIProcess) BOOL ok = WriteFile(pipes[StdIn][PipeWrite], data.data(), data.size(), &sz, NULL); if (!ok) sz = 0; # else - sz = ::write(pipes[StdIn][PipeWrite], data.data(), data.size()); + size_t offset = 0; + while (offset < data.size()) { + ssize_t wr = ::write(pipes[StdIn][PipeWrite], data.data() + offset, data.size() - offset); + if (wr < 0) { + if (errno == EINTR) continue; + return false; + } + offset += wr; + } + sz = offset; # endif return sz == (SizeType)data.size_s(); } -- 2.54.0 From 9680bb2af8df31eef052eb2a3d6025b89f022069 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 23:55:16 +0300 Subject: [PATCH 08/36] fix: remove unreachable return false in PICrypt::verifySign() fix picrypth --- libs/crypt/picrypt.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/crypt/picrypt.cpp b/libs/crypt/picrypt.cpp index bee924d9..c31560dc 100644 --- a/libs/crypt/picrypt.cpp +++ b/libs/crypt/picrypt.cpp @@ -20,6 +20,7 @@ #include "picrypt.h" #include "pitranslator.h" + #include @@ -53,7 +54,7 @@ bool PICrypt::setKey(const PIByteArray & _key) { } -bool PICrypt::setKey(const PIString & secret) {; +bool PICrypt::setKey(const PIString & secret) { key_ = hash(secret); return key_.isNotEmpty(); } @@ -262,7 +263,6 @@ PIByteArray PICrypt::signMessage(const PIByteArray & data, const PIByteArray & s bool PICrypt::verifySign(const PIByteArray & data, const PIByteArray & signature, const PIByteArray & public_key) { if (!init()) return false; return (crypto_sign_verify_detached(signature.data(), data.data(), data.size(), public_key.data()) == 0); - return false; } -- 2.54.0 From 96f0221051fb575b727f44823fa728ebb80186d6 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 23:55:25 +0300 Subject: [PATCH 09/36] fix: PIStreamPacker division by zero when max_packet_size is 0 --- libs/io_utils/pistreampacker.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/io_utils/pistreampacker.cpp b/libs/io_utils/pistreampacker.cpp index c16db3b0..9b67e18f 100644 --- a/libs/io_utils/pistreampacker.cpp +++ b/libs/io_utils/pistreampacker.cpp @@ -73,6 +73,7 @@ void PIStreamPacker::send(const PIByteArray & data) { } 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) -- 2.54.0 From c3491cbf6ff3a1d03a8a5b787998f88db568c411 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 23:55:43 +0300 Subject: [PATCH 10/36] =?UTF-8?q?fix:=20PISingleApplication=20TOCTOU=20?= =?UTF-8?q?=E2=80=94=20two=20shm=20reads=20replaced=20with=20single=20atom?= =?UTF-8?q?ic=20read?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/main/application/pisingleapplication.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libs/main/application/pisingleapplication.cpp b/libs/main/application/pisingleapplication.cpp index 46e342e3..217a1ba7 100644 --- a/libs/main/application/pisingleapplication.cpp +++ b/libs/main/application/pisingleapplication.cpp @@ -128,12 +128,14 @@ void PISingleApplication::run() { int st_[2] = {sacnt, sacnt}; shm->write(st_, 8); // piCoutObj << "write" << sacnt; - int ri[3] = {0, 0, 0}; const int hdr_sz = sizeof(int) * 3; - shm->read(ri, hdr_sz); + PIByteArray all(shm->size()); + shm->read(all.data(), all.size()); + int ri[3] = {0, 0, 0}; + memcpy(ri, all.data(), hdr_sz); if (ri[2] != 0 && ri[0] == ri[1]) { - readed.resize(shm->size() - hdr_sz); - shm->read(readed.data(), readed.size(), hdr_sz); + readed.resize(all.size() - hdr_sz); + memcpy(readed.data(), all.data() + hdr_sz, readed.size()); PIByteArray msg; readed >> msg; if (msg.isNotEmpty()) { -- 2.54.0 From 4f601a940844d3f52d9e0e8cdf630ad419ffb8ea Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 23:55:59 +0300 Subject: [PATCH 11/36] =?UTF-8?q?fix:=20removeAllFilters=20holds=20lock=20?= =?UTF-8?q?during=20delete=20=E2=80=94=20move=20destructors=20outside=20lo?= =?UTF-8?q?ck=20scope?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/main/io_utils/piconnection.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libs/main/io_utils/piconnection.cpp b/libs/main/io_utils/piconnection.cpp index ee7d0a5b..2e0e407f 100644 --- a/libs/main/io_utils/piconnection.cpp +++ b/libs/main/io_utils/piconnection.cpp @@ -572,6 +572,8 @@ bool PIConnection::removeFilter(const PIString & name_) { void PIConnection::removeAllFilters() { + PIVector to_delete; + PIVector diags_to_delete; __device_pool__->lock(); for (auto i = extractors.begin(); i != extractors.end(); i++) { if (!i.value()) continue; @@ -581,14 +583,18 @@ void PIConnection::removeAllFilters() { it.value().removeAll(i.value()->extractor); } if (diags_.value(i.value()->extractor)) { - delete diags_.value(i.value()->extractor); + diags_to_delete << diags_.value(i.value()->extractor); + diags_.remove(i.value()->extractor); } - diags_.remove(i.value()->extractor); - delete i.value(); + to_delete << i.value(); } extractors.clear(); bounded_extractors.clear(); __device_pool__->unlock(); + for (auto e: to_delete) + delete e; + for (auto d: diags_to_delete) + delete d; } -- 2.54.0 From f5cf06c20048f1d201e9f65c791724765db9673a Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 23:56:10 +0300 Subject: [PATCH 12/36] =?UTF-8?q?fix:=20addDevice=20deletes=20read=20threa?= =?UTF-8?q?d=20without=20stopAndWait=20=E2=80=94=20race=20on=20dev=20close?= =?UTF-8?q?/open?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/main/io_utils/piconnection.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/main/io_utils/piconnection.cpp b/libs/main/io_utils/piconnection.cpp index 2e0e407f..50f6e565 100644 --- a/libs/main/io_utils/piconnection.cpp +++ b/libs/main/io_utils/piconnection.cpp @@ -1000,6 +1000,7 @@ PIIODevice * PIConnection::DevicePool::addDevice(PIConnection * parent, const PI if (pmode == mode || pmode == PIIODevice::ReadWrite) return dd->dev; if ((mode & PIIODevice::ReadOnly) > 0) { if (dd->rthread) { + dd->rthread->stopAndWait(); delete dd->rthread; dd->rthread = nullptr; dd->started = false; -- 2.54.0 From 288c1b3575657ddde212fd8237f1bb7685f187f9 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 23:56:25 +0300 Subject: [PATCH 13/36] =?UTF-8?q?fix:=20Sender::tick=20accesses=20parent->?= =?UTF-8?q?diags=5F=20without=20lock=20=E2=80=94=20data=20race=20/=20use-a?= =?UTF-8?q?fter-free?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/main/io_utils/piconnection.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/libs/main/io_utils/piconnection.cpp b/libs/main/io_utils/piconnection.cpp index 50f6e565..8b04f33a 100644 --- a/libs/main/io_utils/piconnection.cpp +++ b/libs/main/io_utils/piconnection.cpp @@ -1234,9 +1234,14 @@ void PIConnection::Sender::tick(int) { if (data.isEmpty()) return; // piCoutObj << "write"<write(data); - PIDiagnostics * diag = parent->diags_.value(d, nullptr); - if (diag && ret > 0) diag->sended(ret); + int ret = d->write(data); + if (ret > 0) { + PIDiagnostics * diag = nullptr; + __device_pool__->lock(); + diag = parent->diags_.value(d, nullptr); + __device_pool__->unlock(); + if (diag) diag->sended(ret); + } } } -- 2.54.0 From 9c3690d371b7c439f3d8392c883c3eedea2a9412 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Thu, 6 Aug 2026 10:18:30 +0300 Subject: [PATCH 14/36] fix(PIFFT_float): correct plan entry size from 4 to 8 The float FFT plan generator wrote 8 ints per entry (indices +0..+7) but declared entrysize=4, causing heap overwrite into the next entry and incorrect plan array sizing. --- libs/main/math/pifft.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/main/math/pifft.cpp b/libs/main/math/pifft.cpp index 129328d0..afdbc6f2 100644 --- a/libs/main/math/pifft.cpp +++ b/libs/main/math/pifft.cpp @@ -1205,7 +1205,7 @@ void PIFFT_float::ftbase_ftbasegenerateplanrec(int n, ae_int_t stackptr, int debugi) { int k, m, n1, n2, esize, entryoffset; - int ftbase_ftbaseplanentrysize = 4; + int ftbase_ftbaseplanentrysize = 8; int ftbase_ftbasecffttask = 0; int ftbase_fftcooleytukeyplan = 0; int ftbase_fftbluesteinplan = 1; -- 2.54.0 From ef8fdb30f40e23943ce5dbd6fde430abcdaf92d9 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Thu, 6 Aug 2026 10:18:40 +0300 Subject: [PATCH 15/36] fix(PISharedMemory): reset data and unlink on mmap failure When mmap() returned MAP_FAILED, data stayed as (void*)-1, causing subsequent read/write/close to pass the null guard and crash. Now resets data=nullptr, and calls shm_unlink when owner=true to avoid orphaned shared memory objects. --- libs/main/io_devices/pisharedmemory.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/main/io_devices/pisharedmemory.cpp b/libs/main/io_devices/pisharedmemory.cpp index c50ea67d..6577b5dc 100644 --- a/libs/main/io_devices/pisharedmemory.cpp +++ b/libs/main/io_devices/pisharedmemory.cpp @@ -144,6 +144,9 @@ bool PISharedMemory::openDevice() { ::close(fd); if (PRIVATE->data == MAP_FAILED) { piCoutObj << "mmap error," << errorString(); + if (PRIVATE->owner) shm_unlink((const char *)PRIVATE->name.data()); + PRIVATE->data = nullptr; + PRIVATE->owner = false; return false; } // piCoutObj << "opened" << PRIVATE->data; -- 2.54.0 From c3cd9496ddc9e0827cd130ff66db4521cac7f094 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Thu, 6 Aug 2026 10:18:46 +0300 Subject: [PATCH 16/36] fix(PIVariant): add missing break in setValueFromString switch Three complex type cases (pivComplexf, pivComplexd, pivComplexld) lacked break statements, causing fallthrough to pivTime. Setting a complex variant from string silently overwrote the value with PITime::fromString(), corrupting the variant's type and data. --- libs/main/types/pivariant.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/main/types/pivariant.cpp b/libs/main/types/pivariant.cpp index 5de19a3a..8ec9c04c 100644 --- a/libs/main/types/pivariant.cpp +++ b/libs/main/types/pivariant.cpp @@ -133,15 +133,15 @@ void PIVariant::setValueFromString(const PIString & v) { case PIVariant::pivComplexf: { PIStringList sl = v.mid(1, v.size_s() - 2).split(';'); setValue(complexf(sl.size() > 0 ? sl[0].toFloat() : 0.f, sl.size() > 1 ? sl[1].toFloat() : 0.f)); - } + } break; case PIVariant::pivComplexd: { PIStringList sl = v.mid(1, v.size_s() - 2).split(';'); setValue(complexd(sl.size() > 0 ? sl[0].toDouble() : 0., sl.size() > 1 ? sl[1].toDouble() : 0.)); - } + } break; case PIVariant::pivComplexld: { PIStringList sl = v.mid(1, v.size_s() - 2).split(';'); setValue(complexld(sl.size() > 0 ? sl[0].toLDouble() : 0.L, sl.size() > 1 ? sl[1].toLDouble() : 0.L)); - } + } break; case PIVariant::pivTime: { setValue(PITime::fromString(v)); } break; @@ -243,7 +243,7 @@ PIVariant::Type PIVariant::typeFromName(const PIString & tname) { if (s == "int" || s == "signed" || s == "signedint" || s == "int32_t") return PIVariant::pivInt; if (s == "long" || s == "longint" || s == "signedlong" || s == "signedlongint" || s == "sdword") return PIVariant::pivInt; if (s == "llong" || s == "longlong" || s == "longlongint" || s == "signedlonglong" || s == "signedlonglongint" || s == "sqword" || - s == "int64_t") + s == "int64_t") return PIVariant::pivLLong; if (s == "uchar" || s == "byte" || s == "uint8_t") return PIVariant::pivUChar; if (s == "ushort" || s == "unsignedshort" || s == "unsignedshortint" || s == "word" || s == "uint16_t") return PIVariant::pivUShort; -- 2.54.0 From 3e72f4e533aa1291105bbdfabd6b131abb103d9a Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Thu, 6 Aug 2026 10:18:51 +0300 Subject: [PATCH 17/36] fix(PIBitArray): guard pop_back() against empty array pop_back() called resize(size_ - 1) without checking for empty. On empty array, uint underflow produced UINT_MAX, causing bytesInBits(UINT_MAX) overflow and subsequent OOB access. pop_front() already had this guard; pop_back() was missing it. --- libs/main/types/pibitarray.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/main/types/pibitarray.h b/libs/main/types/pibitarray.h index 4723e0ab..189e2067 100644 --- a/libs/main/types/pibitarray.h +++ b/libs/main/types/pibitarray.h @@ -5,7 +5,7 @@ //! \~russian Упакованный массив битов /* PIP - Platform Independent Primitives - Packed bit array + Packed bit array Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify @@ -173,7 +173,10 @@ public: //! \~english Remove one bit from the end of array. //! \~russian Удаляет один бит с конца массива. - PIBitArray & pop_back() { return resize(size_ - 1); } + PIBitArray & pop_back() { + if (size_ == 0) return *this; + return resize(size_ - 1); + } //! \~english Remove one bit from the beginning of array. //! \~russian Удаляет один бит с начала массива. -- 2.54.0 From 4d9cd20ac59b57eb4cedd470b936b42fbcb2ad01 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Thu, 6 Aug 2026 10:18:57 +0300 Subject: [PATCH 18/36] fix(PIDir): initialize list pointer and check scandir return MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dirent** list was uninitialized, so scandir failure (returning -1) left it with indeterminate value. The unconditional free(list) at the end was then UB — typically heap corruption or crash. --- libs/main/io_devices/pidir.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/libs/main/io_devices/pidir.cpp b/libs/main/io_devices/pidir.cpp index bb0607b4..6aabe59d 100644 --- a/libs/main/io_devices/pidir.cpp +++ b/libs/main/io_devices/pidir.cpp @@ -372,15 +372,16 @@ PIVector PIDir::entries(const PIRegularExpression & regexp) { closedir(dir); } # else - dirent ** list; - int cnt = scandir(p.data(), - &list, - 0, + dirent ** list = nullptr; + int cnt = scandir(p.data(), + &list, + 0, # if defined(MAC_OS) || defined(ANDROID) || defined(BLACKBERRY) - alphasort); + alphasort); # else versionsort); # endif + if (cnt < 0) return ret; for (int i = 0; i < cnt; ++i) { ret << PIFile::fileInfo(dp + PIString(list[i]->d_name)); free(list[i]); -- 2.54.0 From 31ae52623d79ab17bc8e3c6d8858206201c2bc8d Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Thu, 6 Aug 2026 10:19:02 +0300 Subject: [PATCH 19/36] fix(PIConditionVariable): clamp negative timeout in waitFor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When elapsed time exceeded the timeout, the remaining milliseconds expression went negative and was implicitly converted to DWORD, wrapping to ~0xFFFFFFFF (5 days) — effectively INFINITE. Now clamps to 0 and returns false when time has elapsed. --- libs/main/thread/piconditionvar.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/libs/main/thread/piconditionvar.cpp b/libs/main/thread/piconditionvar.cpp index 753b2cfc..d32dc3a4 100644 --- a/libs/main/thread/piconditionvar.cpp +++ b/libs/main/thread/piconditionvar.cpp @@ -135,17 +135,19 @@ bool PIConditionVariable::waitFor(PIMutex & lk, PISystemTime timeout, std::funct if (condition()) break; bool isTimeout; #if defined(WINDOWS) - isTimeout = SleepConditionVariableCS(&PRIVATE->nativeHandle, - (PCRITICAL_SECTION)lk.handle(), - timeout.toMilliseconds() - (int)measurer.elapsed_m()) == 0; + { + int remain = (int)(timeout.toMilliseconds() - (int)measurer.elapsed_m()); + if (remain <= 0) return false; + isTimeout = SleepConditionVariableCS(&PRIVATE->nativeHandle, (PCRITICAL_SECTION)lk.handle(), remain) == 0; + } #elif defined(FREERTOS) - EventBits_t uxBits; - uxBits = xEventGroupWaitBits(PRIVATE->nativeHandle, - 1, - pdTRUE, - pdTRUE, - (timeout.toMilliseconds() - (int)measurer.elapsed_m()) / portTICK_PERIOD_MS); - isTimeout = (uxBits & 1) == 0; + { + int remain = (int)(timeout.toMilliseconds() - (int)measurer.elapsed_m()); + if (remain <= 0) return false; + EventBits_t uxBits; + uxBits = xEventGroupWaitBits(PRIVATE->nativeHandle, 1, pdTRUE, pdTRUE, remain / portTICK_PERIOD_MS); + isTimeout = (uxBits & 1) == 0; + } #else isTimeout = pthread_cond_timedwait(&PRIVATE->nativeHandle, (pthread_mutex_t *)lk.handle(), &expire_ts) != 0; #endif -- 2.54.0 From 5e4a55cc0ce49520f372de83e678834b3d6ea4d1 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Thu, 6 Aug 2026 10:19:07 +0300 Subject: [PATCH 20/36] fix(PIThread): remove spurious unlock() in _waitForFinish The function called unlock() on thread_mutex without a matching lock(), causing UB on Windows (releasing an unowned critical section) or silently dropping a user-held lock. --- libs/main/thread/pithread.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/main/thread/pithread.cpp b/libs/main/thread/pithread.cpp index 0f9aa389..23070bba 100644 --- a/libs/main/thread/pithread.cpp +++ b/libs/main/thread/pithread.cpp @@ -1080,7 +1080,6 @@ bool PIThread::_waitForFinish(PISystemTime max_tm) { if (!running_) return true; #ifdef WINDOWS if (!isExists(PRIVATE->thread)) { - unlock(); return true; } #endif -- 2.54.0 From 1aa6f1af8150eb69058c67165bfd816b2ebf6b64 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Thu, 6 Aug 2026 10:19:13 +0300 Subject: [PATCH 21/36] fix(PIByteArray): prevent OOB in fromBase64 with non-multiple-of-4 input The buffer was sized as floor(sz/4)*3 but the loop ran ceil(sz/4) times, writing 3 bytes per iteration. For sz%4!=0, this wrote past the buffer end. Also guarded sz<4 to avoid processing trivially short or malformed input. --- libs/main/types/pibytearray.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/main/types/pibytearray.cpp b/libs/main/types/pibytearray.cpp index f3c5359d..7d064d40 100644 --- a/libs/main/types/pibytearray.cpp +++ b/libs/main/types/pibytearray.cpp @@ -198,10 +198,11 @@ PIByteArray PIByteArray::fromBase64(const PIByteArray & base64) { base64HelpStruct hs; PIByteArray ret; const int sz = base64.size_s(); - int ind = -1; + if (sz < 4) return PIByteArray(); + int ind = -1; uchar t[4]; ret.resize(sz / 4 * 3); - for (int i = 0; i < sz; i += 4) { + for (int i = 0; i < sz / 4 * 4; i += 4) { hs.setAscii(base64.data(i)); hs.getBytes(t); ret[++ind] = (t[0]); -- 2.54.0 From c8ad8ecaf4a2f8392454354c3a75b841bca2d44b Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Thu, 6 Aug 2026 10:19:18 +0300 Subject: [PATCH 22/36] fix(PISPI): close fd on ioctl error paths Three ioctl calls after successful open() returned false without closing the file descriptor, leaking one fd per failed open attempt. --- libs/main/io_devices/pispi.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libs/main/io_devices/pispi.cpp b/libs/main/io_devices/pispi.cpp index b432abf4..8871b129 100644 --- a/libs/main/io_devices/pispi.cpp +++ b/libs/main/io_devices/pispi.cpp @@ -106,18 +106,24 @@ bool PISPI::openDevice() { ret = ioctl(PRIVATE->fd, SPI_IOC_WR_MODE, &spi_mode); if (ret == -1) { piCoutObj << "can't set spi write mode"; + ::close(PRIVATE->fd); + PRIVATE->fd = -1; return false; } // piCoutObj << "set bits" << spi_bits; ret = ioctl(PRIVATE->fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits); if (ret == -1) { piCoutObj << "can't set bits per word"; + ::close(PRIVATE->fd); + PRIVATE->fd = -1; return false; } // piCoutObj << "set speed" << spi_speed; ret = ioctl(PRIVATE->fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed); if (ret == -1) { piCoutObj << "can't set max write speed hz"; + ::close(PRIVATE->fd); + PRIVATE->fd = -1; return false; } piCoutObj << "SPI open" << path() << "speed:" << spi_speed / 1000 << "KHz" -- 2.54.0 From 9e280ef9768d2b03f55fe9982f9b187cbb428f43 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Thu, 6 Aug 2026 10:19:23 +0300 Subject: [PATCH 23/36] fix(PISerial): close fd when probe read fails in availableDevicesInfo When the non-blocking probe read returned EIO, the loop continued without closing the file descriptor, leaking one fd per rejected port on each call. --- libs/main/io_devices/piserial.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/main/io_devices/piserial.cpp b/libs/main/io_devices/piserial.cpp index 401d5dfc..0adecd95 100644 --- a/libs/main/io_devices/piserial.cpp +++ b/libs/main/io_devices/piserial.cpp @@ -1292,6 +1292,11 @@ PIVector PISerial::availableDevicesInfo(bool test) { if (!rok) { ret.remove(i); --i; +#ifdef WINDOWS + CloseHandle(hComm); +#else + ::close(fd); +#endif continue; } #ifdef WINDOWS -- 2.54.0 From 1a153eefbc6d43574a0eac60ddc930c187c454f6 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Thu, 6 Aug 2026 10:19:28 +0300 Subject: [PATCH 24/36] fix(PISystemInfo): correct guard for df output parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The guard ml.size_s() < 2 allowed size==2, but ml[2] was then accessed — OOB read on truncated or unusual df output lines. --- libs/main/system/pisysteminfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/main/system/pisysteminfo.cpp b/libs/main/system/pisysteminfo.cpp index dbf5fafe..fd993bd1 100644 --- a/libs/main/system/pisysteminfo.cpp +++ b/libs/main/system/pisysteminfo.cpp @@ -177,7 +177,7 @@ PIVector PISystemInfo::mountInfo(bool ignore_cache) { l_df.pop_front(); for (const auto & s: l_df) { PIStringList ml(s.replacedAll(" ", " ").split(" ")); - if (ml.size_s() < 2) continue; + if (ml.size_s() < 3) continue; if (ml.front() == "none") continue; m.space_all = ml[1].toULLong(); m.space_used = ml[2].toULLong(); -- 2.54.0 From fc2e49b0966568b5abb0766c0b100de163dd1359 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Mon, 10 Aug 2026 09:54:10 +0300 Subject: [PATCH 25/36] =?UTF-8?q?Revert=20"fix:=20PISingleApplication=20TO?= =?UTF-8?q?CTOU=20=E2=80=94=20two=20shm=20reads=20replaced=20with=20single?= =?UTF-8?q?=20atomic=20read"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1b23a258b840ddab6dd80e0e0da451bc68688cee. --- libs/main/application/pisingleapplication.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/libs/main/application/pisingleapplication.cpp b/libs/main/application/pisingleapplication.cpp index 217a1ba7..46e342e3 100644 --- a/libs/main/application/pisingleapplication.cpp +++ b/libs/main/application/pisingleapplication.cpp @@ -128,14 +128,12 @@ void PISingleApplication::run() { int st_[2] = {sacnt, sacnt}; shm->write(st_, 8); // piCoutObj << "write" << sacnt; + int ri[3] = {0, 0, 0}; const int hdr_sz = sizeof(int) * 3; - PIByteArray all(shm->size()); - shm->read(all.data(), all.size()); - int ri[3] = {0, 0, 0}; - memcpy(ri, all.data(), hdr_sz); + shm->read(ri, hdr_sz); if (ri[2] != 0 && ri[0] == ri[1]) { - readed.resize(all.size() - hdr_sz); - memcpy(readed.data(), all.data() + hdr_sz, readed.size()); + readed.resize(shm->size() - hdr_sz); + shm->read(readed.data(), readed.size(), hdr_sz); PIByteArray msg; readed >> msg; if (msg.isNotEmpty()) { -- 2.54.0 From f9a846a3e9ff8463862ecc22b9bed726235ba8ef Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Mon, 10 Aug 2026 10:42:01 +0300 Subject: [PATCH 26/36] Revert "fix(PIFFT_float): correct plan entry size from 4 to 8" This reverts commit 2bb3884aeeb9d3242320864e6fd1e962068a9e5d. --- libs/main/math/pifft.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/main/math/pifft.cpp b/libs/main/math/pifft.cpp index afdbc6f2..129328d0 100644 --- a/libs/main/math/pifft.cpp +++ b/libs/main/math/pifft.cpp @@ -1205,7 +1205,7 @@ void PIFFT_float::ftbase_ftbasegenerateplanrec(int n, ae_int_t stackptr, int debugi) { int k, m, n1, n2, esize, entryoffset; - int ftbase_ftbaseplanentrysize = 8; + int ftbase_ftbaseplanentrysize = 4; int ftbase_ftbasecffttask = 0; int ftbase_fftcooleytukeyplan = 0; int ftbase_fftbluesteinplan = 1; -- 2.54.0 From 1c9cdbba19d0f7197a7bd555b9580e8efd12aed3 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Mon, 10 Aug 2026 15:48:12 +0300 Subject: [PATCH 27/36] fix(picloudserver): use piMin to avoid signed/unsigned UB in Client::readDevice piMini(max_size, buff.size()) implicitly narrows size_t to int, which is UB for large buffers. Replace with explicit piMin matching the correct pattern used in picloudclient.cpp. --- libs/cloud/picloudserver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/cloud/picloudserver.cpp b/libs/cloud/picloudserver.cpp index 709f94ef..a6b3c94d 100644 --- a/libs/cloud/picloudserver.cpp +++ b/libs/cloud/picloudserver.cpp @@ -201,7 +201,7 @@ ssize_t PICloudServer::Client::readDevice(void * read_to, ssize_t max_size) { if (buff.isEmpty()) { sz = 0; } else { - sz = piMini(max_size, buff.size()); + sz = piMin(max_size, buff.size_s()); memcpy(read_to, buff.data(), sz); buff.remove(0, sz); } -- 2.54.0 From b180d91b1eafe9f14e2e6fcaa4c2ca7cb11194dd Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Mon, 10 Aug 2026 15:48:24 +0300 Subject: [PATCH 28/36] fix(pibasetransfer): make shared flags std::atomic to eliminate data race Flags break_, is_sending, is_receiving, is_pause are accessed from both the send thread (send_process) and the read thread (received) without synchronization. Plain bool reads/writes from multiple threads is undefined behavior per C++ standard. Convert to std::atomic with proper initializers. --- libs/main/io_utils/pibasetransfer.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/main/io_utils/pibasetransfer.h b/libs/main/io_utils/pibasetransfer.h index bf46fda3..901fe936 100644 --- a/libs/main/io_utils/pibasetransfer.h +++ b/libs/main/io_utils/pibasetransfer.h @@ -28,6 +28,8 @@ #include "picrc.h" #include "pidiagnostics.h" +#include + //! \~\ingroup IO-Utils //! \~\brief @@ -301,7 +303,7 @@ protected: bool send_process(); uint packet_header_size, part_header_size; - bool break_, is_sending, is_receiving, is_pause; + std::atomic break_{true}, is_sending{false}, is_receiving{false}, is_pause{false}; PIString state_string; llong bytes_all, bytes_cur; -- 2.54.0 From c3bdc5464e2858e37919d9df1f8d98cb3304d3f1 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Mon, 10 Aug 2026 15:48:31 +0300 Subject: [PATCH 29/36] fix(piconnection): prevent UAF in DevicePool::run() during connection deletion The background run() thread copies allConnections() then iterates each connection's diags_ map. If a PIConnection is deleted from another thread between the copy and the iteration, dereferencing the pointer is UAF. Add null check and protect diags_ iteration with __device_pool__ lock. --- libs/main/io_utils/piconnection.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/main/io_utils/piconnection.cpp b/libs/main/io_utils/piconnection.cpp index 8b04f33a..afe2c77b 100644 --- a/libs/main/io_utils/piconnection.cpp +++ b/libs/main/io_utils/piconnection.cpp @@ -1140,10 +1140,13 @@ PIConnection::DevicePool::DeviceData::~DeviceData() { void PIConnection::DevicePool::run() { PIVector conns(PIConnection::allConnections()); for (PIConnection * c: conns) { + if (!c) continue; + __device_pool__->lock(); for (auto d = c->diags_.begin(); d != c->diags_.end(); d++) { if (!d.value()) continue; d.value()->tick(1); } + __device_pool__->unlock(); } } -- 2.54.0 From aa64eb4c7f7e1f2dda4fd19f20e0e586bdb62daf Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Mon, 10 Aug 2026 15:48:40 +0300 Subject: [PATCH 30/36] =?UTF-8?q?fix(pibasetransfer):=20fix=20ABBA=20deadl?= =?UTF-8?q?ock=20=E2=80=94=20enforce=20consistent=20mutex=20lock=20orderin?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit received() acquired mutex_header and mutex_session in different orders depending on packet type: - pt_Data path: mutex_header → mutex_session - pt_Start path: mutex_session → mutex_header send_process() acquires mutex_session independently. When running concurrently with received(), the reversed lock ordering creates a classic ABBA deadlock scenario. Fix: enforce consistent ordering (mutex_header → mutex_session) in all code paths. Restructured pt_Start case and buildSession() to always acquire mutex_header before mutex_session. --- libs/main/io_utils/pibasetransfer.cpp | 127 +++++++++++++------------- 1 file changed, 63 insertions(+), 64 deletions(-) diff --git a/libs/main/io_utils/pibasetransfer.cpp b/libs/main/io_utils/pibasetransfer.cpp index 1829cd45..2f54fed9 100644 --- a/libs/main/io_utils/pibasetransfer.cpp +++ b/libs/main/io_utils/pibasetransfer.cpp @@ -133,6 +133,67 @@ void PIBaseTransfer::received(PIByteArray data) { } mutex_header.unlock(); break; + case pt_Start: + mutex_header.lock(); + if (is_pause && (is_sending || is_receiving)) { + if (header.session_id == h.session_id) { + is_pause = false; + mutex_header.unlock(); + resumed(); + return; + } + } + if (is_sending && header.session_id != h.session_id) { + sendBreak(h.session_id); + mutex_header.unlock(); + return; + } + if (is_receiving) { + if (header.session_id != h.session_id) { + piCoutObj << "restart receive"_tr("PIBaseTransfer"); + mutex_header.unlock(); + finish_receive(false, true); + } else { + header.id = 0; + sendReply(pt_ReplySuccess); + mutex_header.unlock(); + return; + } + } + mutex_header.unlock(); + if (data.size() == sizeof(StartRequest)) { + StartRequest sr; + data >> sr; + mutex_header.lock(); + mutex_session.lock(); + bytes_cur = 0; + state_string = "start request"; + bytes_all = sr.size; + header.session_id = h.session_id; + header.id = 0; + packets_count = 10; + session.clear(); + replies.clear(); + session.resize(sr.packets); + replies.resize(sr.packets + 1); + replies.fill(pt_Unknown); + pm_string.resize(replies.size(), '-'); + diag.reset(); + is_receiving = true; + break_ = false; + mutex_send.lock(); + send_queue = 0; + mutex_send.unlock(); + beginReceive(); + receiveStarted(); + state_string = "receiving"; + replies[0] = pt_ReplySuccess; + pm_string[0] = '#'; + mutex_session.unlock(); + sendReply(pt_ReplySuccess); + mutex_header.unlock(); + } + break; case pt_ReplySuccess: case pt_ReplyInvalid: mutex_header.lock(); @@ -199,68 +260,6 @@ void PIBaseTransfer::received(PIByteArray data) { return; } break; - case pt_Start: - mutex_header.lock(); - if (is_pause && (is_sending || is_receiving)) { - if (header.session_id == h.session_id) { - is_pause = false; - mutex_header.unlock(); - resumed(); - return; - } - } - if (is_sending && header.session_id != h.session_id) { - sendBreak(h.session_id); - mutex_header.unlock(); - return; - } - if (is_receiving) { - if (header.session_id != h.session_id) { - piCoutObj << "restart receive"_tr("PIBaseTransfer"); - mutex_header.unlock(); - finish_receive(false, true); - } else { - header.id = 0; - sendReply(pt_ReplySuccess); - mutex_header.unlock(); - return; - } - } - mutex_header.unlock(); - if (data.size() == sizeof(StartRequest)) { - StartRequest sr; - data >> sr; - mutex_session.lock(); - mutex_header.lock(); - bytes_cur = 0; - state_string = "start request"; - bytes_all = sr.size; - header.session_id = h.session_id; - header.id = 0; - packets_count = 10; - session.clear(); - replies.clear(); - session.resize(sr.packets); - replies.resize(sr.packets + 1); - replies.fill(pt_Unknown); - pm_string.resize(replies.size(), '-'); - diag.reset(); - // piCoutObj << "receiveStarted()"; - is_receiving = true; - break_ = false; - mutex_send.lock(); - send_queue = 0; - mutex_send.unlock(); - beginReceive(); - receiveStarted(); - state_string = "receiving"; - replies[0] = pt_ReplySuccess; - pm_string[0] = '#'; - mutex_session.unlock(); - sendReply(pt_ReplySuccess); - mutex_header.unlock(); - } - break; case pt_Pause: mutex_header.lock(); if (header.session_id == h.session_id) { @@ -424,8 +423,8 @@ int PIBaseTransfer::checkSession() { void PIBaseTransfer::buildSession(PIVector parts) { - mutex_session.lock(); mutex_header.lock(); + mutex_session.lock(); state_string = "calculating parts ... "; session.clear(); header.session_id = randomi(); @@ -478,8 +477,8 @@ void PIBaseTransfer::buildSession(PIVector parts) { } } if (cur_size > min_size) session << lfi; - mutex_header.unlock(); mutex_session.unlock(); + mutex_header.unlock(); } -- 2.54.0 From 16adea05af20424a7de4e518cf67385d5c1415ef Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Mon, 10 Aug 2026 16:26:02 +0300 Subject: [PATCH 31/36] fix(picompress): pass correct source size to uncompress() piDecompress() appended original size as 8-byte ullong at the end of compressed data, but passed zba.size() (including the 8 bytes) to uncompress(). This fed trailing metadata as part of the zlib stream. Fix: subtract sizeof(ullong) from source length. --- libs/compress/picompress.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/compress/picompress.cpp b/libs/compress/picompress.cpp index 90e915e9..b52eaa3d 100644 --- a/libs/compress/picompress.cpp +++ b/libs/compress/picompress.cpp @@ -68,7 +68,7 @@ PIByteArray piDecompress(const PIByteArray & zba) { ba.resize(sz); int ret = 0; ulong s = sz; - ret = uncompress(ba.data(), &s, zba.data(), zba.size()); + ret = uncompress(ba.data(), &s, zba.data(), zba.size() - sizeof(ullong)); if (ret != Z_OK) { piCout << "[PICompress]" << "Error: invalid input or not enought memory"_tr("PICompress"); -- 2.54.0 From 9294cd7b7b410b08585b38a3dcadb218a78eb8b7 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Mon, 10 Aug 2026 16:26:08 +0300 Subject: [PATCH 32/36] fix(picrypt): use crypto_box_* constants for public-key decrypt The decrypt() function for crypto_box (public-key) used crypto_secretbox_NONCEBYTES and crypto_secretbox_MACBYTES (secret-key constants) instead of crypto_box_* constants. These happen to be equal in libsodium but are semantically different APIs. Fix ensures consistency with the encrypt() counterpart. --- libs/crypt/picrypt.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/crypt/picrypt.cpp b/libs/crypt/picrypt.cpp index c31560dc..63b65efe 100644 --- a/libs/crypt/picrypt.cpp +++ b/libs/crypt/picrypt.cpp @@ -317,10 +317,10 @@ PIByteArray PICrypt::decrypt(const PIByteArray & crypt_data, const PIByteArray & return PIByteArray(); } PIByteArray n; - n.resize(crypto_secretbox_NONCEBYTES); + n.resize(crypto_box_NONCEBYTES); const ullong data_size = crypt_data.size() - n.size(); PIByteArray ret; - ret.resize(data_size - crypto_secretbox_MACBYTES); + ret.resize(data_size - crypto_box_MACBYTES); memcpy(n.data(), crypt_data.data(data_size), n.size()); if (crypto_box_open_easy(ret.data(), crypt_data.data(), data_size, n.data(), public_key.data(), secret_key.data()) != 0) { // Bad key -- 2.54.0 From 4d2c26871061541989aef7908767b9a0bbd34001 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Mon, 10 Aug 2026 16:26:13 +0300 Subject: [PATCH 33/36] fix(piluaprogram): add destructor to close Lua state luaL_newstate() allocates a Lua state (~2-4 MB with libraries) in the constructor, but there was no destructor to call lua_close(). Every PILuaProgram instance leaked its entire Lua state. Add ~PILuaProgram() that calls lua_close(PRIVATE->lua_state). --- libs/lua/piluaprogram.cpp | 5 +++++ libs/main/lua/piluaprogram.h | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/libs/lua/piluaprogram.cpp b/libs/lua/piluaprogram.cpp index 2a09518b..3c4bca65 100644 --- a/libs/lua/piluaprogram.cpp +++ b/libs/lua/piluaprogram.cpp @@ -30,6 +30,11 @@ PILuaProgram::PILuaProgram() { } +PILuaProgram::~PILuaProgram() { + lua_close(PRIVATE->lua_state); +} + + bool PILuaProgram::load(const PIString & script) { int ret = luaL_dostring(PRIVATE->lua_state, script.dataUTF8()); if (ret != 0) return false; diff --git a/libs/main/lua/piluaprogram.h b/libs/main/lua/piluaprogram.h index 73a863ed..2e7b95cf 100644 --- a/libs/main/lua/piluaprogram.h +++ b/libs/main/lua/piluaprogram.h @@ -41,6 +41,10 @@ public: //! \~russian Создает объект Lua-программы и открывает стандартные библиотеки Lua. PILuaProgram(); + //! \~english Closes the Lua state and releases all associated resources. + //! \~russian Закрывает Lua state и освобождает все связанные ресурсы. + ~PILuaProgram(); + //! \~english Loads and executes Lua source code from \a script. //! \~russian Загружает и выполняет исходный код Lua из \a script. bool load(const PIString & script); -- 2.54.0 From 9a4384a378baae76f324bb9a3c53dbc06f44aa37 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Mon, 10 Aug 2026 16:26:18 +0300 Subject: [PATCH 34/36] fix(piopencl): release context on command queue creation failure If clCreateContext succeeded but clCreateCommandQueue failed, the context handle was returned without being released, leaking the OpenCL context and all associated GPU resources. --- libs/opencl/piopencl.cpp | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/libs/opencl/piopencl.cpp b/libs/opencl/piopencl.cpp index 20087d09..f9e602ed 100644 --- a/libs/opencl/piopencl.cpp +++ b/libs/opencl/piopencl.cpp @@ -213,6 +213,7 @@ PIOpenCL::Context * PIOpenCL::Context::create(const PIOpenCL::DeviceList & dl) { if (ret != 0) { piCout << "[PIOpenCL::Context]" << "clCreateCommandQueue error" << ret; + clReleaseContext(con); return 0; } piCout << "create done for" << dl[0].name; @@ -461,7 +462,7 @@ void PIOpenCL::Buffer::copyFrom(void * data, int elements_count, int elements_fr cl_int ret = clEnqueueWriteBuffer(context_->PRIVATEWB->queue, PRIVATE->buffer, CL_TRUE, - elements_from_offset * def.size(), + elements_from_offset * def.size(), elements_count * def.size(), data, 0, @@ -484,22 +485,22 @@ void PIOpenCL::Buffer::copy(Buffer * buffer_from, int elements_count, int elements_from_offset, int elements_to_offset) { - if (!buffer_from || !buffer_to) return; - if (!buffer_from->PRIVATEWB->buffer || !buffer_to->PRIVATEWB->buffer) return; - if (elements_count < 0) elements_count = piMini(buffer_from->elements, buffer_to->elements); - cl_int ret = clEnqueueCopyBuffer(buffer_from->context_->PRIVATEWB->queue, - buffer_from->PRIVATEWB->buffer, - buffer_to->PRIVATEWB->buffer, - elements_from_offset * buffer_from->def.size(), - elements_to_offset * buffer_to->def.size(), - elements_count * buffer_from->def.size(), - 0, - nullptr, - nullptr); - if (ret != 0) { - piCout << "[PIOpenCL::Buffer]" - << "clEnqueueCopyBuffer error" << ret; - } + if (!buffer_from || !buffer_to) return; + if (!buffer_from->PRIVATEWB->buffer || !buffer_to->PRIVATEWB->buffer) return; + if (elements_count < 0) elements_count = piMini(buffer_from->elements, buffer_to->elements); + cl_int ret = clEnqueueCopyBuffer(buffer_from->context_->PRIVATEWB->queue, + buffer_from->PRIVATEWB->buffer, + buffer_to->PRIVATEWB->buffer, + elements_from_offset * buffer_from->def.size(), + elements_to_offset * buffer_to->def.size(), + elements_count * buffer_from->def.size(), + 0, + nullptr, + nullptr); + if (ret != 0) { + piCout << "[PIOpenCL::Buffer]" + << "clEnqueueCopyBuffer error" << ret; + } } -- 2.54.0 From 69673ff7c6a1d38c52b0cbbd4609dde7da4338ba Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Mon, 10 Aug 2026 16:26:23 +0300 Subject: [PATCH 35/36] fix(curl_thread_pool): clean up clients_in_proc in destroy() destroy() aborted and deleted clients in the queue but did not delete clients that were currently being processed (clients_in_proc). If a thread was terminated while processing a client, that client object and its CURL handle were leaked. --- libs/http_client/curl_thread_pool_p.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/http_client/curl_thread_pool_p.cpp b/libs/http_client/curl_thread_pool_p.cpp index d9b1bfc7..dc562508 100644 --- a/libs/http_client/curl_thread_pool_p.cpp +++ b/libs/http_client/curl_thread_pool_p.cpp @@ -92,5 +92,10 @@ void CurlThreadPool::destroy() { for (auto c: *cr) delete c; } + { + auto cr = clients_in_proc.getRef(); + for (auto c: *cr) + delete c; + } // piCout << "~CurlThreadPool ok"; } -- 2.54.0 From ace5960e705dab91c384aaf209c7c3f4515e5d31 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Mon, 10 Aug 2026 16:26:30 +0300 Subject: [PATCH 36/36] fix(piusb): fix 4 bugs in USB read/write/enumeration/close 1. Missing break in readDevice() Bulk case: Bulk read result was overwritten by interrupt read, silently corrupting all bulk reads. 2. Wrong variable in writeDevice(): switch checked ep_read instead of ep_write; Interrupt case used ep_read.address for writes. 3. Interface mismatch in closeDevice(): always released interface 0 (intefrace_) instead of the actually claimed interface (interface_claimed), leaking non-zero interface claims. 4. Wrong index in interface enumeration: used outer loop variable c (configuration index) instead of i (interface index), reading wrong interface descriptors or causing OOB access. --- libs/usb/piusb.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/libs/usb/piusb.cpp b/libs/usb/piusb.cpp index 95687efa..c45070ee 100644 --- a/libs/usb/piusb.cpp +++ b/libs/usb/piusb.cpp @@ -239,7 +239,7 @@ bool PIUSB::openDevice() { for (int i = 0; i < dc.bNumInterfaces; ++i) { conf.interfaces << Interface(); Interface & infc(conf.interfaces.back()); - struct usb_interface_descriptor * di(dc.interface[c].altsetting); + struct usb_interface_descriptor * di(dc.interface[i].altsetting); infc.index = i; infc.value_to_select = di->bAlternateSetting; infc.class_code = di->bInterfaceClass; @@ -287,7 +287,7 @@ bool PIUSB::openDevice() { } // else piCoutObj << "ok"; // usb_reset(hdev); - // usb_set_configuration(hdev, 1); + // usb_set_configuration(hdev, 1); // usb_set_altinterface(hdev, 0); # ifndef WINDOWS @@ -320,7 +320,7 @@ bool PIUSB::openDevice() { bool PIUSB::closeDevice() { #ifdef PIP_USB if (hdev == 0) return true; - usb_release_interface(hdev, intefrace_); + if (interface_claimed >= 0) usb_release_interface(hdev, interface_claimed); usb_close(hdev); hdev = 0; interface_claimed = -1; @@ -340,6 +340,7 @@ ssize_t PIUSB::readDevice(void * read_to, ssize_t max_size) { case Endpoint::Bulk: /*piCoutObj << "bulk read" << max_size;*/ ret = usb_bulk_read(hdev, ep_read.address, (char *)read_to, max_size, timeout_r); + break; case Endpoint::Interrupt: ret = usb_interrupt_read(hdev, ep_read.address, (char *)read_to, max_size, timeout_r); default: break; } @@ -354,11 +355,11 @@ ssize_t PIUSB::readDevice(void * read_to, ssize_t max_size) { ssize_t PIUSB::writeDevice(const void * data, ssize_t max_size) { #ifdef PIP_USB if (isClosed() || ep_write.isNull()) return -1; - switch (ep_read.transfer_type) { + switch (ep_write.transfer_type) { case Endpoint::Bulk: /*piCoutObj << "bulk write" << max_size;*/ return usb_bulk_write(hdev, ep_write.address, (char *)const_cast(data), max_size, timeout_w); - case Endpoint::Interrupt: return usb_interrupt_write(hdev, ep_read.address, (char *)data, max_size, timeout_w); + case Endpoint::Interrupt: return usb_interrupt_write(hdev, ep_write.address, (char *)data, max_size, timeout_w); default: break; } return -1; @@ -442,7 +443,7 @@ PICout operator<<(PICout s, const PIUSB::Endpoint & v) { PIString PIUSB::constructFullPathDevice() const { PIString ret; ret = PIString::fromNumber(vendorID(), 16).toLowerCase() + ":" + PIString::fromNumber(productID(), 16).toLowerCase() + ":" + - deviceNumber() + ":" + endpointRead().address + ":" + endpointWrite().address; + deviceNumber() + ":" + endpointRead().address + ":" + endpointWrite().address; return ret; } -- 2.54.0