mirror of
https://github.com/systemd/systemd.git
synced 2026-08-11 01:25:28 +00:00
resolved: honour per-link DNSOverTLS=yes for certificate verification
dnstls_stream_connect_tls() reads the manager-wide dns_over_tls_mode
field directly, so when the global mode was 'no' or 'opportunistic' but
a link was configured with strict DNSOverTLS=yes, the DoT connection
was established without SSL_VERIFY_PEER or hostname/IP checks.
Use dns_server_get_dns_over_tls_mode() which honours the per-link
override.
(cherry picked from commit 052ff5fb5d)
This commit is contained in:
@@ -92,7 +92,7 @@ int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server) {
|
||||
return -EIO;
|
||||
sym_SSL_set_bio(s, TAKE_PTR(rb), TAKE_PTR(wb));
|
||||
|
||||
if (server->manager->dns_over_tls_mode == DNS_OVER_TLS_YES) {
|
||||
if (dns_server_get_dns_over_tls_mode(server) == DNS_OVER_TLS_YES) {
|
||||
X509_VERIFY_PARAM *v;
|
||||
|
||||
sym_SSL_set_verify(s, SSL_VERIFY_PEER, NULL);
|
||||
|
||||
@@ -1498,6 +1498,114 @@ EOF
|
||||
grep -qF "1.2.3.4" "$RUN_OUT"
|
||||
}
|
||||
|
||||
# Regression test for CVE/strict-DoT verification: when the global
|
||||
# DNSOverTLS= is "no" or "opportunistic" but a specific link is configured
|
||||
# with DNSOverTLS=yes (per-link strict mode), resolved must still enable
|
||||
# OpenSSL peer certificate verification and host/IP name checking on that
|
||||
# link. Previously dnstls_stream_connect_tls() looked at the manager-wide
|
||||
# field directly instead of dns_server_get_dns_over_tls_mode(), so a
|
||||
# self-signed/untrusted DoT server would be silently accepted on a strict
|
||||
# link, allowing trivial MITM.
|
||||
testcase_dot_strict_per_link_verify() {
|
||||
if ! command -v openssl >/dev/null; then
|
||||
echo "openssl not found, skipping per-link strict DoT test"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local certdir s_server_pid="" link_addr="10.123.99.1"
|
||||
local journal_cursor
|
||||
|
||||
certdir="$(mktemp -d)"
|
||||
|
||||
# shellcheck disable=SC2317,SC2329
|
||||
cleanup() {
|
||||
if [[ -n "$s_server_pid" ]] && kill -0 "$s_server_pid" 2>/dev/null; then
|
||||
kill "$s_server_pid" || :
|
||||
wait "$s_server_pid" 2>/dev/null || :
|
||||
fi
|
||||
rm -f /run/systemd/resolved.conf.d/95-strict-dot-test.conf
|
||||
rm -f /run/systemd/network/10-dns3-strict-dot.netdev
|
||||
rm -f /run/systemd/network/10-dns3-strict-dot.network
|
||||
networkctl reload || :
|
||||
ip link del dns3-sdot 2>/dev/null || :
|
||||
systemctl reload systemd-resolved.service || :
|
||||
rm -rf "$certdir" "$journal_cursor"
|
||||
}
|
||||
|
||||
trap cleanup RETURN ERR
|
||||
|
||||
# Self-signed cert for CN=strict-dot.test, with a SAN matching the link
|
||||
# IP. It is intentionally NOT in the system CA store, so peer
|
||||
# verification (when actually enabled) MUST reject it.
|
||||
openssl req -x509 -newkey rsa:2048 -nodes \
|
||||
-keyout "$certdir/key.pem" -out "$certdir/cert.pem" \
|
||||
-subj "/CN=strict-dot.test" \
|
||||
-addext "subjectAltName=IP:${link_addr},DNS:strict-dot.test" \
|
||||
-days 1
|
||||
|
||||
# Dummy interface that hosts both the fake DoT server and the strict
|
||||
# client-side configuration.
|
||||
{
|
||||
echo "[NetDev]"
|
||||
echo "Name=dns3-sdot"
|
||||
echo "Kind=dummy"
|
||||
} >/run/systemd/network/10-dns3-strict-dot.netdev
|
||||
{
|
||||
echo "[Match]"
|
||||
echo "Name=dns3-sdot"
|
||||
echo ""
|
||||
echo "[Network]"
|
||||
echo "IPv6AcceptRA=no"
|
||||
echo "Address=${link_addr}/24"
|
||||
echo "DNS=${link_addr}"
|
||||
echo "DNSOverTLS=yes"
|
||||
echo "DNSSEC=no"
|
||||
} >/run/systemd/network/10-dns3-strict-dot.network
|
||||
|
||||
# Force the manager-wide DoT mode to "no". Before the fix this would
|
||||
# cause SSL_VERIFY_PEER to NOT be set, even though the link is strict.
|
||||
{
|
||||
echo "[Resolve]"
|
||||
echo "DNSOverTLS=no"
|
||||
} >/run/systemd/resolved.conf.d/95-strict-dot-test.conf
|
||||
|
||||
networkctl reload
|
||||
networkctl reconfigure dns3-sdot
|
||||
/usr/lib/systemd/systemd-networkd-wait-online --timeout=30 --interface=dns3-sdot --dns
|
||||
|
||||
systemctl reload systemd-resolved.service
|
||||
|
||||
assert_eq "$(resolvectl --json=short dnsovertls dns3-sdot | jq -rc '.[0].dnsOverTLS')" 'yes'
|
||||
|
||||
# Start an unauthenticated TLS listener using the self-signed cert.
|
||||
# -naccept lets resolved reconnect a few times during retries.
|
||||
openssl s_server \
|
||||
-accept "${link_addr}:853" \
|
||||
-cert "$certdir/cert.pem" -key "$certdir/key.pem" \
|
||||
-quiet -naccept 8 </dev/null >/dev/null 2>&1 &
|
||||
s_server_pid=$!
|
||||
|
||||
timeout 10s bash -c "until ss -lnt 'sport = :853' | grep -F ${link_addr} >/dev/null; do sleep 0.2; done"
|
||||
|
||||
journal_cursor="$(mktemp)"
|
||||
journalctl -n0 -q --cursor-file="$journal_cursor"
|
||||
|
||||
# The query must fail: a strict-DoT link must refuse an untrusted cert.
|
||||
set +e
|
||||
timeout 15s resolvectl query -i dns3-sdot strict-dot.test
|
||||
local rc=$?
|
||||
set -e
|
||||
if [[ $rc -eq 0 ]]; then
|
||||
echo "resolvectl query against untrusted DoT server on strict link succeeded, should have failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Ensure the test failed for the right reason
|
||||
journalctl --sync
|
||||
journalctl -u systemd-resolved.service --cursor-file="$journal_cursor" \
|
||||
--grep "Failed to invoke SSL_do_handshake.*(certificate verify failed|self[- ]signed certificate|unable to get local issuer)"
|
||||
}
|
||||
|
||||
testcase_static_record() {
|
||||
mkdir -p /run/systemd/resolve/static.d/
|
||||
cat >/run/systemd/resolve/static.d/statictest.rr <<EOF
|
||||
|
||||
Reference in New Issue
Block a user