Sending ICMPv6 ECHO REQUEST over a connected ICMP socket panics the kernel

Originator:m.hanauska
Number:rdar://13474485 Date Originated:21-Mar-2013
Status:Closed Resolved:Fixed in Mavericks (MacOS 10.9)
Product:OS X Product Version:10.8.3
Classification:Crash/Hang/Data Loss Reproducible:Always
 
Summary:
Sending an ICMPv6 ECHO REQUEST packet over a connected ICMP datagram socket (without root permissions) causes a kernel panic. This issue affects 10.8.x as well as 10.7.x (verified with 10.7.5).

Steps to Reproduce:
1. Create an ICMPv6 socket: socket(PF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6)
2. Connect the socket to an IPv6 address: connect(...)
3. Create an ICMPv6 packet by hand
4. Send the ICMPv6 packet over the socket: send(...)

Expected Results:
Either the packet is sent or an error is returned.

Actual Results:
The kernel panics.

Regression:
n/a

Notes:
I can even tell you exactly where the bug is in the kernel code. I only have the kernel code from 10.8.2 (10.8.3 source has not been released yet), but the problem still exists in 10.8.3.

Take a look at icmp6_dgram_send() in icmp6.c:

__private_extern__ int
icmp6_dgram_send(struct socket *so, int flags, struct mbuf *m,
    struct sockaddr *nam, struct mbuf *control, struct proc *p)
{
#pragma unused(flags, p)
	int error = 0;
	struct inpcb *inp = sotoinpcb(so);
	struct sockaddr_in6 tmp;
	struct sockaddr_in6 *dst = (struct sockaddr_in6 *)(void *)nam;
	struct icmp6_hdr *icmp6;

	if (kauth_cred_issuser(so->so_cred))
		return rip6_output(m, so, (struct sockaddr_in6 *)(void *)nam,
		    control, 0);

	/* always copy sockaddr to avoid overwrites */
	if (so->so_state & SS_ISCONNECTED) {
		if (nam) {
			m_freem(m);
			return EISCONN;
		}
		/* XXX */
		bzero(&tmp, sizeof(tmp));
		tmp.sin6_family = AF_INET6;
		tmp.sin6_len = sizeof(struct sockaddr_in6);
		bcopy(&inp->in6p_faddr, &tmp.sin6_addr,
			  sizeof(struct in6_addr));
		dst = &tmp;
	} else {
		if (nam == NULL) {
			m_freem(m);
			return ENOTCONN;
		}
		tmp = *(struct sockaddr_in6 *)(void *)nam;
		dst = &tmp;
	}

If the socket is connected (`SS_ISCONNECTED`) and we get to beyond the code above, we know for sure that `nam` is NULL (otherwise we had returned with the error code `EISCONN`). `nam` is not touched till the end of the function. At the end of the function, the following function call is performed:

	return rip6_output(m, so, (struct sockaddr_in6 *)(void *)nam,
	   control, 0);

Remember, `nam` *IS NULL*! I think you probably wanted to use `dst` here, not `nam`. This is probably a copy&paste error from the same call at the beginning of the function, which is only performed if the socket is owned by root.

Now take a look at rip6_output() in raw_ip6.c:

int
rip6_output(
	register struct mbuf *m,
	struct socket *so,
	struct sockaddr_in6 *dstsock,
	struct mbuf *control,
	int israw)
{
	struct in6_addr *dst;
	struct ip6_hdr *ip6;
	struct inpcb *in6p;
	u_int	plen = m->m_pkthdr.len;
	int error = 0;
	struct ip6_pktopts opt, *optp = 0;
	struct ip6_moptions *im6o = NULL;
	struct ifnet *oifp = NULL;
	int type = 0, code = 0;		/* for ICMPv6 output statistics only */
	mbuf_svc_class_t msc = MBUF_SC_UNSPEC;
	struct ip6_out_args ip6oa =
	    { IFSCOPE_NONE, { 0 }, IP6OAF_SELECT_SRCIF };
	int flags = IPV6_OUTARGS;

	if (dstsock && IN6_IS_ADDR_V4MAPPED(&dstsock->sin6_addr)) {
		m_freem(m);
		return (EINVAL);
	}

	in6p = sotoin6pcb(so);

	if (in6p->inp_flags & INP_BOUND_IF) {
		ip6oa.ip6oa_boundif = in6p->inp_boundifp->if_index;
		ip6oa.ip6oa_flags |= IP6OAF_BOUND_IF;
	}
	if (in6p->inp_flags & INP_NO_IFT_CELLULAR)
		ip6oa.ip6oa_flags |= IP6OAF_NO_CELLULAR;

	dst = &dstsock->sin6_addr;

dstsock is NULL! So this line in fact is the same as `dst = (NULL + 8)`. `dst` is not being used in the code up to that line:

	/*
	 * Next header might not be ICMP6 but use its pseudo header anyway.
	 */
	ip6->ip6_dst = *dst;

Here the system panics because this operation is a memory access to address 0x8, which is within the first memory page and read access to this page cause a CPU page fault exception.

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!