diff --git a/.github/workflows/test_offline.yaml b/.github/workflows/test_offline.yaml index 64e8617..8814877 100644 --- a/.github/workflows/test_offline.yaml +++ b/.github/workflows/test_offline.yaml @@ -40,7 +40,13 @@ jobs: if: matrix.tls_backend == 'openssl' run: sudo apt-get install -y libssl-dev - name: disable network - run: sudo sh -c 'echo > /etc/resolv.conf' + run: | + sudo iptables -A OUTPUT -o lo -j ACCEPT + sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT + sudo iptables -A OUTPUT -j REJECT + sudo ip6tables -A OUTPUT -o lo -j ACCEPT + sudo ip6tables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT + sudo ip6tables -A OUTPUT -j REJECT - name: build and run tests (OpenSSL) if: matrix.tls_backend == 'openssl' run: cd test && make @@ -49,3 +55,8 @@ jobs: - name: build and run tests (No TLS) if: matrix.tls_backend == 'no-tls' run: cd test && make test_no_tls && ./test_no_tls + - name: restore network + if: always() + run: | + sudo iptables -F OUTPUT + sudo ip6tables -F OUTPUT diff --git a/test/test.cc b/test/test.cc index 7d16ddb..010b9de 100644 --- a/test/test.cc +++ b/test/test.cc @@ -9114,29 +9114,31 @@ TEST(ClientVulnerabilityTest, ZipBombWithoutContentLength) { signal(SIGPIPE, SIG_IGN); #endif - auto server_thread = std::thread([&compressed] { - auto srv = ::socket(AF_INET, SOCK_STREAM, 0); - default_socket_options(srv); - detail::set_socket_opt_time(srv, SOL_SOCKET, SO_RCVTIMEO, 5, 0); - detail::set_socket_opt_time(srv, SOL_SOCKET, SO_SNDTIMEO, 5, 0); + // Set up the listening socket in the main thread so the server is guaranteed + // to be ready before the client connects (eliminates race condition). + auto srv = ::socket(AF_INET, SOCK_STREAM, 0); + default_socket_options(srv); + detail::set_socket_opt_time(srv, SOL_SOCKET, SO_RCVTIMEO, 5, 0); + detail::set_socket_opt_time(srv, SOL_SOCKET, SO_SNDTIMEO, 5, 0); - sockaddr_in addr{}; - addr.sin_family = AF_INET; - addr.sin_port = htons(PORT + 3); - ::inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT + 3); + ::inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); - int opt = 1; - ::setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, + int opt = 1; + ::setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, #ifdef _WIN32 - reinterpret_cast(&opt), + reinterpret_cast(&opt), #else - &opt, + &opt, #endif - sizeof(opt)); + sizeof(opt)); - ::bind(srv, reinterpret_cast(&addr), sizeof(addr)); - ::listen(srv, 1); + ASSERT_EQ(0, ::bind(srv, reinterpret_cast(&addr), sizeof(addr))); + ASSERT_EQ(0, ::listen(srv, 1)); + auto server_thread = std::thread([&compressed, srv] { sockaddr_in cli_addr{}; socklen_t cli_len = sizeof(cli_addr); auto cli = ::accept(srv, reinterpret_cast(&cli_addr), &cli_len); @@ -9180,10 +9182,12 @@ TEST(ClientVulnerabilityTest, ZipBombWithoutContentLength) { detail::close_socket(cli); } - detail::close_socket(srv); }); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + auto se = detail::scope_exit([&] { + detail::close_socket(srv); + server_thread.join(); + }); size_t total_decompressed = 0; @@ -9204,8 +9208,6 @@ TEST(ClientVulnerabilityTest, ZipBombWithoutContentLength) { } } - server_thread.join(); - // The decompressed size must be capped by payload_max_length. Without // protection, the client would decompress the full 10MB from a tiny // compressed payload, enabling a zip bomb DoS attack.