From 9fed57c10c10fe7cb5e7bf7b89c4a9e12cf6b072 Mon Sep 17 00:00:00 2001 From: Hyperbots-prod-server Date: Sat, 25 Jul 2026 13:08:58 +0300 Subject: [PATCH 1/2] daemon: fix undefined 32-bit shift that crashes native arm64 builds FilesCache::setCardCPZ and MPDevice::getUInt64EncryptionKeyOld derive a SimpleCrypt key by shifting a 32-bit value by (i*8) for i in 0..7. A shift >= 32 is undefined behaviour: x86 silently wrapped the count, but for arm64 clang compiles the provably-undefined iterations into a brk trap, so the daemon crashes (EXC_BREAKPOINT) the moment a card CPZ is read - i.e. on every device connect/unlock on Apple Silicon. Verified on hardware (Mooltipass Mini BLE, macOS 26.5.2, Mac14,6): deterministic SIGTRAP at FilesCache::setCardCPZ+1376 in every crash report, and the shipped binary contains brk #1 (0xd4200020) at exactly that offset. Mask the shift count ((i*8) & 31) to make the historical x86 wrapping explicit and well-defined: derived keys stay byte-identical with existing Intel installs, so cached/exported data continues to decrypt. The V2 path (getUInt64EncryptionKey) already shifts in 64-bit and is untouched. --- src/FilesCache.cpp | 6 +++++- src/MPDevice.cpp | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/FilesCache.cpp b/src/FilesCache.cpp index 7fa2f5fd..151393ea 100644 --- a/src/FilesCache.cpp +++ b/src/FilesCache.cpp @@ -156,8 +156,12 @@ bool FilesCache::setCardCPZ(QByteArray cardCPZ) m_filePath = dataDir.absoluteFilePath(fileName); qint64 m_key = 0; + // The shift must stay 32-bit and wrap at 32 ((i*8) & 31) to derive the + // same key historical x86 builds produced: shifting a 32-bit value by + // >= 32 is undefined behaviour that x86 silently wrapped, while on + // arm64 the compiler turns it into a trap (crash on Apple Silicon). for (int i = 0;i < std::min(8, static_cast(cardCPZ.size()));i++) - m_key += (static_cast(cardCPZ[i]) & 0xFF) << (i * 8); + m_key += (static_cast(cardCPZ[i]) & 0xFF) << ((i * 8) & 31); m_simpleCrypt.setKey(m_key); m_simpleCrypt.setIntegrityProtectionMode(SimpleCrypt::ProtectionHash); diff --git a/src/MPDevice.cpp b/src/MPDevice.cpp index 3e5e1c55..c1c88ddf 100755 --- a/src/MPDevice.cpp +++ b/src/MPDevice.cpp @@ -5414,9 +5414,13 @@ quint64 MPDevice::getUInt64EncryptionKey() quint64 MPDevice::getUInt64EncryptionKeyOld() { qint64 key = 0; + // This legacy key derivation must reproduce what historical x86 builds + // computed: the 32-bit shift by >= 32 was undefined behaviour that x86 + // silently wrapped ((i*8) & 31), while on arm64 the compiler turns the + // unmasked form into a trap (crash on Apple Silicon). for (int i = 0; i < std::min(8, static_cast(m_cardCPZ.size())) ; i++) { - key += (static_cast(m_cardCPZ[i]) & 0xFF) << (i*8); + key += (static_cast(m_cardCPZ[i]) & 0xFF) << ((i*8) & 31); } return key; From c4b93daf7fea635875b44536af69ebe9af909462 Mon Sep 17 00:00:00 2001 From: Hyperbots-prod-server Date: Sat, 25 Jul 2026 13:08:59 +0300 Subject: [PATCH 2/2] daemon: ignore errored HID input reports on macOS IOKit invokes the input-report callback with a non-success result (and meaningless buffer/length) when the device drops mid-read, such as during BLE device interface re-enumeration. The callback ignored the result code and parsed whatever was in the buffer. Drop such reports instead of feeding them to the message protocol. --- src/MPDevice_mac.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/MPDevice_mac.cpp b/src/MPDevice_mac.cpp index c075da95..a9e0792a 100644 --- a/src/MPDevice_mac.cpp +++ b/src/MPDevice_mac.cpp @@ -29,12 +29,22 @@ void _read_report_callback(void *context, uint8_t *report, CFIndex report_length) { - Q_UNUSED(result); Q_UNUSED(sender); Q_UNUSED(report_type); Q_UNUSED(report_id); MPDevice_mac *dev = reinterpret_cast(context); + + // IOKit invokes this callback with an error result (and meaningless + // buffer/length) when the device drops mid-read, e.g. while the BLE + // device re-enumerates its interfaces. Never feed that to the parser. + if (result != kIOReturnSuccess || !report || report_length <= 0) + { + qWarning() << "Ignoring errored HID input report, result:" << result + << "length:" << static_cast(report_length); + return; + } + QByteArray data((const char *)report, report_length); if (dev->isBT()) {