diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/README.md | 23 | ||||
| -rw-r--r-- | tests/kernel-module-install/test.sh | 31 | ||||
| -rw-r--r-- | tests/unit/dhcpv6_safeguards_test.c | 69 | ||||
| -rw-r--r-- | tests/unit/run_safeguards.sh | 6 |
4 files changed, 117 insertions, 12 deletions
diff --git a/tests/README.md b/tests/README.md index 870cf8c8..44bae6a4 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,6 +2,11 @@ These tests are done for Ubuntu and Debian distros. Please use latest stable Debian or Ubuntu to run the tests. +The commands in this guide require CMake/CTest 3.20 or newer, including +`ctest --test-dir`. The standalone kernel-module installation script requires +CMake 3.15 or newer for `cmake --install`. These command requirements are +separate from the project's minimum CMake version of 3.10. + ## Focused unit tests The standalone unit tests compile production callbacks with small test fixtures @@ -16,18 +21,14 @@ These tests do not require installed kernel modules or root privileges. The sanitizer runtime needs an environment that permits its process inspection. The runner has six suites: `dhcpv6`, `radius`, `vrf`, `lcp`, `mempool`, and -`log_line`. With no names it attempts all six. The DHCPv6 fixture currently -refers to the removed `conf_dns_count` variable, and the runner does not link -the `ipv6_dns.c` implementation now used by the reply builder. That fixture -and its build command must be updated before a full run can pass. - -The other five suites can be run explicitly while that regression is being fixed: +`log_line`. With no names it runs all six: ```bash -sh tests/unit/run_safeguards.sh build radius vrf lcp mempool log_line +sh tests/unit/run_safeguards.sh build ``` -A successful subset run does not validate the DHCPv6 safeguards. The +The DHCPv6 suite checks bounded DNS replies and failure propagation when +non-truncatable options exhaust reply space. The [integration validation record](../docs/ng40-safeguards-review.md#validation) describes results from the revision at which those safeguards were introduced. @@ -37,6 +38,9 @@ The kernel module installation regression test is rootless and does not need kernel headers. It uses a fake kernel build tree to verify that `ipoe`, `vlan_mon`, and `ppposeq` are installed from their actual build directories, honor `DESTDIR`, and make installation fail when `modules_install` fails. +It also verifies that Debian and CentOS packaging configurations include +`ppposeq.ko` with the standalone driver package name, and omit it when the +build flag is disabled. Run it through CTest after configuring the project: @@ -69,7 +73,8 @@ sudo apt install iproute2 ppp pppoe isc-dhcp-client ``` Install the build dependencies described in the project README, including -kernel headers and Lua development files for the options below. Run these +kernel headers and Lua 5.1 development files (`liblua5.1-0-dev` on Debian and +Ubuntu) for `LUA=TRUE` below. Run these commands from the repository root. Install accel-pppd (make install or use distro package). Do not run accel-pppd using systemd or other supervisors diff --git a/tests/kernel-module-install/test.sh b/tests/kernel-module-install/test.sh index 1fbfd623..2f3ec579 100644 --- a/tests/kernel-module-install/test.sh +++ b/tests/kernel-module-install/test.sh @@ -36,3 +36,34 @@ if FAIL_MODULES_INSTALL=1 DESTDIR="${work_dir}/failed-stage" \ fi grep -q 'Failed to install kernel module' "${work_dir}/failed-install.log" + +# Packaging configurations bypass modules_install and must install the .ko +# explicitly, including when PPPoSEQ is built as a standalone driver package. +for package_type in Debian12 Centos9; do + package_build="${work_dir}/package-${package_type}" + package_stage="${work_dir}/stage-${package_type}" + "${cmake_command}" \ + -S "${source_dir}" \ + -B "${package_build}" \ + -DBUILD_TESTING=OFF \ + -DBUILD_DRIVER_ONLY=TRUE \ + -DBUILD_PPPOSEQ_DRIVER=TRUE \ + -DIGNORE_GIT=TRUE \ + -DCPACK_TYPE="${package_type}" \ + -DMODULES_KDIR=test-kernel \ + -DCENTOS_KDIR=test-kernel \ + -DKDIR="${script_dir}" + "${cmake_command}" --build "${package_build}" + DESTDIR="${package_stage}" \ + "${cmake_command}" --install "${package_build}" + test -f "${package_stage}/lib/modules/test-kernel/extra/ppposeq.ko" + grep -q 'set(CPACK_PACKAGE_NAME "accel-ppp-ppposeq-kmod")' \ + "${package_build}/CPackConfig.cmake" + + # A stale build artifact must not be installed after the flag is disabled. + "${cmake_command}" -S "${source_dir}" -B "${package_build}" \ + -DBUILD_PPPOSEQ_DRIVER=FALSE + DESTDIR="${package_stage}-disabled" \ + "${cmake_command}" --install "${package_build}" + test ! -e "${package_stage}-disabled/lib/modules/test-kernel/extra/ppposeq.ko" +done diff --git a/tests/unit/dhcpv6_safeguards_test.c b/tests/unit/dhcpv6_safeguards_test.c index 63692ee3..0938047c 100644 --- a/tests/unit/dhcpv6_safeguards_test.c +++ b/tests/unit/dhcpv6_safeguards_test.c @@ -14,6 +14,7 @@ static size_t response_len; void log_warn(const char *fmt, ...) {} void log_emerg(const char *fmt, ...) {} void log_ppp_error(const char *fmt, ...) {} +void log_ppp_warn(const char *fmt, ...) {} void log_ppp_info2(const char *fmt, ...) {} int ip6addr_add_peer(int i, struct in6_addr *a, struct in6_addr *p) { return 0; } int ip6route_add(int i, const struct in6_addr *a, int p, const struct in6_addr *g, int m, uint32_t prio, const char *v) { return 0; } @@ -60,10 +61,36 @@ static size_t relay(unsigned char *buf, size_t len) return len + hdrlen + 4; } +static void check_dns_reply(unsigned int requested) +{ + struct dhcpv6_packet *pkt; + struct dhcpv6_option *opt; + unsigned int count = 0; + size_t offset, len; + + assert(sent == 1); + assert(response_len <= sizeof(response)); + pkt = dhcpv6_packet_parse(response, response_len); + assert(pkt); + list_for_each_entry(opt, &pkt->opt_list, entry) { + if (ntohs(opt->hdr->code) != D6_OPTION_DNS_SERVERS) + continue; + len = ntohs(opt->hdr->len); + assert(len && len % sizeof(struct in6_addr) == 0); + for (offset = 0; offset < len; offset += sizeof(struct in6_addr)) + assert(!memcmp(opt->hdr->data + offset, &in6addr_loopback, + sizeof(in6addr_loopback))); + count += len / sizeof(struct in6_addr); + } + assert(count > 0 && count < requested); + dhcpv6_packet_free(pkt); +} + int main(void) { unsigned char buf[16000], oro[4000], name[256]; const unsigned char valid_name[] = {2, 'g', 'w', 0}; + unsigned char domain[] = {1, 'x', 0}; struct dhcpv6_packet *pkt, *reply; struct dhcpv6_option *opt; struct ap_session ses = {0}; @@ -132,8 +159,27 @@ int main(void) } assert(!dhcpv6_packet_parse(buf, len)); - /* An ORO can amplify a small request into more than 4096 response bytes. */ - conf_dns_count = 1; + /* A large DNS list is truncated to the reply buffer capacity. */ + assert(ipv6_dns_reserve(&conf_dns, 300) == 0); + for (i = 0; i < 300; i++) + conf_dns.addr[i] = in6addr_loopback; + conf_dns.count = 300; + u_write_be16(oro, D6_OPTION_DNS_SERVERS); + len = request(buf); + len += option(buf + len, D6_OPTION_ORO, oro, 2); + pkt = dhcpv6_packet_parse(buf, len); + assert(pkt); + pkt->ses = &ses; + sent = 0; + dhcpv6_send_reply(pkt, &pd, D6_REPLY); + check_dns_reply(conf_dns.count); + sent = 0; + dhcpv6_send_reply2(pkt, &pd, D6_REPLY); + check_dns_reply(conf_dns.count); + dhcpv6_packet_free(pkt); + + /* Repeated ORO entries leave no room for the final Preference option. */ + conf_dns.count = 1; for (i = 0; i < sizeof(oro); i += 2) u_write_be16(oro + i, D6_OPTION_DNS_SERVERS); len = request(buf); @@ -147,6 +193,25 @@ int main(void) dhcpv6_send_reply2(pkt, &pd, D6_REPLY); assert(!sent); dhcpv6_packet_free(pkt); + free(conf_dns.addr); + memset(&conf_dns, 0, sizeof(conf_dns)); + + /* Domain-list options cannot be shortened; exhaustion drops the reply. */ + conf_dnssl = domain; + conf_dnssl_size = sizeof(domain); + for (i = 0; i < sizeof(oro); i += 2) + u_write_be16(oro + i, D6_OPTION_DOMAIN_LIST); + len = request(buf); + len += option(buf + len, D6_OPTION_ORO, oro, sizeof(oro)); + pkt = dhcpv6_packet_parse(buf, len); + assert(pkt); + pkt->ses = &ses; + sent = 0; + dhcpv6_send_reply(pkt, &pd, D6_REPLY); + assert(!sent); + dhcpv6_send_reply2(pkt, &pd, D6_REPLY); + assert(!sent); + dhcpv6_packet_free(pkt); puts("DHCPv6 safeguards: PASS"); return 0; } diff --git a/tests/unit/run_safeguards.sh b/tests/unit/run_safeguards.sh index 7f18c492..001cd19e 100644 --- a/tests/unit/run_safeguards.sh +++ b/tests/unit/run_safeguards.sh @@ -13,11 +13,15 @@ for name in $names; do # LCP's global layer table retains the whole daemon under ASan; its test # checks wire lengths with UBSan. The packet/VRF tests also use ASan. if [ "$name" = lcp ]; then extra=-fno-sanitize=address; fi + set -- "tests/unit/${name}_safeguards_test.c" + if [ "$name" = dhcpv6 ]; then + set -- "$@" accel-pppd/ipv6_dns.c + fi ${CC:-cc} -O1 -g -Wall -Wno-unused-function -Wno-unused-result -D_GNU_SOURCE -DAP_SESSIONID_LEN=16 \ -DOPENSSL_API_COMPAT=0x10100000L -fno-strict-aliasing \ -ffunction-sections -fdata-sections -Wl,--gc-sections \ -fsanitize=address,undefined -fno-sanitize-recover=all $extra \ -I "$build" -I accel-pppd -I accel-pppd/include -I accel-pppd/triton \ - -o "$out/$name" "tests/unit/${name}_safeguards_test.c" -lcrypto -lpthread + -o "$out/$name" "$@" -lcrypto -lpthread "$out/$name" done |
