DF-3730 / 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 | /* * DF-3730 harness โ ciss_cam_complete_fixup() (sys/dev/raid/ciss/ciss.c: * 3323-3325) rewrites the INQUIRY result of logical volumes: * * padstr(inq->vendor, "COMPAQ", 8); ok (8-byte field) * padstr(inq->product, ciss_name_ldrive_org(...), 8); ok (16-byte field) * padstr(inq->revision, ciss_name_ldrive_status(...), 16); <-- 16 bytes into the * 4-byte revision * field * (scsi_all.h:739: char revision[SID_REVISION_SIZE=4], at buffer offset 32) * * The only gate is csio->dxfer_len >= SHORT_INQUIRY_LENGTH (36) (ciss.c:3305), * so a CAM client that issues a standard 36-byte INQUIRY (e.g. via the CAM * pass-through: camcontrol) gets 12 bytes written past its 36-byte data * buffer โ kernel heap overflow with (limited) string content. * * Build: cc -O2 -Wall -o harness harness.c * Fixed: cc -DFIXED ... uses SID_REVISION_SIZE (4) for the revision padstr. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> /* ---- real kernel constants (scsi_all.h) ---- */ #define SHORT_INQUIRY_LENGTH 36 /* scsi_all.h:652 */ #define SID_VENDOR_SIZE 8 /* scsi_all.h:734 */ #define SID_PRODUCT_SIZE 16 /* scsi_all.h:736 */ #define SID_REVISION_SIZE 4 /* scsi_all.h:738 */ /* struct scsi_inquiry_data prefix: device(1) reserved(1) version(1) * ... flags area ... vendor@8 product@16 revision@32 โ per SPC/SPC-2 layout * used by scsi_all.h. */ /* cissvar.h:374-384 โ exact replica of padstr() */ static int padstr_checking(char *targ_base, size_t targ_off, size_t targ_len, const char *src, int len, size_t bufsize) { size_t i; int overflow = 0; for (i = 0; i < (size_t)len; i++) { char c = (*src != 0) ? *src++ : ' '; size_t at = targ_off + i; if (at >= bufsize) { if (!overflow) fprintf(stderr, " write at buffer+%zu" " (>= %zu)\n", at, bufsize); overflow++; } else if (at >= targ_off + targ_len) { fprintf(stderr, " write at %zu past field end %zu" " (still inside buffer)\n", at, targ_off + targ_len); } ((volatile char *)targ_base)[at] = c; /* the actual store */ } return overflow; } static const char * status_name(int s) /* ciss_name_ldrive_status */ { return s == 0 ? "OK" : s == 1 ? "failed" : "unknown status"; } int main(void) { char *buf; /* the CAM data buffer */ size_t dxfer_len = SHORT_INQUIRY_LENGTH; /* 36-byte INQUIRY */ size_t alloc = 64; /* 36 + 28 canary */ int over = 0, i; printf("== DF-3730 ciss_cam_complete_fixup padstr overflow ==\n"); printf("CAM client INQUIRY: dxfer_len=%zu (passes the ciss.c:3305 gate" " >= %d)\n\n", dxfer_len, SHORT_INQUIRY_LENGTH); buf = malloc(alloc); memset(buf, 0, alloc); for (i = dxfer_len; i < (int)alloc; i++) /* canary past buffer */ buf[i] = (char)(0xC0 + (i - dxfer_len)); /* ciss.c:3323-3325 โ the three padstr calls, offsets per * struct scsi_inquiry_data */ over += padstr_checking(buf, 8, SID_VENDOR_SIZE, "COMPAQ", 8, dxfer_len); over += padstr_checking(buf, 16, SID_PRODUCT_SIZE, "RAID 5", 8, dxfer_len); over += padstr_checking(buf, 32, SID_REVISION_SIZE, status_name(1), #ifdef FIXED SID_REVISION_SIZE, #else 16, /* ciss.c:3325 โ the bug */ #endif dxfer_len); /* show the smashed canary */ printf(" buffer[32..47] = "); for (i = 32; i < 48; i++) printf("%02x ", (unsigned char)buf[i]); printf("\n canary [36..47] was 0xc0..0xcb\n\n"); if (over > 0) { printf("[BUG REPRODUCED] %d bytes written past the end of the" " %zu-byte INQUIRY data\n" "buffer (revision field is 4 bytes at offset 32; padstr" " writes 16).\nKernel impact: 12-byte heap overflow past" " a kmalloc'd CAM buffer with\nstatus-string content" " (root/operator via CAM pass-through).\n", over, dxfer_len); return 0; } printf("[FIXED] revision padstr bounded to SID_REVISION_SIZE (4):" " no bytes past the buffer.\n"); return 0; } |