diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 5781418..87ad8e4 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 31 +#define MONTAUK_BUILD_NUMBER 36 diff --git a/kernel/src/Drivers/USB/Bluetooth/A2dp.cpp b/kernel/src/Drivers/USB/Bluetooth/A2dp.cpp index e736f00..6ef3464 100644 --- a/kernel/src/Drivers/USB/Bluetooth/A2dp.cpp +++ b/kernel/src/Drivers/USB/Bluetooth/A2dp.cpp @@ -971,9 +971,64 @@ namespace Drivers::USB::Bluetooth::A2dp { return true; // channel configured + query sent; proceed to AVDTP } + // Wait for the ACL link to be ENCRYPTED before dialing any L2CAP channel. + // Post-SSP, the sink is REQUIRED to ignore unencrypted L2CAP -- dialing + // SDP/AVDTP before Encryption Change lands is a race we historically lost + // often (connRsp stays 0xFFFF on every attempt; "toggle the headphones" + // sometimes won the race by accident). Pump ProcessPendingCommands here: + // the SET_CONN_ENCRYPT that MAKES encryption happen sits in the pending + // queue and otherwise only goes out when the idle loop gets around to it. + static bool WaitEncrypted(uint32_t timeoutMs) { + uint64_t start = Timekeeping::GetMilliseconds(); + while (Timekeeping::GetMilliseconds() - start < timeoutMs) { + Xhci::PollEvents(); + Hci::DrainEvents(); + Hci::ProcessPendingCommands(); + auto* c = Hci::GetActiveConnection(); + if (c && c->Encrypted) return true; + for (int j = 0; j < 100; j++) asm volatile("" ::: "memory"); + } + return false; + } + bool StartSource(uint32_t timeoutMs) { constexpr int kMaxAttempts = 4; + if (WaitEncrypted(3000)) { + KernelLogStream(OK, "BT-A2DP") << "Link encrypted; dialing channels"; + } else { + // Some sinks may not encrypt (legacy); proceed as before, but say so. + KernelLogStream(WARNING, "BT-A2DP") + << "Link not encrypted after 3s; dialing anyway"; + } + + // Give the sink first move. On RECONNECTION (bonded device) sinks -- + // Bose QC included -- commonly dial the AVDTP channels THEMSELVES + // right after encryption, and ignore the source's own CONN_REQs while + // their setup is in flight (observed: connRsp stays 0xFFFF on every + // dialed attempt, zero inbound traffic... because we never stopped + // transmitting long enough to receive). Listen briefly before + // dialing; fresh pairings are source-driven and just spend the wait. + g_sigCid = 0; + g_mediaCid = 0; + g_state = State::Idle; + g_txLabel = 1; + g_avdtpResponseReady = false; + { + uint64_t lStart = Timekeeping::GetMilliseconds(); + while (Timekeeping::GetMilliseconds() - lStart < 2500) { + Xhci::PollEvents(); + Hci::DrainEvents(); + Hci::ProcessPendingCommands(); + if (g_sigCid != 0) break; + for (int j = 0; j < 100; j++) asm volatile("" ::: "memory"); + } + } + if (g_sigCid != 0) { + KernelLogStream(OK, "BT-A2DP") << "Sink opened AVDTP to us (cid=" + << base::hex << (uint64_t)g_sigCid << base::dec << ")"; + } + // Connection phase, retried. A sink commonly ignores the very first // L2CAP CONN_REQ that lands right after Encryption Change (connRsp stays // 0xFFFF, the headset never answers). Re-dial up to kMaxAttempts. We @@ -983,8 +1038,10 @@ namespace Drivers::USB::Bluetooth::A2dp { // (connRsp!=0xFFFF) we do NOT loop -- that's the config phase, surfaced // by its own logs. We never reset the CID allocator, so each retry uses // a fresh local CID and a late response for an old one cannot cross-wire. - for (int attempt = 0; attempt < kMaxAttempts; attempt++) { - g_sigCid = 0; + // Dial only while no signaling channel exists in either direction; + // an inbound one (from the listen phase above, or landing between + // retries) short-circuits straight to negotiation. + for (int attempt = 0; attempt < kMaxAttempts && g_sigCid == 0; attempt++) { g_mediaCid = 0; g_state = State::Idle; g_txLabel = 1; diff --git a/kernel/src/Drivers/USB/Bluetooth/Bluetooth.cpp b/kernel/src/Drivers/USB/Bluetooth/Bluetooth.cpp index 28c6e14..41de253 100644 --- a/kernel/src/Drivers/USB/Bluetooth/Bluetooth.cpp +++ b/kernel/src/Drivers/USB/Bluetooth/Bluetooth.cpp @@ -334,6 +334,18 @@ namespace Drivers::USB::Bluetooth { // Enable Simple Secure Pairing Hci::WriteSSPMode(1); + // Enable Secure Connections host support (P-256 / AES-CCM). Link + // keys are procedure-bound: a bond minted over Secure Connections + // (BlueZ always negotiates SC -> key Type=7) CANNOT authenticate a + // legacy link -- the controller must fail with status 5. Without + // this, a key shared with a Linux install (dual boot, see + // scripts/import-bluez-bond.sh) is cryptographically fine yet + // unusable, and our own pairings mint legacy P-192 keys that Linux + // then silently replaces. Must follow Write SSP Mode. + uint8_t scOn = 0x01; + Hci::SendCommand(Hci::OP_WRITE_SC_HOST_SUPPORT, &scOn, 1); + Hci::WaitCommandComplete(Hci::OP_WRITE_SC_HOST_SUPPORT); + // Allow role switch + sniff on new connections. The controller default // link policy is 0x0000 (deny both). A multipoint headset (Bose QC // Ultra) that also holds a link to a phone requests a role switch to diff --git a/kernel/src/Drivers/USB/Bluetooth/Hci.cpp b/kernel/src/Drivers/USB/Bluetooth/Hci.cpp index 5299225..f118f01 100644 --- a/kernel/src/Drivers/USB/Bluetooth/Hci.cpp +++ b/kernel/src/Drivers/USB/Bluetooth/Hci.cpp @@ -184,7 +184,12 @@ namespace Drivers::USB::Bluetooth::Hci { // the DHKey check fail (Simple Pairing Complete status 0x05). Instead the // handlers ENQUEUE the reply here; ProcessPendingCommands() sends it later // from top-level (process) context with a real, confirmed transfer. - struct PendingHciCmd { uint16_t opcode; uint8_t len; uint8_t params[16]; }; + // params must hold the LARGEST queued reply: Link Key Request Reply is + // 22 bytes (BD_ADDR 6 + key 16). This was 16, which silently TRUNCATED + // the link key reply to 10 key bytes -- every stored-key reconnection + // failed authentication (status 5) since the dawn of the driver; only + // fresh pairings (whose replies fit) ever worked. + struct PendingHciCmd { uint16_t opcode; uint8_t len; uint8_t params[22]; }; static PendingHciCmd g_pending[16] = {}; static volatile uint8_t g_pendingHead = 0; static volatile uint8_t g_pendingTail = 0; @@ -192,7 +197,7 @@ namespace Drivers::USB::Bluetooth::Hci { static void EnqueueHciCmd(uint16_t opcode, const uint8_t* params, uint8_t len) { uint8_t next = (uint8_t)((g_pendingHead + 1) & 15); if (next == g_pendingTail) return; // full -> drop (should never happen) - if (len > 16) len = 16; + if (len > sizeof(g_pending[0].params)) len = sizeof(g_pending[0].params); g_pending[g_pendingHead].opcode = opcode; g_pending[g_pendingHead].len = len; for (uint8_t i = 0; i < len; i++) g_pending[g_pendingHead].params[i] = params[i]; @@ -1271,13 +1276,23 @@ namespace Drivers::USB::Bluetooth::Hci { while (g_pendingTail != g_pendingHead) { PendingHciCmd c = g_pending[g_pendingTail]; g_pendingTail = (uint8_t)((g_pendingTail + 1) & 15); - SendCommand(c.opcode, c.params, c.len); + bool sent = SendCommand(c.opcode, c.params, c.len); // Set Connection Encryption returns Command Status (not Complete), // so wait on that instead of burning a 1s timeout that delays A2DP. if (c.opcode == OP_SET_CONN_ENCRYPT) { WaitCommandStatus(c.opcode, 1000); } else { - WaitCommandComplete(c.opcode, nullptr, 0, 1000); + // Log delivery + the controller's verdict: security replies + // (link key / IO cap / confirm) failing HERE is invisible at + // the HCI level otherwise, and looks exactly like the remote + // rejecting authentication. + uint8_t st[1] = { 0xEE }; + bool ok = WaitCommandComplete(c.opcode, st, 1, 1000); + KernelLogStream(INFO, "BT-HCI") << "queued cmd " << base::hex + << (uint64_t)c.opcode << base::dec << " len=" << (uint64_t)c.len + << " sent=" << (uint64_t)(sent ? 1 : 0) + << " cc=" << (uint64_t)(ok ? 1 : 0) + << " status=" << base::hex << (uint64_t)st[0] << base::dec; } } s_active.store(false, std::memory_order_release); diff --git a/kernel/src/Drivers/USB/Bluetooth/Hci.hpp b/kernel/src/Drivers/USB/Bluetooth/Hci.hpp index 6b41bfc..4803abd 100644 --- a/kernel/src/Drivers/USB/Bluetooth/Hci.hpp +++ b/kernel/src/Drivers/USB/Bluetooth/Hci.hpp @@ -49,6 +49,7 @@ namespace Drivers::USB::Bluetooth::Hci { constexpr uint16_t OP_WRITE_SCAN_ENABLE = 0x0C1A; constexpr uint16_t OP_WRITE_CLASS_OF_DEVICE = 0x0C24; constexpr uint16_t OP_WRITE_SSP_MODE = 0x0C56; + constexpr uint16_t OP_WRITE_SC_HOST_SUPPORT = 0x0C7A; constexpr uint16_t OP_WRITE_INQUIRY_MODE = 0x0C45; constexpr uint16_t OP_WRITE_PAGE_TIMEOUT = 0x0C18; constexpr uint16_t OP_WRITE_AUTH_ENABLE = 0x0C20; diff --git a/scripts/import-bluez-bond.sh b/scripts/import-bluez-bond.sh index 0506956..8f81017 100755 --- a/scripts/import-bluez-bond.sh +++ b/scripts/import-bluez-bond.sh @@ -83,14 +83,13 @@ with open(path, "wb") as f: print(f"wrote {path} ({len(blob)} bytes, 1 bond)") EOF -# Pin the configured MAC to the factory address (truthful display; the 0xFC31 -# override is cosmetic on this hardware anyway). +# Remove any BD_ADDR override: the controller must run its factory address +# with NO 0xFC31 write. Verified on the AX211: writing 0xFC31 -- even with +# the factory address itself -- desyncs the address the firmware uses in SSP +# crypto from the one on air, and every pairing fails with status 5. CONF="$TARGET/config/bluetooth.toml" -mkdir -p "$TARGET/config" if [ -f "$CONF" ] && grep -q '^ *mac *=' "$CONF"; then - sed -i "s/^ *mac *=.*/mac = \"$ADAPTER\"/" "$CONF" -else - printf 'mac = "%s"\n' "$ADAPTER" >> "$CONF" + sed -i '/^ *mac *=/d' "$CONF" + echo "removed mac override from $CONF (0xFC31 breaks SSP on AX211)" fi -echo "set $CONF mac = $ADAPTER" echo "done — boot MontaukOS; the device should reconnect without re-pairing."