Files
ocserv/tests/test-cookie-sid-overflow
Nikos Mavrogiannopoulos 8aa1022696 tests: add reproducer for oversized webvpncontext=/webvpn= cookie overflow
Sending a Cookie header with a webvpncontext= value whose base64-decoded
length exceeds SID_SIZE crashes the worker with SIGSEGV before it can
send an HTTP response.  The test detects both pre- and post-response
crashes: a connection reset (HTTP 000) and a "died with sigsegv" entry
in the server log respectively.

Relates: #719

Signed-off-by: Nikos Mavrogiannopoulos <n.mavrogiannopoulos@gmail.com>
2026-06-07 08:29:52 +02:00

132 lines
5.1 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
#
# Copyright (C) 2026 Nikos Mavrogiannopoulos
#
# This file is part of ocserv.
#
# ocserv is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at
# your option) any later version.
#
# ocserv is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ocserv; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Regression test for issue #719: heap buffer overflow via oversized
# webvpncontext= and webvpn= cookie values.
#
# Both cookies are decoded into fixed-size buffers (SID_SIZE = 32 bytes).
# A pre-decode bounds check must reject values whose decoded size exceeds the
# buffer before any decoding takes place. Without the check the decoder
# writes attacker-controlled data past the end of the buffer, corrupting
# worker_st and crashing the worker (connection reset: HTTP status "000").
#
# Attack surface: remote, unauthenticated, via Cookie header.
#
# Scenario 1 webvpncontext= (vulnerable before fix):
# nlen = BASE64_DECODE_LENGTH(tmplen) can be >> SID_SIZE=32. Without a
# pre-check the decoder writes up to nlen bytes into ws->sid (32 bytes),
# overwriting the rest of worker_st. The worker segfaults before it can
# send an HTTP response.
#
# Scenario 2 webvpn= (existing guard):
# The webvpn= branch already performs the pre-check and decodes into the
# large ws->buffer scratch area. This scenario verifies the guard holds.
SERV="${SERV:-../src/ocserv}"
srcdir=${srcdir:-.}
NO_NEED_ROOT=1
. "$(dirname "$0")"/common.sh
eval "${GETPORT}"
if ! which curl >/dev/null 2>&1; then
echo "curl not found, skipping test"
exit 77
fi
CURL_OUT="test-cookie-sid-overflow.$$.curl"
SRVLOG="test-cookie-sid-overflow.$$.log"
finish() {
test -n "${PID}" && kill "${PID}" >/dev/null 2>&1
rm -f "${CURL_OUT}" "${SRVLOG}" "${CONFIG}"
test -n "${SOCKDIR}" && rm -rf "${SOCKDIR}"
}
trap finish EXIT
echo "Testing oversized cookie bounds checking (issue #719)..."
update_config test-user-cert.config
# Launch with output captured so we can grep for worker crash messages.
# The main process logs "Child NNN died with sigsegv" when a worker crashes,
# which lets us detect crashes that happen after the HTTP response is sent
# (those would not be visible via curl's connection-reset path alone).
LD_PRELOAD=libsocket_wrapper.so:libuid_wrapper.so UID_WRAPPER=1 UID_WRAPPER_ROOT=1 \
"${SERV}" -d 1 -f -c "${CONFIG}" >"${SRVLOG}" 2>&1 &
PID=$!
wait_server $PID
# 750 null bytes → 1000-char base64 string, decoding to ~750 bytes.
# This is far above SID_SIZE=32 (+8 slack), so the pre-decode bounds check
# must reject it before any write to ws->sid or ws->cookie occurs.
OVERSIZED=$(dd if=/dev/zero bs=750 count=1 2>/dev/null | base64 | tr -d '\n')
# curl_get: unauthenticated GET; extra options via "$@".
# Prints the HTTP status code; response body goes to CURL_OUT.
curl_get() {
LD_PRELOAD=libsocket_wrapper.so \
curl --silent --insecure --max-time 10 \
--user-agent "AnyConnect Linux_64 4.10" \
--write-out "%{http_code}" \
"$@" -o "${CURL_OUT}"
}
# check_no_crash: verify neither the connection was reset (pre-response crash)
# nor the server log records a worker SIGSEGV (post-response crash).
# A brief sleep lets the main process write the SIGCHLD log entry before we
# grep, since the worker may die concurrently with curl returning.
check_no_crash() {
local desc="$1" http_code="$2"
if [ "${http_code}" = "000" ]; then
cat "${SRVLOG}"
fail "${PID}" "Worker crashed on ${desc} (connection reset before response)"
fi
sleep 1
if grep -q "died with sig\|sigsegv\|SIGSEGV" "${SRVLOG}"; then
cat "${SRVLOG}"
fail "${PID}" "Worker crashed on ${desc} (SIGSEGV after response)"
fi
echo " * ok (HTTP ${http_code}, no worker crash)"
}
# --- 1. webvpncontext= (the vulnerable path before the fix) ---------------
# Without the fix: oc_base64_decode() writes ~750 bytes into ws->sid (32 bytes),
# corrupts worker_st, and the worker segfaults before sending any response →
# curl sees a connection reset (HTTP "000").
# With the fix: the pre-check (nlen > SID_SIZE+8 → return) rejects the
# oversized value and the server returns a normal HTTP status.
echo -n " * Oversized webvpncontext= cookie... "
http_code=$(curl_get -H "Cookie: webvpncontext=${OVERSIZED}" \
"https://${ADDRESS}:${PORT}/")
check_no_crash "oversized webvpncontext= cookie" "${http_code}"
# --- 2. webvpn= (existing guard, should already be safe) ------------------
# The webvpn= branch performs the pre-check and decodes into ws->buffer (16 KB)
# rather than directly into ws->cookie. Verify the guard is intact.
echo -n " * Oversized webvpn= cookie... "
http_code=$(curl_get -H "Cookie: webvpn=${OVERSIZED}" \
"https://${ADDRESS}:${PORT}/")
check_no_crash "oversized webvpn= cookie" "${http_code}"
echo "All cookie overflow tests passed."
exit 0