Talos Vulnerability Report

TALOS-2015-0052

Network Time Protocol ntpd multiple integer overflow read access violations

Oct 21, 2015
Description

When processing a specially crafted private mode packet, an integer overflow can occur leading to out of bounds memory copy operation. The crafted packet needs to have the correct message authentication code and a valid timestamp. When processed by the NTP daemon, it leads to an immediate crash.

Tested Versions

ntp-dev.4.3.70

Product URLs

http://www.ntp.org

Details

While parsing a private mode packet with a request code of RESET_PEER (0x16) two values 16 bit big endian values from the packet are interpreted as number of items and size of item respectively. A faulty check on the number of items ends up decrementing the value which is later used in a second check. If the initial value was zero, the decremented value will be 65535 leading to a crash in a while loop.

Technical Information

With a request code of RESET_PEER, the function reset_peer() is reached

  reset_peer - clear a peer's statistics

static void
reset_peer(
    sockaddr_u srcadr,
    endpt inter,
    struct req_pkt inpkt
    )
{
    u_short			items;
    size_t			item_sz;
    char 			datap;
    struct conf_unpeer	cp;
    struct peer 		p;
    sockaddr_u		peeraddr;
    int			bad;

We check first to see that every peer exists. If not, we return an error.

    items = INFO_NITEMS(inpkt-err_nitems);								[1]
    item_sz = INFO_ITEMSIZE(inpkt-mbz_itemsize); 						[2]
    datap = inpkt-u.data;
    if (item_sz  sizeof(cp)) {
        req_ack(srcadr, inter, inpkt, INFO_ERR_FMT);
        return;
    }

    bad = FALSE;
    while (items--  0 && !bad) { 										[3]
        ZERO(cp);
        memcpy(&cp, datap, item_sz);
        ZERO_SOCK(&peeraddr);
        if (client_v6_capable && cp.v6_flag) {
            AF(&peeraddr) = AF_INET6;
            SOCK_ADDR6(&peeraddr) = cp.peeraddr6;
        } else {
            AF(&peeraddr) = AF_INET;
            NSRCADR(&peeraddr) = cp.peeraddr;
        }

#ifdef ISC_PLATFORM_HAVESALEN
        peeraddr.sa.sa_len = SOCKLEN(&peeraddr);
#endif
        p = findexistingpeer(&peeraddr, NULL, NULL, -1, 0);
        printf(%d %dn, bad,  p);
        if (NULL == p)
            bad++;
        datap += item_sz;
    }

    if (bad) {
        req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA);
        return;
    }

Now do it in earnest.

datap = inpkt-u.data;
    while (items--  0) {
    [4]
        ZERO(cp);
        memcpy(&cp, datap, item_sz);
        ZERO_SOCK(&peeraddr);
        if (client_v6_capable && cp.v6_flag) {
            AF(&peeraddr) = AF_INET6;
            SOCK_ADDR6(&peeraddr) = cp.peeraddr6;
        } else {
            AF(&peeraddr) = AF_INET;
            NSRCADR(&peeraddr) = cp.peeraddr;
        }
        SET_PORT(&peeraddr, 123);
#ifdef ISC_PLATFORM_HAVESALEN
        peeraddr.sa.sa_len = SOCKLEN(&peeraddr);
#endif
        p = findexistingpeer(&peeraddr, NULL, NULL, -1, 0);
        while (p != NULL) {
            peer_reset(p);
            p = findexistingpeer(&peeraddr, NULL, p, -1, 0);
        }
        datap += item_sz;
    }

    req_ack(srcadr, inter, inpkt, INFO_OKAY);
}

At [1] the number of items is derived and the size of each item at [2]. Size can be either 4 of 24. At [3], a check is being made to ensure ‘items’ is greated than zero. If it’s not, the check fails and the code never enters the while loop. At [4], the same check is performed again, but because of the postdecrement operator at [3], the value in ‘items’ is now already decremented by one.

