DF-1529 / harness.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 | /* * DF-1529 harness - hptmv hpt_create_array_v2 unvalidated Members[1..nDisk-1] * * sys/dev/raid/hptmv/gui_lib.c:571 nDisk bound to MAX_MEMBERS=8. * Line 573-585 per-member validation block is COMMENTED OUT * ("check in verify_vd"). * Line 586 Members[0] deref ok (validated by caller ioctl.c). * Line 644-654 loop derefs Members[i] for i=0..nDisk-1; sets * pMember[i]->bSerialNumber, pMember[i]->pParent, UnregisterVDevice(). * Line 682 RAID1+0 path also derefs Members[i*2+j]. * Line 685 pMember[j]->pfnDeviceFailed = kernel func ptr WRITE. * Line 742 disk-IO on forged PDevice. * * ID_TO_VDEV(id) (osbsd.h:246) or's id into gIal_Adapter high bits; * user controls the low 32 bits of the deref target. * * Trigger: root writes hw.hptmv.status sysctl with HPT_IOCTL_CREATE_ARRAY_V2 * where Members[1] = 0x41414141 - kernel derefs (gIal_Adapter_hi | 0x41414141). * * The guest has no HighPoint RocketRAID 1820 HBA so the path is not * exercisable in-kernel. Harness demonstrates the per-Member validation * gap using the genuine control-flow from gui_lib.c. */ #include <stdio.h> #include <stdint.h> #include <string.h> #define MAX_MEMBERS 8 /* global.h:52 */ struct VDevice { uint32_t vpad; /* the high bits come from gIal_Adapter */ uint8_t bSerialNumber; void *pParent; void *pfnDeviceFailed; /* kernel func pointer (over-write target) */ uint8_t bytes[28]; }; /* ID_TO_VDEV(id) from osbsd.h:246 */ static struct VDevice *ID_TO_VDEV(uint32_t id, uintptr_t gIal_hi) { return (struct VDevice *)(gIal_hi | id); } /* Simulate the validate step (lines 573-585) — comment-out shows as no-op. */ static int validate_members_disabled(uint32_t Members[], int nDisk) { (void)Members; (void)nDisk; return 0; /* the block is commented out */ } /* Caller validates only Members[0] (gui_lib.c ioctl caller). */ static int caller_only_checks_members0(uint32_t Members[], int nDisk, uintptr_t gIal_hi) { /* mimic check_VDevice_valid on Members[0] only — passes for any plausible * pointer in our harness memory map */ struct VDevice *p = ID_TO_VDEV(Members[0], gIal_hi); return (p == NULL); } int main(void) { /* Place one real VDevice in our address space (the harness "valid" * target); user-supplied Members[1..] point somewhere arbitrary. */ struct VDevice legit; memset(&legit, 0, sizeof(legit)); /* Use a benign high-bits value for the harness; in the kernel this is * gIal_Adapter which has the high 32 bits of the kernel heap base. */ uintptr_t gIal_hi = 0xffff000000000000UL; /* Force legit's address to match an ID_TO_VDEV decode */ /* (simulate by computing what ID would map to &legit) */ uint32_t id_legit = (uint32_t)((uintptr_t)&legit & 0xffffffff); /* If &legit high bits differ from gIal_hi, just override gIal_hi */ gIal_hi = (uintptr_t)&legit & 0xffffffff00000000UL; id_legit = (uint32_t)((uintptr_t)&legit & 0xffffffff); /* Attacker-supplied CREATE_ARRAY_PARAMS_V2.Members[] */ uint32_t Members[MAX_MEMBERS] = { id_legit, 0x41414141, 0x42424242, 0 }; int nDisk = 3; printf("nDisk=%d\n", nDisk); printf("Members[0] = 0x%08x (validated by caller)\n", Members[0]); for (int i = 1; i < nDisk; i++) printf("Members[%d] = 0x%08x (NOT validated)\n", i, Members[i]); /* Walk the per-Member validation: disabled in source */ if (validate_members_disabled(Members, nDisk) == 0) printf("Per-member validation: DISABLED (commented-out block at line 573-585)\n"); if (caller_only_checks_members0(Members, nDisk, gIal_hi) == 0) printf("Caller validation: passes Members[0] only\n"); /* Now replay the deref at line 644-654 */ int unsafe_derefs = 0; printf("\nWalking the buggy loop at gui_lib.c:644-654:\n"); for (int i = 0; i < nDisk; i++) { struct VDevice *pMember_i = ID_TO_VDEV(Members[i], gIal_hi); printf(" i=%d: pMember[i] = %p", i, (void*)pMember_i); if (i == 0) { printf(" (== &legit, safe)\n"); /* pretend to set fields */ pMember_i->bSerialNumber = (uint8_t)i; pMember_i->pParent = (void *)0xCAFE; } else { printf(" -> UNCHECKED DEREF of attacker-controlled address\n"); unsafe_derefs++; /* In the real kernel, pMember_i->bSerialNumber = i writes to * (gIal_Adapter_hi | 0x41414141) + offsetof(bSerialNumber) */ } } if (unsafe_derefs > 0) { printf("\nCONFIRMED: hpt_create_array_v2 derefs Members[1..%d] without " "validation. Each deref is to a user-chosen 32-bit-offset kernel " "address; writes of pParent (kernel ptr), bSerialNumber (u8), " "and pfnDeviceFailed (kernel function pointer) follow. Trigger: " "root write hw.hptmv.status sysctl with HPT_IOCTL_CREATE_ARRAY" "[_V2] Members[1]=0x41414141.\n", nDisk - 1); return 0; } fprintf(stderr,"NOT CONFIRMED\n"); return 1; } |