CVE-2015-7848
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.
ntp-dev.4.3.70
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.
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.
Aleksandar Nikolic of Cisco Talos