In case the initial value of ‘items’ was 0, the first check will fail, but the post decrement operator will decremet this value thus overflowing the integer. New value of ‘items’ will be 65535 when it reaches [4], passing the check and entering the while loop. A crash occures on memcpy when ‘datap’ pointer is incremented past the allocated memory.

Size of the ‘memcpy’ is limited to either 4 of 24, and the destination buffer is always large enough, thus limiting this issue to an out of bounds read leading to sudden termination of the process.

The similar condition occurs inside do_restrict() function, which can be triggered with a packet with different opcode (0x12 for example) and different size value (0x0c for example)

static void
do_restrict(
    sockaddr_u srcadr,
    endpt inter,
    struct req_pkt inpkt,
    int op
    )
{
    char 			datap;
    struct conf_restrict	cr;
    u_short			items;
    size_t			item_sz;
    sockaddr_u		matchaddr;
    sockaddr_u		matchmask;
    int			bad;

Do a check of the flags to make sure that only the NTPPORT flag is set, if any. If not, complain about it. Note we are very picky here.

    items = INFO_NITEMS(inpkt-err_nitems); 					[1]
    item_sz = INFO_ITEMSIZE(inpkt-mbz_itemsize);
    datap = inpkt-u.data;
    if (item_sz  sizeof(cr)) {
        req_ack(srcadr, inter, inpkt, INFO_ERR_FMT);
        return;
    }

    bad = FALSE;
    while (items--  0 && !bad) {								[2]
        memcpy(&cr, datap, item_sz);
        cr.flags = ntohs(cr.flags);
        cr.mflags = ntohs(cr.mflags);
        if (~RESM_NTPONLY & cr.mflags)
            bad = 1;
        if (~RES_ALLFLAGS & cr.flags)
            bad = 2;
        if (INADDR_ANY != cr.mask) {
            if (client_v6_capable && cr.v6_flag) {
                if (IN6_IS_ADDR_UNSPECIFIED(&cr.addr6))
                    bad = 4;
            } else {
                if (INADDR_ANY == cr.addr)
                    bad = 8;
            }
        }
        datap += item_sz;
    }

    if (bad) {
        msyslog(LOG_ERR, do_restrict bad = %#x, bad);
        req_ack(srcadr, inter, inpkt, INFO_ERR_FMT);
        return;
    }

Looks okay, try it out

    	ZERO_SOCK(&matchaddr);
    	ZERO_SOCK(&matchmask);
    	datap = inpkt-u.data;

    	while (items--  0) { 									[3]
    		memcpy(&cr, datap, item_sz);
    		cr.flags = ntohs(cr.flags);
    		cr.mflags = ntohs(cr.mflags);
    		if (client_v6_capable && cr.v6_flag) {
    			AF(&matchaddr) = AF_INET6;
    			AF(&matchmask) = AF_INET6;
    			SOCK_ADDR6(&matchaddr) = cr.addr6;
    			SOCK_ADDR6(&matchmask) = cr.mask6;
    		} else {
    			AF(&matchaddr) = AF_INET;
    			AF(&matchmask) = AF_INET;
    			NSRCADR(&matchaddr) = cr.addr;
    			NSRCADR(&matchmask) = cr.mask;
    		}
    		hack_restrict(op, &matchaddr, &matchmask, cr.mflags,
    			      cr.flags, 0);
    		datap += item_sz;
    	}

    	req_ack(srcadr, inter, inpkt, INFO_OKAY);
    }

Same as in reset_peer(), at [1] the ‘items’ value is derived, at [2] it is checked against zero and decremented, and at [3], the decremented value is used in a check again.

Both of these issues can be triggered by the supplied proof of concept code which contains two payloads, for each of the vulnerabilities.

The vulnerabilities can be triggered in the latest version of ntpd with the supplied configuration files that set up proper keys and options as the vulnerabilities are only reachable with proper authentication.

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.