πŸ”’
There are new articles available, click to refresh the page.
Before yesterdayZero Day Initiative - Blog

CVE-2021-20226: A Reference-Counting Bug in the Linux Kernel io_uring Subsystem

22 April 2021 at 16:27

In June 2020, we received a Linux kernel submission detailing a reference-counting bug in the recently introduced io_uring subsystem. The bug leads to a use-after-free on any file structure, which can be leveraged for privilege escalation in the kernel. This bug was submitted by Ryota Shiga (@Ga_ryo_) of Flatt Security.

We believe that the vulnerability affected the Linux kernel from version 5.6 to 5.7 inclusive. The vulnerability has been assigned identifiers ZDI-21-001 and CVE-2021-20226.

The Vulnerability

Linux kernel 5.1 introduced a new asynchronous I/O feature called io_uring. This subsystem operates by batching I/O operation system calls, so that multiple I/O operations can be performed in one system call.

Linux kernel 5.6 has a flawed implementation of the IORING_OP_CLOSE operation. When a system call passes a files_struct to a kernel thread, io_grab_files() doesn’t increment the reference counter at (1). This can lead to a later access of the freed file structure.

Exploitation

The map_lookup_elem() and map_update_elem() functions are good candidates for use in exploiting this bug.

The fdget() at (2) is an optimized function that doesn't increase the reference count if the current task is single-thread. The returned file structure, f, can be freed by a later IORING_OP_CLOSE. The __bpf_copy_key() syscall at (3) is actually a wrapper for copy_from_user(). This provides an opportunity to produce a race condition by using userfaultfd and triggering the vulnerability. At this point, file structure f and its corresponding map are freed. The memory of the map can be reallocated with fake data at (4) and (5). Finally, we can read arbitrary memory at (6) and disclose to usermode.

Here is an overview for the exploit timeline:

Figure 1 - The Exploit Timeline

Figure 1 - The Exploit Timeline

The recvmsg() function is for timing control. The freed bpf_map can be faked by spraying with setxattr(). The arbitrary write can be achieved by map_update_elem(). This exploit method is restricted to a single-core environment due to the condition of fdget().

Conclusion

New features mean new attack surfaces, and new attack surfaces often lead to new bugs being discovered. It will be interesting to see if any other vulnerabilities are found in this subsystem. Regardless, it was a great find by Ryota, and we appreciate his submission. If that name sounds familiar at all, Ryota also competed in the most recent Pwn2Own and won $30,000 demonstrating a different privilege escalation bug on Ubuntu. We look forward to seeing more from him in the future.

You can find me on TwitterΒ @_wmliang_, and follow theΒ teamΒ for the latest in exploit techniques and security patches.

CVE-2021-20226: A Reference-Counting Bug in the Linux Kernel io_uring Subsystem

CVE-2021-31440: An Incorrect Bounds Calculation in the Linux Kernel eBPF Verifier

27 May 2021 at 16:00

In April 2021, the ZDI received a Linux kernel submission that turned out to be an incorrect bounds calculation bug in the extended Berkeley Packet Filter (eBPF) verifier. This bug was submitted to the program by Manfred Paul (@_manfp) of the RedRocket CTF team (@redrocket_ctf). Manfred Paul had successfully exploited two other eBPF verifier bugs in Pwn2OwnΒ 2020Β and 2021 respectively.

This particular bug bypassed the eBPF verification and resulted in an out-of-bounds (OOB) access in the Linux kernel. The researcher exploited this bug and demonstrated a Kubernetes container escape. The patch was recently released asΒ CVE-2021-31440Β . Linux kernel versions from 5.7 and on were affected.

The Vulnerability

After CVE-2020-8835, one significant change was added to the verifier, namely 32-bit bound tracking. For the unsigned and signed minimum and maximum bounds, the 32-bit bounds u32_min_value, u32_max_value, s32_min_value and s32_max_value additionally and exclusively apply to the lower 32 bits of each tracked register.

However, a very similar mistake was reintroduced in __reg_combine_64_into_32(). This function uses the known bounds on a 64-bit register to infer bounds for the register’s lower 32 bits.

If the smin_value and smax_valueat (1) are both within in the range of signed 32-bit integers, the 32-bit signed bounds are updated accordingly. In all other cases the 32-bit signed bounds remain in the β€œunbounded” state. This logic is proper. However, in the corresponding logic for unsigned bounds, the checks on umin_value and umax_value are performed separately at (2) and (3). This logic is incorrect. For example, consider what happens if a register has umin_value = 1 and umax_value = 1<<32. data-preserve-html-node="true" At (2), the verifier will set u32_min_value to 1. At runtime, the register’s actual value can be 1<<32, data-preserve-html-node="true" making the lower 32 bits equal to 0. This violates the correctness of the register’s bounds, which indicate that the minimum value of the lower 32 bits is 1.

