DF-2245 / kqsort_depth_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 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 | /* * kqsort_depth_harness.c -- DF-2245 proof-of-defect (standalone) * * Replicates the EXACT algorithm of sys/libkern/qsort.c (kqsort) twice: * (1) kqsort_VULN : identical to the in-kernel code (recurse on the * "< pivot" band, iterate on the "> pivot" band). * (2) kqsort_FIXED : recurse on the SMALLER band, iterate on the larger * one -> recursion depth bounded to O(log2 n). * * Both carry a global max-recursion-depth counter. We feed each: * - random input * - sorted ascending / descending * - "organ-pipe" * - an adaptive median-of-3 killer adversary (McIlroy-style) * * The vulnerable variant reaches O(n) depth on the killer input; the fixed * variant stays at O(log n). This proves the code defect WITHOUT depending * on any kernel path -- the defect is a pure algorithmic property of * qsort.c lines 173-184. * * Build: cc -O2 -o kqsort_depth_harness kqsort_depth_harness.c * Run: ./kqsort_depth_harness */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> static int g_maxdepth; /* observed max recursion depth (VULN or FIXED) */ static int g_curdepth; static int g_cap; /* hard recursion cap (prevents userspace stack overflow so we can print) */ static int g_overflow; /* set if cap hit */ #define DEPTH_PROLOG() do { ++g_curdepth; if(g_curdepth>g_maxdepth) g_maxdepth=g_curdepth; \ if (g_cap && g_curdepth>=g_cap){ g_overflow=1; --g_curdepth; return; } } while(0) /* ----------------------------- common qsort guts -------------------------- */ typedef int cmp_t(const void *, const void *); static inline char *med3(char *a, char *b, char *c, cmp_t *cmp) { return cmp(a, b) < 0 ? (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a)) : (cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c)); } static inline void swapfunc(char *a, char *b, int n, int st) { long i; char *pi, *pj; if (st <= 1) { i=n/sizeof(long); pi=a; pj=b; do { long t=*(long*)pi; *(long*)pi=*(long*)pj; *(long*)pj=t; pi++; pj++; } while(--i>0); } else { i=n; pi=a; pj=b; do { char t=*pi; *pi=*pj; *pj=t; pi++; pj++; } while(--i>0); } } #define SWAPINIT(a,es) st = ((char*)(a)-(char*)0)%sizeof(long)||es%sizeof(long)?2:es==sizeof(long)?0:1 #define min(a,b) ((a)<(b)?(a):(b)) #define vecswap(a,b,n,st) if((n)>0) swapfunc(a,b,n,st) /* --------------------------- VULNERABLE (as in kernel) -------------------- */ static void kqsort_VULN(char *a, size_t n, size_t es, cmp_t *cmp) { char *pa,*pb,*pc,*pd,*pl,*pm,*pn; int d,r,st,sc; DEPTH_PROLOG(); loop: SWAPINIT(a,es); sc=0; if (n<7){ for(pm=a+es;pm<a+n*es;pm+=es) for(pl=pm;pl>a&&cmp(pl-es,pl)>0;pl-=es) swapfunc(pl,pl-es,es,st); --g_curdepth; return; } pm=a+(n/2)*es; if(n>7){pl=a;pn=a+(n-1)*es; if(n>40){d=(n/8)*es;pl=med3(pl,pl+d,pl+2*d,cmp);pm=med3(pm-d,pm,pm+d,cmp);pn=med3(pn-2*d,pn-d,pn,cmp);} pm=med3(pl,pm,pn,cmp);} {swapfunc(a,pm,es,st);} pa=pb=a+es; pc=pd=a+(n-1)*es; for(;;){ while(pb<=pc&&(r=cmp(pb,a))<=0){ if(r==0){sc=1;swapfunc(pa,pb,es,st);pa+=es;} pb+=es;} while(pb<=pc&&(r=cmp(pc,a))>=0){ if(r==0){sc=1;swapfunc(pd,pc,es,st);pd-=es;} pc-=es;} if(pb>pc)break; swapfunc(pb,pc,es,st); sc=1; pb+=es; pc-=es; } if(sc==0){ for(pm=a+es;pm<a+n*es;pm+=es) for(pl=pm;pl>a&&cmp(pl-es,pl)>0;pl-=es){swapfunc(pl,pl-es,es,st);} --g_curdepth; return; } pn=a+n*es; r=min(pa-a,pb-pa); vecswap(a,pb-r,r,st); r=min(pd-pc,pn-pd-es); vecswap(pb,pn-r,r,st); if((r=pb-pa)>es) kqsort_VULN(a,r/es,es,cmp); /* RECURSE on "<" */ if((r=pd-pc)>es){ a=pn-r; n=r/es; goto loop; } /* ITERATE on ">" */ --g_curdepth; } /* --------------------------- FIXED (recurse on smaller) ------------------- */ static void kqsort_FIXED(char *a, size_t n, size_t es, cmp_t *cmp) { char *pa,*pb,*pc,*pd,*pl,*pm,*pn; int d,r,st,sc; DEPTH_PROLOG(); loop: SWAPINIT(a,es); sc=0; if (n<7){ for(pm=a+es;pm<a+n*es;pm+=es) for(pl=pm;pl>a&&cmp(pl-es,pl)>0;pl-=es) swapfunc(pl,pl-es,es,st); --g_curdepth; return; } pm=a+(n/2)*es; if(n>7){pl=a;pn=a+(n-1)*es; if(n>40){d=(n/8)*es;pl=med3(pl,pl+d,pl+2*d,cmp);pm=med3(pm-d,pm,pm+d,cmp);pn=med3(pn-2*d,pn-d,pn,cmp);} pm=med3(pl,pm,pn,cmp);} {swapfunc(a,pm,es,st);} pa=pb=a+es; pc=pd=a+(n-1)*es; for(;;){ while(pb<=pc&&(r=cmp(pb,a))<=0){ if(r==0){sc=1;swapfunc(pa,pb,es,st);pa+=es;} pb+=es;} while(pb<=pc&&(r=cmp(pc,a))>=0){ if(r==0){sc=1;swapfunc(pd,pc,es,st);pd-=es;} pc-=es;} if(pb>pc)break; swapfunc(pb,pc,es,st); sc=1; pb+=es; pc-=es; } if(sc==0){ for(pm=a+es;pm<a+n*es;pm+=es) for(pl=pm;pl>a&&cmp(pl-es,pl)>0;pl-=es){swapfunc(pl,pl-es,es,st);} --g_curdepth; return; } pn=a+n*es; r=min(pa-a,pb-pa); vecswap(a,pb-r,r,st); r=min(pd-pc,pn-pd-es); vecswap(pb,pn-r,r,st); { size_t nl = (pb-pa)/es; /* "<" partition count */ size_t nr = (pd-pc)/es; /* ">" partition count */ char *larger_a; size_t larger_n; char *smaller_a; size_t smaller_n; if (nl >= nr) { larger_a=a; larger_n=nl; smaller_a=pn-(pd-pc); smaller_n=nr; } else { larger_a=pn-(pd-pc); larger_n=nr; smaller_a=a; smaller_n=nl; } /* recurse on the SMALLER half -> O(log n) depth bound */ if (smaller_n*es > es) kqsort_FIXED(smaller_a, smaller_n, es, cmp); /* iterate on the larger half */ if (larger_n*es > es) { a=larger_a; n=larger_n; goto loop; } } --g_curdepth; } /* ----------------------------- inputs ------------------------------------- */ static cmp_t icmp; /* plain int comparator */ static int icmp(const void *a, const void *b){ int x=*(const int*)a, y=*(const int*)b; return (x>y)-(x<y); } /* McIlroy-style adaptive median-of-3 killer adversary. * val[] holds each item's current rank; ties are broken adversarially by * handing out a fresh increasing rank, which steers the med3 pivot so the * "< pivot" partition stays large. This drives the vulnerable (recurse-on-<) * variant to O(n) depth. */ static int *kval; static int knext; static int killer_cmp(const void *a, const void *b){ int x=*(const int*)a, y=*(const int*)b; /* x,y are item indices */ if (kval[x]==kval[y]) { kval[x]=knext++; return 1; } /* promote x above y */ return kval[x]<kval[y]?-1:1; } static void reset_depth(void){ g_maxdepth=0; g_curdepth=0; g_overflow=0; } static int *mkarr(int n, const char *kind){ int *a=malloc(n*sizeof(int)); int i; if (!strcmp(kind,"random")) for(i=0;i<n;i++) a[i]=rand(); else if(!strcmp(kind,"asc")) for(i=0;i<n;i++) a[i]=i; else if(!strcmp(kind,"desc")) for(i=0;i<n;i++) a[i]=n-i; else if(!strcmp(kind,"organ")){ for(i=0;i<n/2;i++) a[i]=i+1; for(;i<n;i++) a[i]=n-i; } else if(!strcmp(kind,"killer")){ for(i=0;i<n;i++) a[i]=i; /* indices, val-driven */ kval=malloc(n*sizeof(int)); memset(kval,0,n*sizeof(int)); knext=1; } else { for(i=0;i<n;i++) a[i]=0; } return a; } static int verify_sorted(int *a, int n, cmp_t *cmp){ for(int i=1;i<n;i++) if(cmp(a+i-1,a+i)>0) return 0; return 1; } int main(void){ int ns[]={64,128,256,512,1024,2048,4096}; const char *kinds[]={"random","asc","desc","organ","killer"}; g_cap = 200000; /* prevent userspace stack overflow; flag if exceeded */ printf("%-7s %-8s %12s %12s %14s\n","N","input","VULN_depth","FIXED_depth","log2(N)"); printf("------------------------------------------------------------------------\n"); for(unsigned k=0;k<sizeof(ns)/sizeof(ns[0]);k++){ int n=ns[k]; int log2n=0; while((1<<log2n)<n) log2n++; for(unsigned j=0;j<sizeof(kinds)/sizeof(kinds[0]);j++){ const char *kind=kinds[j]; /* VULN */ int *a=mkarr(n,kind); reset_depth(); cmp_t *c = !strcmp(kind,"killer") ? killer_cmp : icmp; kqsort_VULN((char*)a,n,sizeof(int),c); int vd=g_maxdepth; int vo=g_overflow; int okv = strcmp(kind,"killer")==0 ? 1 : verify_sorted(a,n,icmp); free(a); if(kval){free(kval);kval=NULL;} /* FIXED */ a=mkarr(n,kind); reset_depth(); c = !strcmp(kind,"killer") ? killer_cmp : icmp; kqsort_FIXED((char*)a,n,sizeof(int),c); int fd=g_maxdepth; int fo=g_overflow; int okf = strcmp(kind,"killer")==0 ? 1 : verify_sorted(a,n,icmp); free(a); if(kval){free(kval);kval=NULL;} (void)okv;(void)okf; char vbuf[24], fbuf[24]; snprintf(vbuf,sizeof vbuf, vo?"%d(OVF)":"%d", vd); snprintf(fbuf,sizeof fbuf, fo?"%d(OVF)":"%d", fd); printf("%-7d %-8s %12s %12s %14d\n", n, kind, vbuf, fbuf, log2n); } } /* log2 table for reference */ printf("\nReference log2(N): "); for(unsigned k=0;k<sizeof(ns)/sizeof(ns[0]);k++){ int n=ns[k], l=0; while((1<<l)<n) l++; printf("N=%d->%d ", n, l); } printf("\n"); return 0; } |