/* $NetBSD: npf_processor.c,v 1.9.2.4 2013/02/08 19:18:10 riz Exp $ */ /*- * Copyright (c) 2009-2010 The NetBSD Foundation, Inc. * All rights reserved. * * This material is based upon work partially supported by The * NetBSD Foundation under a contract with Mindaugas Rasiukevicius. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * NPF n-code processor. * Inspired by the Berkeley Packet Filter. * * Few major design goals are: * * - Keep engine lightweight, well abstracted and simple. * - Avoid knowledge of internal network buffer structures (e.g. mbuf). * - Avoid knowledge of network protocols. * * There are two instruction sets: RISC-like and CISC-like. The later are * instructions to cover most common filter cases, and reduce interpretation * overhead. These instructions use protocol knowledge and are supposed to * be fully optimized. * * N-code memory address and thus instructions should be word aligned. * All processing is done in 32 bit words, since both instructions (their * codes) and arguments use 32 bits words. */ #include __KERNEL_RCSID(0, "$NetBSD: npf_processor.c,v 1.9.2.4 2013/02/08 19:18:10 riz Exp $"); #include #include #include #include "npf_impl.h" #include "npf_ncode.h" /* * nc_fetch_word: fetch a word (32 bits) from the n-code and increase * instruction pointer by one word. */ static inline const void * nc_fetch_word(const void *iptr, uint32_t *a) { const uint32_t *tptr = (const uint32_t *)iptr; KASSERT(ALIGNED_POINTER(iptr, uint32_t)); *a = *tptr++; return tptr; } /* * nc_fetch_double: fetch two words (2 x 32 bits) from the n-code and * increase instruction pointer by two words. */ static inline const void * nc_fetch_double(const void *iptr, uint32_t *a, uint32_t *b) { const uint32_t *tptr = (const uint32_t *)iptr; KASSERT(ALIGNED_POINTER(iptr, uint32_t)); *a = *tptr++; *b = *tptr++; return tptr; } /* * nc_jump: helper function to jump to specified line (32 bit word) * in the n-code, fetch a word, and update the instruction pointer. */ static inline const void * nc_jump(const void *iptr, int n, u_int *lcount) { /* Detect infinite loops. */ if (__predict_false(*lcount == 0)) { return NULL; } *lcount = *lcount - 1; return (const uint32_t *)iptr + n; } /* * npf_ncode_process: process n-code using data of the specified packet. * * => Argument nbuf (network buffer) is opaque to this function. * => Chain of nbufs (and their data) should be protected from any change. * => N-code memory address and thus instructions should be aligned. * => N-code should be protected from any change. * => Routine prevents from infinite loop. */ int npf_ncode_process(npf_cache_t *npc, const void *ncode, nbuf_t *nbuf, const int layer) { /* N-code instruction pointer. */ const void * i_ptr; /* Virtual registers. */ uint32_t regs[NPF_NREGS]; /* Local, state variables. */ uint32_t d, i, n; npf_addr_t addr; u_int lcount; int cmpval; nbuf_reset(nbuf); i_ptr = ncode; regs[0] = layer; lcount = NPF_LOOP_LIMIT; cmpval = 0; process_next: /* * Loop must always start on instruction, therefore first word * should be an opcode. Most used instructions are checked first. */ i_ptr = nc_fetch_word(i_ptr, &d); if (__predict_true(NPF_CISC_OPCODE(d))) { /* It is a CISC-like instruction. */ goto cisc_like; } /* * RISC-like instructions. * * - ADVR, LW, CMP, CMPR * - BEQ, BNE, BGT, BLT * - RET, TAG, MOVE * - AND, J, INVL */ switch (d) { case NPF_OPCODE_ADVR: i_ptr = nc_fetch_word(i_ptr, &i); /* Register */ KASSERT(i < NPF_NREGS); if (!nbuf_advance(nbuf, regs[i], 0)) { goto fail; } break; case NPF_OPCODE_LW: { void *n_ptr; i_ptr = nc_fetch_double(i_ptr, &n, &i); /* Size, register */ KASSERT(i < NPF_NREGS); KASSERT(n >= sizeof(uint8_t) && n <= sizeof(uint32_t)); n_ptr = nbuf_ensure_contig(nbuf, n); if (nbuf_flag_p(nbuf, NBUF_DATAREF_RESET)) { npf_recache(npc, nbuf); } if (n_ptr == NULL) { goto fail; } memcpy(®s[i], n_ptr, n); break; } case NPF_OPCODE_CMP: i_ptr = nc_fetch_double(i_ptr, &n, &i); /* Value, register */ KASSERT(i < NPF_NREGS); if (n != regs[i]) { cmpval = (n > regs[i]) ? 1 : -1; } else { cmpval = 0; } break; case NPF_OPCODE_CMPR: i_ptr = nc_fetch_double(i_ptr, &n, &i); /* Value, register */ KASSERT(i < NPF_NREGS); if (regs[n] != regs[i]) { cmpval = (regs[n] > regs[i]) ? 1 : -1; } else { cmpval = 0; } break; case NPF_OPCODE_BEQ: i_ptr = nc_fetch_word(i_ptr, &n); /* N-code line */ if (cmpval == 0) goto make_jump; break; case NPF_OPCODE_BNE: i_ptr = nc_fetch_word(i_ptr, &n); if (cmpval != 0) goto make_jump; break; case NPF_OPCODE_BGT: i_ptr = nc_fetch_word(i_ptr, &n); if (cmpval > 0) goto make_jump; break; case NPF_OPCODE_BLT: i_ptr = nc_fetch_word(i_ptr, &n); if (cmpval < 0) goto make_jump; break; case NPF_OPCODE_RET: (void)nc_fetch_word(i_ptr, &n); /* Return value */ return n; case NPF_OPCODE_TAG: i_ptr = nc_fetch_double(i_ptr, &n, &i); /* Key, value */ if (nbuf_add_tag(nbuf, n, i)) { goto fail; } break; case NPF_OPCODE_MOVE: i_ptr = nc_fetch_double(i_ptr, &n, &i); /* Value, register */ KASSERT(i < NPF_NREGS); regs[i] = n; break; case NPF_OPCODE_AND: i_ptr = nc_fetch_double(i_ptr, &n, &i); /* Value, register */ KASSERT(i < NPF_NREGS); regs[i] = n & regs[i]; break; case NPF_OPCODE_J: i_ptr = nc_fetch_word(i_ptr, &n); /* N-code line */ make_jump: i_ptr = nc_jump(i_ptr, n - 2, &lcount); if (__predict_false(i_ptr == NULL)) { goto fail; } break; case NPF_OPCODE_INVL: /* Invalidate all cached data. */ npc->npc_info = 0; break; default: /* Invalid instruction. */ KASSERT(false); } goto process_next; cisc_like: /* * CISC-like instructions. */ switch (d) { case NPF_OPCODE_IP4MASK: /* Source/destination, network address, subnet. */ i_ptr = nc_fetch_word(i_ptr, &d); i_ptr = nc_fetch_double(i_ptr, &addr.s6_addr32[0], &n); cmpval = npf_iscached(npc, NPC_IP46) ? npf_match_ipmask(npc, (sizeof(struct in_addr) << 1) | (d & 0x1), &addr, (npf_netmask_t)n) : -1; break; case NPF_OPCODE_IP6MASK: /* Source/destination, network address, subnet. */ i_ptr = nc_fetch_word(i_ptr, &d); i_ptr = nc_fetch_double(i_ptr, &addr.s6_addr32[0], &addr.s6_addr32[1]); i_ptr = nc_fetch_double(i_ptr, &addr.s6_addr32[2], &addr.s6_addr32[3]); i_ptr = nc_fetch_word(i_ptr, &n); cmpval = npf_iscached(npc, NPC_IP46) ? npf_match_ipmask(npc, (sizeof(struct in6_addr) << 1) | (d & 0x1), &addr, (npf_netmask_t)n) : -1; break; case NPF_OPCODE_TABLE: /* Source/destination, NPF table ID. */ i_ptr = nc_fetch_double(i_ptr, &n, &i); cmpval = npf_iscached(npc, NPC_IP46) ? npf_match_table(npc, n, i) : -1; break; case NPF_OPCODE_TCP_PORTS: /* Source/destination, port range. */ i_ptr = nc_fetch_double(i_ptr, &n, &i); cmpval = npf_iscached(npc, NPC_TCP) ? npf_match_tcp_ports(npc, n, i) : -1; break; case NPF_OPCODE_UDP_PORTS: /* Source/destination, port range. */ i_ptr = nc_fetch_double(i_ptr, &n, &i); cmpval = npf_iscached(npc, NPC_UDP) ? npf_match_udp_ports(npc, n, i) : -1; break; case NPF_OPCODE_TCP_FLAGS: /* TCP flags/mask. */ i_ptr = nc_fetch_word(i_ptr, &n); cmpval = npf_iscached(npc, NPC_TCP) ? npf_match_tcpfl(npc, n) : -1; break; case NPF_OPCODE_ICMP4: /* ICMP type/code. */ i_ptr = nc_fetch_word(i_ptr, &n); cmpval = npf_iscached(npc, NPC_ICMP) ? npf_match_icmp4(npc, n) : -1; break; case NPF_OPCODE_ICMP6: /* ICMP type/code. */ i_ptr = nc_fetch_word(i_ptr, &n); cmpval = npf_iscached(npc, NPC_ICMP) ? npf_match_icmp6(npc, n) : -1; break; case NPF_OPCODE_PROTO: i_ptr = nc_fetch_word(i_ptr, &n); cmpval = npf_iscached(npc, NPC_IP46) ? npf_match_proto(npc, n) : -1; break; case NPF_OPCODE_ETHER: /* Source/destination, reserved, ethernet type. */ i_ptr = nc_fetch_word(i_ptr, &d); i_ptr = nc_fetch_double(i_ptr, &n, &i); cmpval = npf_match_ether(nbuf, d, i, ®s[NPF_NREGS - 1]); break; default: /* Invalid instruction. */ KASSERT(false); } goto process_next; fail: /* Failure case. */ return -1; } /* * nc_ptr_check: validate that instruction pointer is not out of range. * If not - advance by number of arguments and fetch specified argument. */ static int nc_ptr_check(uintptr_t *iptr, const void *nc, size_t sz, u_int nargs, uint32_t *val, u_int r) { const uint32_t *tptr = (const uint32_t *)*iptr; u_int i; KASSERT(ALIGNED_POINTER(*iptr, uint32_t)); KASSERT(nargs > 0); if ((uintptr_t)tptr < (uintptr_t)nc) return NPF_ERR_JUMP; if ((uintptr_t)tptr + (nargs * sizeof(uint32_t)) > (uintptr_t)nc + sz) return NPF_ERR_RANGE; for (i = 1; i <= nargs; i++) { if (val && i == r) { *val = *tptr; } tptr++; } *iptr = (uintptr_t)tptr; return 0; } /* * nc_insn_check: validate the instruction and its arguments. */ static int nc_insn_check(const uintptr_t optr, const void *nc, size_t sz, size_t *adv, size_t *jmp, bool *ret) { uintptr_t iptr = optr; uint32_t regidx, val; int error; /* Fetch the instruction code. */ error = nc_ptr_check(&iptr, nc, sz, 1, &val, 1); if (error) return error; regidx = 0; *ret = false; *jmp = 0; /* * RISC-like instructions. */ switch (val) { case NPF_OPCODE_ADVR: error = nc_ptr_check(&iptr, nc, sz, 1, ®idx, 1); break; case NPF_OPCODE_LW: error = nc_ptr_check(&iptr, nc, sz, 1, &val, 1); if (error || val < sizeof(uint8_t) || val > sizeof(uint32_t)) { return error ? error : NPF_ERR_INVAL; } error = nc_ptr_check(&iptr, nc, sz, 1, ®idx, 1); break; case NPF_OPCODE_CMP: error = nc_ptr_check(&iptr, nc, sz, 2, ®idx, 2); break; case NPF_OPCODE_BEQ: case NPF_OPCODE_BNE: case NPF_OPCODE_BGT: case NPF_OPCODE_BLT: error = nc_ptr_check(&iptr, nc, sz, 1, &val, 1); /* Validate jump address. */ goto jmp_check; case NPF_OPCODE_RET: error = nc_ptr_check(&iptr, nc, sz, 1, NULL, 0); *ret = true; break; case NPF_OPCODE_TAG: error = nc_ptr_check(&iptr, nc, sz, 2, NULL, 0); break; case NPF_OPCODE_MOVE: error = nc_ptr_check(&iptr, nc, sz, 2, ®idx, 2); break; case NPF_OPCODE_CMPR: error = nc_ptr_check(&iptr, nc, sz, 1, ®idx, 1); /* Handle first register explicitly. */ if (error || (u_int)regidx < NPF_NREGS) { return error ? error : NPF_ERR_REG; } error = nc_ptr_check(&iptr, nc, sz, 1, ®idx, 1); break; case NPF_OPCODE_AND: error = nc_ptr_check(&iptr, nc, sz, 2, ®idx, 2); break; case NPF_OPCODE_J: error = nc_ptr_check(&iptr, nc, sz, 1, &val, 1); jmp_check: /* * We must check for JMP 0 i.e. to oneself. Pass the jump * address to the caller, it will validate if it is correct. */ if (error == 0 && val == 0) { return NPF_ERR_JUMP; } *jmp = val * sizeof(uint32_t); break; case NPF_OPCODE_INVL: break; /* * CISC-like instructions. */ case NPF_OPCODE_IP4MASK: error = nc_ptr_check(&iptr, nc, sz, 3, &val, 3); if (error) { return error; } if (!val || (val > NPF_MAX_NETMASK && val != NPF_NO_NETMASK)) { return NPF_ERR_INVAL; } break; case NPF_OPCODE_IP6MASK: error = nc_ptr_check(&iptr, nc, sz, 6, &val, 6); if (error) { return error; } if (!val || (val > NPF_MAX_NETMASK && val != NPF_NO_NETMASK)) { return NPF_ERR_INVAL; } break; case NPF_OPCODE_TABLE: error = nc_ptr_check(&iptr, nc, sz, 2, NULL, 0); break; case NPF_OPCODE_TCP_PORTS: error = nc_ptr_check(&iptr, nc, sz, 2, NULL, 0); break; case NPF_OPCODE_UDP_PORTS: error = nc_ptr_check(&iptr, nc, sz, 2, NULL, 0); break; case NPF_OPCODE_TCP_FLAGS: error = nc_ptr_check(&iptr, nc, sz, 1, NULL, 0); break; case NPF_OPCODE_ICMP4: case NPF_OPCODE_ICMP6: error = nc_ptr_check(&iptr, nc, sz, 1, NULL, 0); break; case NPF_OPCODE_PROTO: error = nc_ptr_check(&iptr, nc, sz, 1, NULL, 0); break; case NPF_OPCODE_ETHER: error = nc_ptr_check(&iptr, nc, sz, 3, NULL, 0); break; default: /* Invalid instruction. */ return NPF_ERR_OPCODE; } if (error) { return error; } if ((u_int)regidx >= NPF_NREGS) { /* Invalid register. */ return NPF_ERR_REG; } *adv = iptr - optr; return 0; } /* * nc_jmp_check: validate that jump address points to the instruction. * Loop from the begining of n-code until we hit jump address or error. */ static inline int nc_jmp_check(const void *nc, size_t sz, const uintptr_t jaddr) { uintptr_t iaddr = (uintptr_t)nc; int error; KASSERT(iaddr != jaddr); do { size_t _jmp, adv; bool _ret; error = nc_insn_check(iaddr, nc, sz, &adv, &_jmp, &_ret); if (error) { break; } iaddr += adv; } while (iaddr != jaddr); return error; } /* * npf_ncode_validate: validate n-code. * Performs the following operations: * * - Checks that each instruction is valid (i.e. existing opcode). * - Validates registers i.e. that their indexes are correct. * - Checks that jumps are within n-code and to the instructions. * - Checks that n-code returns, and processing is within n-code memory. */ int npf_ncode_validate(const void *nc, size_t sz, int *errat) { const uintptr_t nc_end = (uintptr_t)nc + sz; uintptr_t iptr = (uintptr_t)nc; int error; bool ret; do { size_t jmp, adv; /* Validate instruction and its arguments. */ error = nc_insn_check(iptr, nc, sz, &adv, &jmp, &ret); if (error) break; /* If jumping, check that address points to the instruction. */ if (jmp && nc_jmp_check(nc, sz, iptr + jmp)) { /* Note: the actual error might be different. */ return NPF_ERR_JUMP; } /* Advance and check for the end of n-code memory block. */ iptr += adv; } while (iptr != nc_end); if (!error) { error = ret ? 0 : NPF_ERR_RANGE; } *errat = (iptr - (uintptr_t)nc) / sizeof(uint32_t); return error; }