Notably, there was already a fix for the case of signed bounds at (1) from December 2020, but it missed the case of unsigned bounds.

Exploitation

The bug can be exploited as follows. Begin with these eBPF instructions:

These instructions set BPF_REG_2 as 1<<32. data-preserve-html-node="true" The two successive NEG instructions cause the verifier to lose track of all bounds for BPF_REG_2, while keeping its runtime value unchanged.

Next:

This conditional branch tests whether BPF_REG_2 is greater than or equal to 1. For the true side of the branch, the verifier sets the register’s umin_value to 1. Furthermore, the verifier calls __reg_combine_64_into_32(), which sets u32_min_value to 1 as well. This is the branch that will be followed at runtime.

Next:

This second conditional branch tests whether BPF_REG_2 is less than or equal to 1, so for the true side of the branch, the verifier sets the register’s u32_max_value to 1. At this point, for the true path, u32_max_value and u32_min_value are both set to 1, meaning that the verifier believes that the value of the lower 32 bits is known to be exactly 1. Recall, though, that the runtime value of BPF_REG_2 has been set to 1<<32 data-preserve-html-node="true" from the beginning, so that the true value of the lower 32 bits is 0. Thus, the verifier has inferred incorrect bounds for the register.

Finally, we can use this to produce an out-of-bounds access:

The BPF_MOV32_REG extends the wrong knowledge to the whole 64-bit register. After two ALU (arithmetic) operations, the register value is believed by the verifier to be 0, but is actually 1. This situation is the same as in the exploit steps in CVE-2020-8835, and the later steps of that exploit can be reused here. The out-of-bounds read/write access is achieved by using the wrongly bounded register, followed by an arbitrary read/write, and privilege escalation to root.

Demonstrating Exploitation

Since the Kubernetes container by default allows access to all system calls, a container escape can be achieved by exploiting this bug. The researcher set the current UID and GID to 0 and obtained the CAP_SYS_MODULE capability. This allowed the program to load an arbitrary kernel module outside of the container.

Conclusion

The eBPF module is still a good place for kernel bug hunting. It was also recently introduced to Windows. This attack surface is usable not only for local privilege escalation but also for container escapes. Those running systems affected by this bug should apply this mitigation, or even better, upgrade the kernel to an unaffected version. Thanks again to Manfred Paul(@_manfp) of the RedRocket CTF team (@redrocket_ctf) for submitting this bug. He’s submitted a few other reports to the program, and each has been great. We hope to see more from him in the future.

You can find me on TwitterΒ @_wmliang_, and follow theΒ teamΒ for the latest in exploit techniques and security patches.

CVE-2021-31440: An Incorrect Bounds Calculation in the Linux Kernel eBPF Verifier

ZDI-21-502: An Information Disclosure Bug in ISC BIND server

16 June 2021 at 15:27

Last year, we received a submission of a remote code execution vulnerability in the ISC BIND server. Later, that same anonymous researcher submitted a second bug in this popular DNS server. Similar to the first bug, it exists within the Simple and Protected GSSAPI Negotiation Mechanism (SPNEGO) component, and its location is quite close to the previous submission. The vendor categorized this bug as low severity, so they did not issue any CVE or advisory. However, this bug is still interesting and worth a closer examination.

This vulnerability affects BIND version prior to 9.11.31 and 9.16.15. It can be triggered remotely and without authentication. It leads to out-of-bounds (OOB) read on heap memory and could allow an information disclosure to remote attackers. It might be possible to leverage this vulnerability in conjunction with the previous submission to execute arbitrary code on an affected BIND server.

The Vulnerability

The root cause is in the der_match_tag_and_length() function. It is used for matching a tag and geting the following length field from network packet. Based on the normal usage of der_get_length() in BIND, the parsed length field, length_ret, should be validated by the caller. However, der_match_tag_and_length() is one of the exceptions.

The length_ret at (1) is under control and has not yet been validated. Let's see one of the callers for der_match_tag_and_length():

The untrusted len is then used to decode the negTokenInit at (2). Many checks within decode_NegTokenInit() are based on len. Now, those checks are incorrect, and could lead to OOB access on different sub-fields, such as mechTypes, reqFlags, mechToken, etc.

The Trigger

The configuration used to reproduce this bug is the same as in the previous submission. The following screenshot is the crafted SPNEGO request for this bug.

Figure 1 - Wireshark view of the crafted SPNEGO request

Figure 1 - Wireshark view of the crafted SPNEGO request

The length_ret at (1) is controlled from offset 0xa5 as 0x91929394. The sub-field mechToken is an octet string. Its length is the crafted value 0x727374, appearing at offset 0xcd.

