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>
This commit is contained in:
Nikos Mavrogiannopoulos
2026-06-07 08:29:52 +02:00
parent 235882cebf
commit 8aa1022696
2 changed files with 132 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
#!/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