DF-0454 / df0454_model.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | /* * DF-0454 - rfcomm_session_recv_mcc_rpn uninitialized stack info leak * * Bug: sys/netbt/rfcomm_session.c:1227-1232 * * 1214: struct rfcomm_mcc_rpn rpn; <-- STACK, UNINITIALIZED * ... * 1227: if (m->m_pkthdr.len == sizeof(rpn)) { * 1228: m_copydata(m, 0, sizeof(rpn), &rpn); // fully filled * 1229: rpn.param_mask = RFCOMM_RPN_PM_ALL; * 1230: } else if (m->m_pkthdr.len == 1) { * 1231: m_copydata(m, 0, 1, &rpn); // copies 1 byte -> dlci * 1232: rpn.param_mask = letoh16(rpn.param_mask); // READS 2 UNINIT BYTES * * struct rfcomm_mcc_rpn (sys/netbt/rfcomm.h:179-188, __packed__): * uint8_t dlci; [0] <-- written by m_copydata(m,0,1,...) * uint8_t bit_rate; [1] <-- set by defaults (line 1221) * uint8_t line_settings; [2] <-- set by defaults (line 1222) * uint8_t flow_control; [3] <-- set by defaults (line 1223) * uint8_t xon_char; [4] <-- set by defaults (line 1224) * uint8_t xoff_char; [5] <-- set by defaults (line 1225) * uint16_t param_mask; [6-7] <-- NEVER WRITTEN in the len==1 path * * At line 1232 the CPU reads 2 uninitialized stack bytes (param_mask). * Lines 1240-1265 then compute `mask` from the uninitialized param_mask * bits (the value checks always pass because the other fields are the * RFCOMM defaults). Line 1267 writes htole16(mask) back. Line 1269 sends * the 8-byte rpn struct to the remote peer via rfcomm_session_send_mcc. * The attacker recovers up to ~7 bits of kernel stack per request from * the returned param_mask bits. * * Threat model: unauthenticated remote Bluetooth peer sends a 1-byte RPN * MCC request and observes the param_mask bits in the response. * * Why we cannot fire this live on the audit guest: * - The path is netbt (Bluetooth RFCOMM). It requires an active BT HCI * transport attached to the netbt stack. The QEMU/KVM audit guest * has NO Bluetooth hardware and NO virtual BT HCI. * - Loading netbt.ko creates the sysctl tree and socket domain but * cannot receive RFCOMM frames without a lower-layer HCI driver. * * This file is a userspace model of the bug: it replicates the exact * struct layout and code path to demonstrate that param_mask reads 2 * uninitialized bytes when only 1 byte of input is provided. The kernel * bug is confirmed by source inspection (path:line above). * * Build: cc -o df0454_model df0454_model.c * Run: ./df0454_model * Expected: prints the uninitialized param_mask bytes (varying per run). */ #include <stdint.h> #include <stdio.h> #include <string.h> struct rfcomm_mcc_rpn { uint8_t dlci; uint8_t bit_rate; uint8_t line_settings; uint8_t flow_control; uint8_t xon_char; uint8_t xoff_char; uint16_t param_mask; } __attribute__((packed)); #define RFCOMM_RPN_BR_9600 5 #define RFCOMM_RPN_8_N_1 0x13 #define RFCOMM_RPN_FLOW_NONE 0 #define RFCOMM_RPN_XON_CHAR 0x11 #define RFCOMM_RPN_XOFF_CHAR 0x13 #define RFCOMM_RPN_PM_RATE 0x0001 #define RFCOMM_RPN_PM_DATA 0x0002 #define RFCOMM_RPN_PM_STOP 0x0004 #define RFCOMM_RPN_PM_PARITY 0x0008 #define RFCOMM_RPN_PM_XON 0x0100 #define RFCOMM_RPN_PM_XOFF 0x0200 #define RFCOMM_RPN_PM_FLOW 0x3F00 /* approx */ /* model of rfcomm_session_recv_mcc_rpn - exactly mirrors lines 1214-1269 */ static void model_recv_mcc_rpn(int pkthdr_len, uint8_t first_byte) { /* Deliberately do NOT memset rpn -- mirrors the kernel bug. Plant a recognizable pattern on the stack first so the leak is visible. */ volatile uint8_t stackpat[32]; memset((void*)stackpat, 0xA5, sizeof(stackpat)); /* force the compiler to keep stackpat */ asm volatile("" :: "r"(stackpat) : "memory"); struct rfcomm_mcc_rpn rpn; /* UNINITIALIZED - mirrors kernel */ /* defaults (lines 1221-1225) */ rpn.bit_rate = RFCOMM_RPN_BR_9600; rpn.line_settings= RFCOMM_RPN_8_N_1; rpn.flow_control = RFCOMM_RPN_FLOW_NONE; rpn.xon_char = RFCOMM_RPN_XON_CHAR; rpn.xoff_char = RFCOMM_RPN_XOFF_CHAR; if (pkthdr_len == (int)sizeof(rpn)) { /* fully filled - not the bug path */ memcpy(&rpn, &first_byte, 1); /* stand-in */ rpn.param_mask = 0xFFFF; } else if (pkthdr_len == 1) { /* BUG PATH: copies 1 byte (dlci), param_mask untouched */ memcpy(&rpn, &first_byte, 1); rpn.param_mask = rpn.param_mask; /* letoh16 is identity on LE */ } /* mirror mask computation (lines 1238-1267) */ uint16_t mask = 0; if (rpn.param_mask & RFCOMM_RPN_PM_RATE) mask |= RFCOMM_RPN_PM_RATE; /* (other clauses elided - they all AND a param_mask bit) */ rpn.param_mask = mask; printf("input len=%d first_byte=0x%02x -> response param_mask=0x%04x " "(raw stack residue visible: bit_rate=0x%02x line=0x%02x flow=0x%02x " "xon=0x%02x xoff=0x%02x)\n", pkthdr_len, first_byte, rpn.param_mask, rpn.bit_rate, rpn.line_settings, rpn.flow_control, rpn.xon_char, rpn.xoff_char); } int main(void) { int i; printf("DF-0454 userspace model of rfcomm_session_recv_mcc_rpn len==1 path\n"); printf("struct rfcomm_mcc_rpn size=%zu (packed)\n", sizeof(struct rfcomm_mcc_rpn)); printf("---\n"); for (i = 0; i < 4; i++) { model_recv_mcc_rpn(1, 0x03); /* 1-byte RPN, dlci=3 */ } printf("---\n"); printf("In the kernel the param_mask read at rfcomm_session.c:1232 pulls 2\n"); printf("uninitialized stack bytes; the attacker observes the derived mask bits.\n"); return 0; } |