feat: allow MAC spoofing in Bluetooth stack, show paired devices in Bluetooth app
This commit is contained in:
@@ -56,6 +56,40 @@ namespace Montauk {
|
||||
return (int64_t)Drivers::USB::Bluetooth::Disconnect(bdAddr);
|
||||
}
|
||||
|
||||
static int64_t Sys_BtSetAddr(const uint8_t* bdAddr) {
|
||||
if (!bdAddr) return -1;
|
||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return -1;
|
||||
|
||||
// bdAddr is a 6-byte buffer, addr[0] = least-significant octet.
|
||||
return Drivers::USB::Bluetooth::SetAddress(bdAddr) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int64_t Sys_BtBonds(BtBondInfo* buf, int maxCount) {
|
||||
if (!buf || maxCount <= 0) return 0;
|
||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return 0;
|
||||
|
||||
Drivers::USB::Bluetooth::Hci::BondInfo tmpBuf[8];
|
||||
int kMax = maxCount;
|
||||
if (kMax > 8) kMax = 8;
|
||||
|
||||
int count = Drivers::USB::Bluetooth::ListBonded(tmpBuf, kMax);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
memcpy(buf[i].bdAddr, tmpBuf[i].Addr, 6);
|
||||
buf[i]._pad[0] = 0;
|
||||
buf[i]._pad[1] = 0;
|
||||
}
|
||||
|
||||
return (int64_t)count;
|
||||
}
|
||||
|
||||
static int64_t Sys_BtForget(const uint8_t* bdAddr) {
|
||||
if (!bdAddr) return -1;
|
||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return -1;
|
||||
|
||||
return (int64_t)Drivers::USB::Bluetooth::ForgetDevice(bdAddr);
|
||||
}
|
||||
|
||||
static int64_t Sys_BtList(BtDevInfo* buf, int maxCount) {
|
||||
if (!buf || maxCount <= 0) return 0;
|
||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return 0;
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 22
|
||||
#define MONTAUK_BUILD_NUMBER 24
|
||||
|
||||
@@ -377,6 +377,16 @@ namespace Montauk {
|
||||
case SYS_BTDISCONNECT:
|
||||
if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
|
||||
return Sys_BtDisconnect((const uint8_t*)frame->arg1);
|
||||
case SYS_BTSETADDR:
|
||||
if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
|
||||
return Sys_BtSetAddr((const uint8_t*)frame->arg1);
|
||||
case SYS_BTBONDS:
|
||||
if ((int64_t)frame->arg2 < 0) return -1;
|
||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtBondInfo), true)) return -1;
|
||||
return Sys_BtBonds((BtBondInfo*)frame->arg1, (int)frame->arg2);
|
||||
case SYS_BTFORGET:
|
||||
if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
|
||||
return Sys_BtForget((const uint8_t*)frame->arg1);
|
||||
case SYS_BTLIST:
|
||||
if ((int64_t)frame->arg2 < 0) return -1;
|
||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtDevInfo), true)) return -1;
|
||||
|
||||
@@ -258,6 +258,13 @@ namespace Montauk {
|
||||
/* Filesystem.hpp -- paginated directory read (path, names, max, startIndex) */
|
||||
static constexpr uint64_t SYS_READDIR_AT = 136;
|
||||
|
||||
/* Bluetooth.hpp -- set adapter BD_ADDR (6-byte buffer, addr[0] = LSB) */
|
||||
static constexpr uint64_t SYS_BTSETADDR = 137;
|
||||
|
||||
/* Bluetooth.hpp -- list bonded (paired) devices / forget a bond */
|
||||
static constexpr uint64_t SYS_BTBONDS = 138;
|
||||
static constexpr uint64_t SYS_BTFORGET = 139;
|
||||
|
||||
// Graceful power-off request actions (SYS_POWER_REQUEST). The desktop posts
|
||||
// a pending action and exits; login.elf reads it, runs the shutdown stages,
|
||||
// then issues the matching SYS_SHUTDOWN / SYS_RESET.
|
||||
@@ -508,6 +515,12 @@ namespace Montauk {
|
||||
char name[64];
|
||||
};
|
||||
|
||||
// Bluetooth bonded (paired) device (returned by SYS_BTBONDS)
|
||||
struct BtBondInfo {
|
||||
uint8_t bdAddr[6];
|
||||
uint8_t _pad[2];
|
||||
};
|
||||
|
||||
struct ThermalInfo {
|
||||
char name[32]; // short zone name (e.g. "THRM", "TZ00")
|
||||
int32_t temperature; // tenths of degrees Celsius, or -1 if unavailable
|
||||
|
||||
@@ -37,6 +37,94 @@ namespace Drivers::USB::Bluetooth {
|
||||
// Forward declaration: firmware-dependent HCI bring-up, run once VFS is up.
|
||||
static void CompleteInit();
|
||||
|
||||
// Path to the Bluetooth config (BD_ADDR override). Written by the
|
||||
// Bluetooth desktop app; read here on boot. See ApplyConfiguredAddress().
|
||||
static constexpr const char* BT_CONFIG_PATH = "0:/config/bluetooth.toml";
|
||||
|
||||
// =========================================================================
|
||||
// bluetooth.toml BD_ADDR override
|
||||
// =========================================================================
|
||||
|
||||
static bool HexNibble(char c, uint8_t& out) {
|
||||
if (c >= '0' && c <= '9') { out = (uint8_t)(c - '0'); return true; }
|
||||
if (c >= 'a' && c <= 'f') { out = (uint8_t)(c - 'a' + 10); return true; }
|
||||
if (c >= 'A' && c <= 'F') { out = (uint8_t)(c - 'A' + 10); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse exactly six ':'/'-'-separated hex octets from a string fragment.
|
||||
// Fills out[0..5] in written order (out[0] is the first printed octet,
|
||||
// matching the desktop app's format_addr); returns false on any deviation.
|
||||
static bool ParseMacStr(const char* s, int len, uint8_t out[6]) {
|
||||
int byteIdx = 0, i = 0;
|
||||
while (byteIdx < 6) {
|
||||
while (i < len && (s[i] == ':' || s[i] == '-' || s[i] == ' ')) i++;
|
||||
uint8_t hi = 0, lo = 0;
|
||||
if (i + 1 >= len || !HexNibble(s[i], hi) || !HexNibble(s[i + 1], lo))
|
||||
return false;
|
||||
out[byteIdx++] = (uint8_t)((hi << 4) | lo);
|
||||
i += 2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read the optional `mac = "XX:XX:XX:XX:XX:XX"` key from bluetooth.toml.
|
||||
// Minimal line scanner (the kernel has no TOML parser, and the file holds a
|
||||
// single value); ignores section headers/comments. Returns false when the
|
||||
// file is absent or the key is missing/malformed.
|
||||
static bool ReadConfiguredMac(uint8_t out[6]) {
|
||||
Fs::Vfs::BackendFile f;
|
||||
if (Fs::Vfs::OpenBackendFile(BT_CONFIG_PATH, f) < 0) return false;
|
||||
uint64_t size = Fs::Vfs::GetBackendFileSize(f);
|
||||
if (size == 0) { Fs::Vfs::CloseBackendFile(f); return false; }
|
||||
|
||||
char buf[512];
|
||||
uint64_t n = size < sizeof(buf) - 1 ? size : sizeof(buf) - 1;
|
||||
Fs::Vfs::ReadBackendFile(f, (uint8_t*)buf, 0, n);
|
||||
Fs::Vfs::CloseBackendFile(f);
|
||||
buf[n] = '\0';
|
||||
|
||||
const char* p = buf;
|
||||
while (*p) {
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
// Match a bare "mac" key (next char must end the identifier).
|
||||
if (p[0] == 'm' && p[1] == 'a' && p[2] == 'c' &&
|
||||
(p[3] == ' ' || p[3] == '\t' || p[3] == '=')) {
|
||||
const char* q = p + 3;
|
||||
while (*q == ' ' || *q == '\t') q++;
|
||||
if (*q == '=') {
|
||||
const char* quote = q + 1;
|
||||
while (*quote && *quote != '"' && *quote != '\n') quote++;
|
||||
if (*quote == '"') {
|
||||
const char* end = quote + 1;
|
||||
while (*end && *end != '"' && *end != '\n') end++;
|
||||
if (*end == '"' &&
|
||||
ParseMacStr(quote + 1, (int)(end - quote - 1), out))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (*p && *p != '\n') p++;
|
||||
if (*p == '\n') p++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply a configured BD_ADDR override (if any) to the freshly-reset
|
||||
// controller. Called from CompleteInit BEFORE ReadBdAddr so the rest of
|
||||
// bring-up uses the overridden address; no HCI Reset must follow (the Intel
|
||||
// 0xFC31 override is volatile and a reset reverts to the factory address).
|
||||
static void ApplyConfiguredAddress() {
|
||||
uint8_t mac[6];
|
||||
if (!ReadConfiguredMac(mac)) return; // no override configured
|
||||
if (Hci::SetBdAddr(mac)) {
|
||||
KernelLogStream(OK, "BT") << "Applied BD_ADDR override from bluetooth.toml";
|
||||
} else {
|
||||
KernelLogStream(WARNING, "BT")
|
||||
<< "BD_ADDR override from bluetooth.toml rejected by controller";
|
||||
}
|
||||
}
|
||||
|
||||
// Intel Bluetooth device IDs
|
||||
static bool IsIntelBt(uint16_t vid, uint16_t pid) {
|
||||
if (vid != 0x8087) return false;
|
||||
@@ -190,6 +278,11 @@ namespace Drivers::USB::Bluetooth {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply a persisted BD_ADDR override (0:/config/bluetooth.toml) now,
|
||||
// after the last reset and before the address is read back, so the
|
||||
// whole bring-up below uses the overridden address.
|
||||
ApplyConfiguredAddress();
|
||||
|
||||
// Read BD_ADDR
|
||||
if (Hci::ReadBdAddr(g_bdAddr)) {
|
||||
KernelLogStream(OK, "BT") << "BD_ADDR: "
|
||||
@@ -307,6 +400,65 @@ namespace Drivers::USB::Bluetooth {
|
||||
return g_bdAddr;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// SetAddress — live BD_ADDR change
|
||||
// =========================================================================
|
||||
|
||||
bool SetAddress(const uint8_t* addr) {
|
||||
if (!g_initialized || !addr) return false;
|
||||
|
||||
// Reject the obviously-invalid addresses (all-zero, broadcast).
|
||||
bool allZero = true, allOnes = true;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (addr[i] != 0x00) allZero = false;
|
||||
if (addr[i] != 0xFF) allOnes = false;
|
||||
}
|
||||
if (allZero || allOnes) return false;
|
||||
|
||||
// Drop any active link first: its pairing/encryption was negotiated
|
||||
// against the old address, so it cannot survive the change.
|
||||
bool droppedLink = false;
|
||||
for (int i = 0; i < Hci::MAX_CONNECTIONS; i++) {
|
||||
auto* conn = Hci::GetConnectionByIndex(i);
|
||||
if (conn && conn->Active) {
|
||||
Hci::Disconnect(conn->Handle, 0x13); // Remote User Terminated
|
||||
droppedLink = true;
|
||||
}
|
||||
}
|
||||
// Let the disconnection(s) complete before reprogramming the address.
|
||||
if (droppedLink) {
|
||||
uint64_t t0 = Timekeeping::GetMilliseconds();
|
||||
while (Timekeeping::GetMilliseconds() - t0 < 300) {
|
||||
Xhci::PollEvents();
|
||||
Hci::DrainEvents();
|
||||
for (int k = 0; k < 200; k++) asm volatile("pause" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
// Program the new address. Do NOT issue an HCI Reset afterwards: the
|
||||
// Intel 0xFC31 override is volatile and a reset reverts it.
|
||||
if (!Hci::SetBdAddr(addr)) return false;
|
||||
|
||||
// Re-read to confirm and refresh the cache; fall back to the requested
|
||||
// bytes if the read fails.
|
||||
uint8_t readback[6] = {};
|
||||
if (Hci::ReadBdAddr(readback)) {
|
||||
memcpy(g_bdAddr, readback, 6);
|
||||
} else {
|
||||
memcpy(g_bdAddr, addr, 6);
|
||||
}
|
||||
|
||||
// Re-assert discoverable + connectable with the new address (no reset
|
||||
// happened, so name/CoD/SSP/event-mask all persist).
|
||||
Hci::WriteScanEnable(0x03);
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (g_bdAddr[i] != addr[i]) return false;
|
||||
}
|
||||
KernelLogStream(OK, "BT") << "BD_ADDR changed at runtime";
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Scan — blocking inquiry
|
||||
// =========================================================================
|
||||
@@ -477,4 +629,21 @@ namespace Drivers::USB::Bluetooth {
|
||||
return count;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// ListBonded / ForgetDevice — paired-device management
|
||||
// =========================================================================
|
||||
|
||||
int ListBonded(Hci::BondInfo* buf, int maxCount) {
|
||||
if (!g_initialized || !buf || maxCount <= 0) return 0;
|
||||
return Hci::ListBonds(buf, maxCount);
|
||||
}
|
||||
|
||||
int ForgetDevice(const uint8_t* bdAddr) {
|
||||
if (!g_initialized || !bdAddr) return -1;
|
||||
// Best-effort: tear down an active link first so we don't keep a live
|
||||
// connection whose key we just discarded.
|
||||
Disconnect(bdAddr);
|
||||
return Hci::ForgetBond(bdAddr) ? 0 : -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,6 +34,16 @@ namespace Drivers::USB::Bluetooth {
|
||||
// Get Bluetooth device address (6 bytes)
|
||||
const uint8_t* GetBdAddr();
|
||||
|
||||
// Change the adapter's BD_ADDR at runtime. Drops any active links (their
|
||||
// pairing/crypto was bound to the old address), issues the Intel vendor
|
||||
// write (0xFC31), re-reads the controller's address into the cache and
|
||||
// re-asserts scan. Returns true only if the controller reports the new
|
||||
// address. Persisting the choice to 0:/config/bluetooth.toml is the
|
||||
// caller's job (userspace); the override is re-applied on boot by
|
||||
// ApplyConfiguredAddress() during init. Bytes are in the same order as
|
||||
// GetBdAddr()/ReadBdAddr (addr[0] = least-significant octet).
|
||||
bool SetAddress(const uint8_t* addr);
|
||||
|
||||
// Scan for nearby devices (blocking, up to timeoutMs)
|
||||
// Returns number of devices found; results written to buf
|
||||
int Scan(Hci::InquiryDevice* buf, int maxCount, uint32_t timeoutMs);
|
||||
@@ -50,4 +60,12 @@ namespace Drivers::USB::Bluetooth {
|
||||
// Returns number of connected devices; info written to buf
|
||||
int ListConnected(Hci::ConnectionInfo* buf, int maxCount);
|
||||
|
||||
// List bonded (paired) devices from the persisted link-key store.
|
||||
// Returns number written to buf (paired devices may be disconnected).
|
||||
int ListBonded(Hci::BondInfo* buf, int maxCount);
|
||||
|
||||
// Forget a paired device: drop the link if connected and remove the bond
|
||||
// (so it must re-pair next time). Returns 0 on success, -1 if not bonded.
|
||||
int ForgetDevice(const uint8_t* bdAddr);
|
||||
|
||||
}
|
||||
|
||||
@@ -256,6 +256,30 @@ namespace Drivers::USB::Bluetooth::Hci {
|
||||
KernelLogStream(OK, "BT-HCI") << "Link key store persisted";
|
||||
}
|
||||
|
||||
int ListBonds(BondInfo* buf, int maxCount) {
|
||||
if (!buf || maxCount <= 0) return 0;
|
||||
int n = 0;
|
||||
for (int i = 0; i < MAX_BONDS && n < maxCount; i++) {
|
||||
if (g_bonds[i].valid) {
|
||||
memcpy(buf[n].Addr, g_bonds[i].addr, 6);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
bool ForgetBond(const uint8_t* addr) {
|
||||
if (!addr) return false;
|
||||
int idx = FindBondIndex(addr);
|
||||
if (idx < 0) return false;
|
||||
g_bonds[idx].valid = false;
|
||||
memset(g_bonds[idx].key, 0, 16);
|
||||
memset(g_bonds[idx].addr, 0, 6);
|
||||
g_bondsDirty = true;
|
||||
FlushLinkKeys(); // persist removal now (caller is process context)
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// USB transfer callback
|
||||
// =========================================================================
|
||||
|
||||
@@ -145,6 +145,11 @@ namespace Drivers::USB::Bluetooth::Hci {
|
||||
|
||||
constexpr int MAX_CONNECTIONS = 4;
|
||||
|
||||
// A persisted (bonded/paired) device, as stored in the link-key store.
|
||||
struct BondInfo {
|
||||
uint8_t Addr[6];
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Intel Bluetooth version info
|
||||
// =========================================================================
|
||||
@@ -252,6 +257,14 @@ namespace Drivers::USB::Bluetooth::Hci {
|
||||
void LoadLinkKeys();
|
||||
void FlushLinkKeys();
|
||||
|
||||
// Enumerate the stored bonds (paired devices) into buf; returns the count
|
||||
// written (<= maxCount). Used to list paired-but-disconnected devices.
|
||||
int ListBonds(BondInfo* buf, int maxCount);
|
||||
|
||||
// Remove a stored bond by BD_ADDR and persist the removal (process context).
|
||||
// Returns true if a matching bond was found and forgotten.
|
||||
bool ForgetBond(const uint8_t* addr);
|
||||
|
||||
// Non-blocking peek at the most recent 0xFF/0x06 secure-send result without
|
||||
// consuming it. Returns true if one has arrived since the last
|
||||
// ClearSecureSendResult(). The payload loop uses this to catch a mid-stream
|
||||
|
||||
Reference in New Issue
Block a user