The beginning of the story
In recent years, we observed some security vulnerabilities in the XNU caused by disclosed sockets. Ned Will from Google Project Zero has released a vulnerability in in6_pcbdetach. Here, we first introduce his vulnerability in detail . Understanding the cause of this vulnerability is very helpful to discover new vulnerabilities.
Details: Issue 1806: XNU: Use-after-free due to stale pointer left by in6_pcbdetach
According to the poc, we noticed that disconnectx changed the status of a socket : the options are freed but not cleared, so they can be used after they are freed, leading to a security issue.
After learning the vulnerability, we tried to find some new functions, which may change the status of socket like disconnectx(). Finally, we found the function shutdown().
There are three modes for shutdown: SHUT_WR, SHUT_RD, and SHUT_RDWR. When you call the function shutdown on a socket with mode SHUT_RD or SHUT_RDWR, it will add SB_DROP flag to the socket.
And we found that, in the function sbappend(), SB_DROP flag is a condition to check whether a mbuf needs to be freed or not. As we can see below, at the very beginning of sbappend, if a socket has the SB_DROP flag enabled, sbappend will release the mbuf and return 0.
Then we begin to check the code where sbappend is called, at last we found a double free vulnerability in nstat_control_send.
By sending data to a network statistics kernel control socket, we can run into the function nstat_control_send in the kernel. Function nstat_control_send calls the function ctl_enqueuembuf that will enqueue an mbuf into the socket’s rcvlist. In case of error, nstat_control_send will free the mbuf. The following code snippet shows the process.
Inside ctl_enqueuembuf, it will call the function sbappend to add the mbuf to the rcv list. Note that if sbappend fails, ENOBUFS will be returned.
From the code execution above, we can see that, when we send data to a networkstatistics kernel control socket that has the SB_DROP flag enabled, the function sbappend will first free the mbuf struct, and the return 0; then ctl_enqueuembuf returns ENOBUFS to nstat_control_send. At this point, nstat_control_send will free the mbuf struct again, resulting in a double free vulnerability.
Not the End
Shortly after we reported it to Apple,we noticed that Ned Will also released a very similar vulnerability in the function ip6_notify_pmtu. The both vulnerabilities are actually caused by the opaque semantic of the function sbappend*(). But Ned Will trigger it in a different way.
Details :Issue1976: XNU: Remote mbuf-double-free in ip6_notify_pmtu
After we studied this vulnerability again, we found there are two ways to make sbappendaddr() free the mbuf and return 0.
Our way to trigger it:
when we send data to a socket that has the SB_DROP flag enabled,the function sbappendaddr will first free the m_mtu, and the return 0. At this point, ip6_notify_pmtu will free m_mtu again, resulting in a double free vulnerability.
Ned Will's :
Let's take a closer look at sbconcat_mbufs. If the sockaddr have a super long socket length, this will make sbconcat_mbufs failure, and then leads to sbappendchain returning 0. As a consequence, sbappendaddr will m_free m0, and return an error.
After we studied the vulnerability and investigated the usage of sbappend* family functions in XNU, we developed a static anlayzer and wrote a rule to help us find similar vulnerabilities. At last, we found a new mbuf double free vulnerability in the function flow_divert_data_out. Vulnerability characteristics are as follows:
Let’s take a look at the function flow_divert_data_out. We can find that flow_divert_data_out invokes function flow_divert_send_app_data, and if flow_divert_send_app_data fails ,then flow_divert_data_out will call mbuf_freem to release data.
Inside function flow_divert_send_app_data, we can find that, for a DGRAM socket, flow_divert_send_app_data calls the function sbappendaddr to append the mbuf to the socket sending queue. The question is, if the destination address (i.e., toaddr) is incorrect, for example, having a super long socket length, this will make sbconcat_mbufs fail, and then leads to sbappendchain returning 0. As a consequence, sbappendaddr will m_free m0, and returns an error.
Let’s go back to flow_divert_data_out. When function flow_divert_send_app_data returns an error, flow_divert_data_out will m_free data again. However, data (that is m0 in sbappendaddr) is already freed by sbappendaddr, resulting in an mbuf double release vulnerability.
Studying publicly available vulnerabilities is very important to security research. In this article, we shared how we make the study process more effective and efficiency. We not only analyzed the root causes of a vulnerability, but also asked ourselves, why the developers made this mistake, how can we apply variant analysis to find other similar vulnerabilities, how to create static and dynamic tools to accelerate the analysis? Now, it's your turn!