Upon receiving this crafted request, an OOB read is triggered within the handling of the mechToken field. The following call stack is based on BIND version 9.16.13.

The Exploitation Plan

Here’s one possible method for gaining information disclosure on an affected server.

After decode_NegTokenInit() parses the negTokenInit and its sub-fields, the loop at (3) searches for valid OIDs within the parsed mechTypes. If a valid OID is found, the server responds with an accept message at (4). Otherwise, the server responds with a reject message at (5). This gives us the ability to get the offset for some heap chunks. It’s possible this bug could be used in conjunction with the heap overflow from the previous submission, but this requires more research.

The Patch

From versionΒ 9.16.15, the ISC implementation of SPNEGO was removed from BIND 9 source code. Instead, BIND 9 now always uses the SPNEGO implementation provided by the system GSSAPI library when it is built with GSSAPI support. Because of this, the related bugs are also removed. This feature change was also applied to version 9.11.31.

You should verify you have a patched version of BIND as many OS distributions provide BIND packages that differ from the official ISC release versions. In particular, it is not uncommon for a distribution to choose a stable base version for their BIND package then selectively apply chosen patches for only those issues they think merit inclusion.Β  A consequence of this is that BIND may contain a fix even if the version number is different (and possibly less) than the version number in which ISC patched the vulnerability.

Conclusion

ISC BIND is the most popular DNS server on the internet. The scope of impact is quite large, especially since the vulnerability can be triggered remotely and without authentication. All are advised to update their DNS servers as soon as possible. This same anonymous researcher also reported a remote code execution bug within the handling of TKEY queries, which was also fixed recently.

We are also looking forward to seeing a reliable full exploit method at some point. As a reminder, ISC BIND is a part of our Targeted Incentive Program and could earn some big payouts for full exploits. We certainly hope to see some in the future.

You can find me on TwitterΒ @_wmliang_, and follow theΒ teamΒ for the latest in exploit techniques and security patches.

ZDI-21-502: An Information Disclosure Bug in ISC BIND server

CVE-2021-2429: A Heap-based Buffer Overflow Bug in the MySQL InnoDB memcached Plugin

2 September 2021 at 16:05

In April 2021, the ZDI received a submission of a vulnerability in the MySQL database. It turned out to be a heap-based buffer overflow bug in the InnoDB memcached plugin. It was submitted to the program by an anonymous researcher.

The vulnerability affects MySQL versions 8.0.25 and prior. It can be triggered remotely and without authentication. Attackers can leverage this vulnerability to execute arbitrary code on the MySQL database server. Oracle patched it in July and assigned itΒ CVE-2021-2429, while ZDI’s identifier is ZDI-2021-889.

The Vulnerability

The following analysis is based on the source code of MySQL Community Server version 8.0.25. The bug is in the memcached GET command, which is used for retrieving data from a table. For performance, the GET command supports fetching multiple key-value pairs in a single memcached query. Here is an example:

The keys specified in the GET command are tokenized by process_get_command() and then handled one by one in innodb_get() .

If a key in the GET command has the form @@containers.name, then the variable report_table_switch will have been set to true, satisfying the branch at (1). The memcpy at (3) copies table_name to the row_buf buffer. Before performing the memcpy, the code at (2) validates that there is still enough space in row_buf. However, this validation is performed with an assert() only. Since assert is a macro that produces code only in debug builds but not in release builds, this leads to a buffer overflow that can be reached when running a release build.

The Trigger

The InnoDB memcached plugin is not enabled by default. One must build MySQL from source with -DWITH_INNODB_MEMCACHED=ON. Here is the build detail. By default, the memcached daemon listens on TCP and UDP port 11211. The payload is a single GET command as seen in the example below.

Β Β Β Β Β Β Β get @@aaa @@aaa @@aaa ...

@@aaa is one of the default rows in the innodb_memcache database.

Each @@aaa is replaced with the table name test/demo_test at (5) within the innodb_get() function shown above. The resulting overflow content has the form test/demo_testtest/demo_testtest/demo_test.... The length of the overflow is controllable by the attacker. After sending the payload, the heap overflow is triggered in the mysqld process. The call stack is shown below.

The Patch

The vulnerability was fixed in version 8.0.26. TheΒ patchΒ is straightforward. It explicitly checks the length before copying.

Conclusion

Although the InnoDB memcached plugin is not enabled by default, it is nonetheless wise to apply the patch as soon as possible. It would not surprise me to see a reliable full exploit in the near future.

You can find me on Twitter @_wmliang_, and follow the team for the latest in exploit techniques and security patches.

CVE-2021-2429: A Heap-based Buffer Overflow Bug in the MySQL InnoDB memcached Plugin

  • There are no more articles
❌