Talos Vulnerability Report

TALOS-2015-0052

Network Time Protocol ntpd multiple integer overflow read access violations

October 21, 2015
CVE Number

CVE-2015-7848

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 RESETPEER, the function resetpeer() 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

Aleksandar Nikolic of Cisco Talos