DF-0916 / trigger.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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | /* * DF-0916 - PoC trigger / defect demonstrator. * * smb_time_unix2dos() in sys/vfs/smbfs/smbfs_subr.c lines 173-178 contains an * unbounded year-computation loop: * * for (year = 1970;; year++) { * inc = year & 0x03 ? 365 : 366; * if (days < inc) break; * days -= inc; * } * * `days` is `u_long t / (24*60*60)`. On x86_64, `u_long` is 64 bits, and * smb_time_local2server() does `*seconds = tsp->tv_sec - tzoff*60;` with no * range guard, so a caller-supplied tv_sec (signed time_t) of INT64_MAX or -1 * produces `days ~= 1e14` / `days ~= 2e17`, requiring ~3e11 / ~6e14 loop * iterations - i.e. minutes-to-days of kernel CPU per syscall. * * The kernel-reachable trigger path is: * utimensat(fd, {INT64_MAX,0}) -> VOP_SETATTR(smbfs vp) * -> smbfs_setattr() [sys/vfs/smbfs/smbfs_vnops.c:297] * -> smbfs_smb_setpattr/setftime/setptime2 (mtime) * -> smb_time_unix2dos(mtime, ...) [sys/vfs/smbfs/smbfs_smb.c:326/421] * * SMBFS is a kld module (not built into GENERIC) and the SMB-client mount * requires an SMB server endpoint, so the full unprivileged utimensat path is * not exercised end-to-end in this guest (no SMB server). This PoC * reproduces the EXACT loop body from smb_time_unix2dos in userspace to * demonstrate the algorithmic defect directly: with INT64_MAX (or -1) input * the loop does not terminate in any human-reasonable timeframe. * * Build: cc -O2 -o trigger trigger.c -lm * Run : ./trigger [seconds_value] * ./trigger # defaults to INT64_MAX * ./trigger -1 # signed -1 reinterpreted as u_long = UINT64_MAX * ./trigger 0 # sane value: returns instantly * * The harness runs the loop with an iteration cap (LOOP_CAP), so you can see * the iteration count required; set LOOP_CAP high enough on a long input to * prove the loop is unbounded. */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <limits.h> #include <time.h> #include <signal.h> #include <unistd.h> #include <string.h> #include <sys/time.h> typedef unsigned long u_long; typedef unsigned short u_short; typedef unsigned int u_int; typedef uint16_t u_int16_t; typedef uint8_t u_int8_t; #define DT_2SECONDS_SHIFT 0 #define DT_MINUTES_SHIFT 5 #define DT_HOURS_SHIFT 11 #define DD_DAY_SHIFT 0 #define DD_MONTH_SHIFT 5 #define DD_YEAR_SHIFT 9 static u_short regyear[] = { 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; static u_short leapyear[] = { 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }; /* Iteration cap. Override with $LOOP_CAP env. */ static unsigned long long LOOP_CAP = 500000000ULL; /* 5e8 - ~1s on a fast CPU */ static volatile int timed_out = 0; static void on_alarm(int s) { (void)s; timed_out = 1; } /* * EXACT copy of smb_time_unix2dos year-loop region (sys/vfs/smbfs/smbfs_subr.c * lines 157..194). We instrument the loop with an iteration counter and honor * LOOP_CAP / SIGALRM so the harness cannot livelock the test machine. */ static int smb_time_unix2dos_instrumented(long long tv_sec, int tzoff, u_int16_t *ddp, u_int16_t *dtp, unsigned long long *iters) { u_long t, days, year, inc; u_short *months; u_short lastdtime, lastddate; u_long lastday; unsigned long long n = 0; /* smb_time_local2server: */ t = (u_long)(tv_sec - (long long)tzoff * 60); t &= ~1; lastdtime = (u_short)((((t / 2) % 30) << DT_2SECONDS_SHIFT) + (((t / 60) % 60) << DT_MINUTES_SHIFT) + (((t / 3600) % 24) << DT_HOURS_SHIFT)); days = t / (24 * 60 * 60); lastday = days; /* The buggy loop. */ for (year = 1970;; year++) { inc = year & 0x03 ? 365 : 366; if (days < inc) break; days -= inc; if (++n >= LOOP_CAP) { *iters = n; return -1; /* hit cap => loop unbounded for this input */ } if (timed_out) { *iters = n; return -2; } } months = year & 0x03 ? regyear : leapyear; /* (the month subloop is bounded by days, so safe.) */ u_long month; for (month = 0; days >= months[month]; month++) ; if (month > 0) days -= months[month - 1]; lastddate = (u_short)(((days + 1) << DD_DAY_SHIFT) + ((month + 1) << DD_MONTH_SHIFT)); if (year > 1980) lastddate += (u_short)((year - 1980) << DD_YEAR_SHIFT); *ddp = lastddate; *dtp = lastdtime; *iters = n; return 0; } int main(int argc, char **argv) { long long input; if (argc >= 2) { if (strcmp(argv[2 % argc], "-") == 0 || strstr(argv[1],"-1")) { /* allow "-1" string */ input = -1LL; } else { input = strtoll(argv[1], NULL, 0); } } else { input = (long long)INT64_MAX; } const char *cap_env = getenv("LOOP_CAP"); if (cap_env) LOOP_CAP = strtoull(cap_env, NULL, 0); printf("DF-0916 smb_time_unix2dos livelock demonstrator\n"); printf(" input tv_sec = %lld (reinterpreted as u_long = 0x%016llx, days=%llu)\n", input, (unsigned long long)(u_long)input, (unsigned long long)((u_long)input / (24UL*60*60))); printf(" LOOP_CAP = %llu iterations\n", LOOP_CAP); printf(" iter cap simulates a hard watchdog (real kernel code has NONE)\n"); u_int16_t ddp = 0, dtp = 0; unsigned long long iters = 0; timed_out = 0; struct sigaction sa; memset(&sa, 0, sizeof sa); sa.sa_handler = on_alarm; sigaction(SIGALRM, &sa, NULL); /* Wall-clock safety: 10s alarm so the harness never wedges. */ alarm(10); struct timeval t0, t1; gettimeofday(&t0, NULL); int rc = smb_time_unix2dos_instrumented(input, /*tzoff*/0, &ddp, &dtp, &iters); gettimeofday(&t1, NULL); double dt = (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec) / 1e6; if (rc == 0) { printf("RESULT: TERMINATED in %llu iterations, %.6fs; ddp=0x%04x dtp=0x%04x\n", iters, dt, ddp, dtp); return 0; } else if (rc == -1) { printf("RESULT: LIVELOCK - hit LOOP_CAP after %llu iterations (%.2fs)\n", iters, dt); printf(" Kernel code (no cap) would continue past %llu iterations;\n", iters); printf(" full iteration count for this input ~= %llu => minutes of kernel CPU.\n", (unsigned long long)((u_long)input / (24UL*60*60) / 365)); return 2; } else { printf("RESULT: TIMEOUT - 10s wall alarm fired after %llu iterations\n", iters); return 3; } } |