mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-28 04:34:54 -04:00
[CVE-2026-3591] sec: usr: Fix a stack use-after-return flaw in SIG(0) handling code
A stack use-after-return flaw in SIG(0) handling code could enable ACL bypass and/or assertion failures in certain circumstances. This flaw has been fixed. ISC would like to thank Mcsky23 for bringing this vulnerability to our attention. Closes isc-projects/bind9#5754 Merge branch '5754-stack-use-after-free-sig0' into 'v9.21.20-release' See merge request isc-private/bind9!920
This commit is contained in:
commit
c64392c731
4 changed files with 191 additions and 10 deletions
|
|
@ -24,6 +24,8 @@
|
|||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <dns/acl.h>
|
||||
|
||||
#ifdef HAVE_DNSTAP
|
||||
#include <fstrm.h>
|
||||
#endif
|
||||
|
|
@ -280,10 +282,10 @@ struct zonelistentry {
|
|||
* asynchronously.
|
||||
*/
|
||||
typedef struct matching_view_ctx {
|
||||
isc_netaddr_t *srcaddr;
|
||||
isc_netaddr_t *destaddr;
|
||||
isc_netaddr_t srcaddr;
|
||||
isc_netaddr_t destaddr;
|
||||
dns_message_t *message;
|
||||
dns_aclenv_t *env;
|
||||
dns_aclenv_t *aclenv;
|
||||
ns_server_t *sctx;
|
||||
isc_loop_t *loop;
|
||||
isc_job_cb cb;
|
||||
|
|
@ -9299,6 +9301,8 @@ get_matching_view_done(void *cbarg) {
|
|||
|
||||
mvctx->cb(mvctx->cbarg);
|
||||
|
||||
dns_aclenv_detach(&mvctx->aclenv);
|
||||
|
||||
if (mvctx->quota_result == ISC_R_SUCCESS) {
|
||||
isc_quota_release(&mvctx->sctx->sig0checksquota);
|
||||
}
|
||||
|
|
@ -9340,10 +9344,10 @@ get_matching_view_continue(void *cbarg, isc_result_t result) {
|
|||
tsig = dns_tsigkey_identity(mvctx->message->tsigkey);
|
||||
}
|
||||
|
||||
if (dns_acl_allowed(mvctx->srcaddr, tsig, mvctx->view->matchclients,
|
||||
mvctx->env) &&
|
||||
dns_acl_allowed(mvctx->destaddr, tsig,
|
||||
mvctx->view->matchdestinations, mvctx->env) &&
|
||||
if (dns_acl_allowed(&mvctx->srcaddr, tsig, mvctx->view->matchclients,
|
||||
mvctx->aclenv) &&
|
||||
dns_acl_allowed(&mvctx->destaddr, tsig,
|
||||
mvctx->view->matchdestinations, mvctx->aclenv) &&
|
||||
!(mvctx->view->matchrecursiveonly &&
|
||||
(mvctx->message->flags & DNS_MESSAGEFLAG_RD) == 0))
|
||||
{
|
||||
|
|
@ -9415,9 +9419,9 @@ get_matching_view(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr,
|
|||
|
||||
matching_view_ctx_t *mvctx = isc_mem_get(message->mctx, sizeof(*mvctx));
|
||||
*mvctx = (matching_view_ctx_t){
|
||||
.srcaddr = srcaddr,
|
||||
.destaddr = destaddr,
|
||||
.env = env,
|
||||
.srcaddr = *srcaddr,
|
||||
.destaddr = *destaddr,
|
||||
.aclenv = dns_aclenv_ref(env),
|
||||
.cb = cb,
|
||||
.cbarg = cbarg,
|
||||
.sigresult = sigresult,
|
||||
|
|
|
|||
41
bin/tests/system/sig0/ns1/named.conf.j2
Normal file
41
bin/tests/system/sig0/ns1/named.conf.j2
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* See the COPYRIGHT file distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*/
|
||||
|
||||
options {
|
||||
query-source address 10.53.0.1;
|
||||
notify-source 10.53.0.1;
|
||||
transfer-source 10.53.0.1;
|
||||
port @PORT@;
|
||||
pid-file "named.pid";
|
||||
listen-on { 10.53.0.1; };
|
||||
listen-on-v6 { none; };
|
||||
recursion no;
|
||||
notify no;
|
||||
};
|
||||
|
||||
key rndc_key {
|
||||
secret "1234abcd8765";
|
||||
algorithm @DEFAULT_HMAC@;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
||||
view "v1" {
|
||||
match-clients { any; };
|
||||
zone "." {
|
||||
type hint;
|
||||
file "/dev/null";
|
||||
};
|
||||
};
|
||||
17
bin/tests/system/sig0/setup.sh
Normal file
17
bin/tests/system/sig0/setup.sh
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
# shellcheck source=conf.sh
|
||||
. ../conf.sh
|
||||
|
||||
key=$($KEYGEN -q -a RSASHA256 -b 2048 sig0.)
|
||||
119
bin/tests/system/sig0/tests_sig0.py
Normal file
119
bin/tests/system/sig0/tests_sig0.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
import base64
|
||||
import glob
|
||||
import os
|
||||
import struct
|
||||
import time
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
||||
|
||||
import dns.flags
|
||||
import dns.message
|
||||
import dns.name
|
||||
import dns.rdata
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.renderer
|
||||
import dns.rrset
|
||||
|
||||
import isctest
|
||||
|
||||
|
||||
def load_bind_private_key(filename):
|
||||
"""Parses a BIND 9 .private key file."""
|
||||
with open(filename, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
data = {}
|
||||
for line in lines:
|
||||
if ":" in line:
|
||||
key, value = line.split(":", 1)
|
||||
data[key.strip()] = value.strip()
|
||||
|
||||
def b64int(k):
|
||||
return int.from_bytes(base64.b64decode(data[k]), byteorder="big")
|
||||
|
||||
rsa_key = rsa.RSAPrivateNumbers(
|
||||
p=b64int("Prime1"),
|
||||
q=b64int("Prime2"),
|
||||
d=b64int("PrivateExponent"),
|
||||
dmp1=b64int("Exponent1"),
|
||||
dmq1=b64int("Exponent2"),
|
||||
iqmp=b64int("Coefficient"),
|
||||
public_numbers=rsa.RSAPublicNumbers(
|
||||
e=b64int("PublicExponent"), n=b64int("Modulus")
|
||||
),
|
||||
).private_key(default_backend())
|
||||
|
||||
return rsa_key
|
||||
|
||||
|
||||
def make_sig0_query(key_file, key_name_str):
|
||||
private_key = load_bind_private_key(key_file)
|
||||
|
||||
qname = dns.name.from_text(".")
|
||||
query = dns.message.make_query(qname, dns.rdatatype.SOA)
|
||||
query.flags |= dns.flags.RD
|
||||
|
||||
# Render message to bytes (needed for signing)
|
||||
renderer = dns.renderer.Renderer()
|
||||
query.to_wire(renderer)
|
||||
msg_bytes = renderer.get_wire()
|
||||
|
||||
# SIG(0) Constants
|
||||
basename = os.path.basename(key_file)
|
||||
key_tag = int(basename.split("+")[2].split(".")[0])
|
||||
|
||||
now = int(time.time())
|
||||
expiration = now + 3600
|
||||
inception = now - 3600
|
||||
signer_name = dns.name.from_text(key_name_str)
|
||||
|
||||
# Construct SIG RDATA header (0=SIG(0), 8=RSASHA256, 0=Labels)
|
||||
sig_rdata_header = struct.pack(
|
||||
"!HBBIIIH", 0, 8, 0, 0, expiration, inception, key_tag
|
||||
)
|
||||
|
||||
sig_rdata_pre_sig = sig_rdata_header + signer_name.to_wire()
|
||||
|
||||
# Sign: ( SIG RDATA sans signature ) + ( Message )
|
||||
signature = private_key.sign(
|
||||
sig_rdata_pre_sig + msg_bytes, padding.PKCS1v15(), hashes.SHA256()
|
||||
)
|
||||
|
||||
# Create the SIG RR
|
||||
full_sig_rdata = sig_rdata_pre_sig + signature
|
||||
sig_rr = dns.rdata.from_wire(
|
||||
dns.rdataclass.ANY,
|
||||
dns.rdatatype.SIG,
|
||||
full_sig_rdata,
|
||||
0,
|
||||
len(full_sig_rdata),
|
||||
)
|
||||
sig_rrset = dns.rrset.from_rdata(qname, 0, sig_rr)
|
||||
query.additional.append(sig_rrset)
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def test_sig0_acl_bypass():
|
||||
key_files = glob.glob("Ksig0.+*.private")
|
||||
assert len(key_files) == 1
|
||||
|
||||
query = make_sig0_query(key_files[0], "sig0.")
|
||||
|
||||
# Send the query
|
||||
res = isctest.query.tcp(query, "10.53.0.1")
|
||||
isctest.check.servfail(res)
|
||||
Loading…
Reference in a new issue