DF-4230 / model_uninit_regs.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 | /* * DF-4230 harness: uninitialized `regs` read in cyapa_poll_thread() * (sys/dev/smbus/cyapa/cyapa.c:1161-1197) * * struct cyapa_regs regs; <-- cyapa.c:1164, uninitialized * error = smbus_request_bus(bus, sc->dev, SMB_WAIT); * if (error == 0) { * error = smbus_trans(..., (void *)®s, sizeof(regs), NULL); * if (error == 0) * isidle = cyapa_raw_input(sc, ®s); * } * if (cyapa_reset || * ((regs.stat & CYAPA_STAT_RUNNING) == 0 && <-- :1192 * (unsigned)(ticks - last_reset) > TIME_TO_RESET)) { * ... * init_device(sc->dev, NULL, sc->addr, 2); <-- :1196 * } * * When smbus_trans() fails (device NAK / bus error) it does not write * `regs`; on the FIRST failing iteration regs.stat is an uninitialized * stack byte that gates the re-init logic. After 3s of consecutive * failures the garbage byte decides whether the driver hammers the bus * with bootloader handshakes every 3 seconds. * * Demonstration: stack-poison + failure stub, exact branch structure. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define CYAPA_MAX_MT 5 #define CYAPA_STAT_RUNNING 0x80 #define TIME_TO_RESET 300 /* hz*3 with hz=100 */ struct cyapa_regs { uint8_t stat; uint8_t fngr; struct { uint8_t xy_high, x_low, y_low, pressure, id; } touch[CYAPA_MAX_MT]; } __packed; static int smbus_trans_fails = 1; /* device absent/NAK */ static int ticks = 1000; static int cyapa_reset_sysctl = 0; /* model of the reset decision, verbatim branch structure of :1191-1193 */ static int poll_iteration(struct cyapa_regs *regs, int last_reset) { int error; error = smbus_trans_fails; /* smbus_trans fails */ if (error == 0) return 0; /* raw_input skipped */ if (cyapa_reset_sysctl || ((regs->stat & CYAPA_STAT_RUNNING) == 0 && /* cyapa.c:1192 */ (unsigned)(ticks - last_reset) > TIME_TO_RESET)) return 1; /* init_device() called */ return 0; } /* * regs is a local of cyapa_poll_thread (cyapa.c:1164); give the model the * same shape so its stack slot lands inside the poisoned region. */ static int poll_thread_model(void) { struct cyapa_regs regs; /* cyapa.c:1164 */ int last_reset = ticks; int reinited = 0, iters = 0; /* * Simulate 40 seconds of polling with the device failing every * transaction (smbus_trans never writes `regs`). */ for (iters = 0; iters < 40; iters++) { if (poll_iteration(®s, last_reset)) { reinited = 1; break; } ticks += 10; /* 10Hz idle poll */ } printf("after %d failing poll iterations: reinited=%d " "(garbage regs.stat=0x%02x decided)\n", iters, reinited, regs.stat); return reinited; } static unsigned char poison_byte = 0xCD; static void poison_stack(void) { volatile unsigned char junk[8192]; memset((void *)junk, poison_byte, sizeof(junk)); } int main(int argc, char **argv) { int r; if (argc > 1) poison_byte = (unsigned char)strtoul(argv[1], NULL, 0); printf("poison byte = 0x%02x (bit7 RUNNING = %s)\n", poison_byte, (poison_byte & 0x80) ? "set" : "clear"); poison_stack(); r = poll_thread_model(); if (r) { printf("RESULT: re-init decision taken from UNINITIALIZED " "regs.stat (byte 0x%02x, RUNNING bit clear)\n", poison_byte); return 0; } printf("RESULT: no re-init (byte 0x%02x had RUNNING bit set)\n", poison_byte); return 0; } |