[Bug 1795921] Re: Out-of-Bounds write in systemd-networkd dhcpv6 option handling
Sebastien Bacher
seb128 at ubuntu.com
Tue Dec 18 16:55:13 UTC 2018
** Changed in: systemd (Ubuntu)
Status: Confirmed => Fix Released
--
You received this bug notification because you are a member of Ubuntu
Foundations Bugs, which is subscribed to systemd in Ubuntu.
https://bugs.launchpad.net/bugs/1795921
Title:
Out-of-Bounds write in systemd-networkd dhcpv6 option handling
Status in systemd package in Ubuntu:
Fix Released
Bug description:
systemd-networkd contains a DHCPv6 client which is written from
scratch and can be spawned automatically on managed interfaces when
IPv6 router advertisement are received:
"Note that DHCPv6 will by default be triggered by Router Advertisement, if that is enabled, regardless of this parameter. By enabling DHCPv6 support explicitly, the DHCPv6 client will be started regardless of the presence of routers on the link, or what flags the routers pass"
(https://www.freedesktop.org/software/systemd/man/systemd.network.html)
The function dhcp6_option_append_ia function is used to encode Identity Associations received by the server into the options buffer of an outgoing DHCPv6 packet:
// https://github.com/systemd/systemd/blob/master/src/libsystemd-network/dhcp6-option.c#L82
int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
uint16_t len;
uint8_t *ia_hdr;
size_t iaid_offset, ia_buflen, ia_addrlen = 0;
DHCP6Address *addr;
int r;
assert_return(buf && *buf && buflen && ia, -EINVAL);
switch (ia->type) {
case SD_DHCP6_OPTION_IA_NA:
len = DHCP6_OPTION_IA_NA_LEN;
iaid_offset = offsetof(DHCP6IA, ia_na);
break;
case SD_DHCP6_OPTION_IA_TA:
len = DHCP6_OPTION_IA_TA_LEN;
iaid_offset = offsetof(DHCP6IA, ia_ta);
break;
default:
return -EINVAL;
}
A: if (*buflen < len)
return -ENOBUFS;
ia_hdr = *buf;
ia_buflen = *buflen;
*buf += sizeof(DHCP6Option);
B: *buflen -= sizeof(DHCP6Option);
C: memcpy(*buf, (char*) ia + iaid_offset, len);
*buf += len;
D: *buflen -= len;
E: LIST_FOREACH(addresses, addr, ia->addresses) {
r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR,
sizeof(addr->iaaddr));
if (r < 0)
return r;
memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
*buf += sizeof(addr->iaaddr);
*buflen -= sizeof(addr->iaaddr);
ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr);
}
r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
if (r < 0)
return r;
return 0;
}
The function receives a pointer to the option buffer buf, it's remaining size buflen and the IA to be added to the buffer. While the check at (A) tries to ensure that the buffer has enough space left to store the IA option, it does not take the additional 4 bytes from the DHCP6Option header into account (B). Due to this the memcpy at (C) can go out-of-bound and *buflen can underflow in (D) giving an attacker a very powerful and largely controlled OOB heap write starting at (E).
The overflow can be triggered relatively easy by advertising a DHCPv6
server with a server-id >= 493 characters long. This will trigger the
following code once the client tries to create a REQUEST message:
//https://github.com/systemd/systemd/blob/7bcf8123c0305131ace02480763377af974924ef/src/libsystemd-network/sd-dhcp6-client.c#L493
case DHCP6_STATE_REQUEST:
case DHCP6_STATE_RENEW:
if (client->state == DHCP6_STATE_REQUEST)
message->type = DHCP6_REQUEST;
else
message->type = DHCP6_RENEW;
A: r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_SERVERID,
client->lease->serverid_len,
client->lease->serverid);
if (r < 0)
return r;
if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) {
B: r = dhcp6_option_append_ia(&opt, &optlen,
&client->lease->ia);
if (r < 0)
return r;
}
optlen starts with the value 512 and gets decreased to 15 (512 - 493 -
4) after the call in A. As 15 > DHCP6_OPTION_IA_NA_LEN but 15 <
(DHCP6_OPTION_IA_NA_LEN + 4) this triggers the memory corruption:
# journalctl -u systemd-networkd
Oct 03 14:27:50 ubuntu18 systemd-networkd[45095]: malloc(): memory corruption
Oct 03 14:27:50 ubuntu18 systemd[1]: systemd-networkd.service: Main process exited, code=killed, status=6/ABRT
Oct 03 14:27:50 ubuntu18 systemd[1]: systemd-networkd.service: Failed with result 'signal'.
Oct 03 14:27:50 ubuntu18 systemd[1]: systemd-networkd.service: Service has no hold-off time, scheduling restart.
Oct 03 14:27:50 ubuntu18 systemd[1]: systemd-networkd.service: Scheduled restart job, restart counter is at 225.
Oct 03 14:27:50 ubuntu18 systemd[1]: Stopped Network Service.
Oct 03 14:27:50 ubuntu18 systemd[1]: Starting Network Service...
Oct 03 14:27:50 ubuntu18 systemd-networkd[45118]: ens38: Gained IPv6LL
Oct 03 14:27:50 ubuntu18 systemd-networkd[45118]: ens33: Gained IPv6LL
Oct 03 14:27:50 ubuntu18 systemd-networkd[45118]: Enumeration completed
Oct 03 14:27:50 ubuntu18 systemd[1]: Started Network Service.
Oct 03 14:27:50 ubuntu18 systemd-networkd[45118]: lo: Link is not managed by us
Oct 03 14:27:50 ubuntu18 systemd-networkd[45118]: ens38: Configured
Oct 03 14:27:53 ubuntu18 systemd-networkd[45118]: free(): corrupted unsorted chunks
Oct 03 14:27:53 ubuntu18 systemd[1]: systemd-networkd.service: Main process exited, code=killed, status=6/ABRT
Oct 03 14:27:53 ubuntu18 systemd[1]: systemd-networkd.service: Failed with result 'signal'.
An interesting aspect of this bug is that systemd-networkd will
restart automatically after a crash, which means that even a somewhat
unstable exploit can work reliably.
Testing was done on a Ubuntu 18.04 server installation running systemd
237-3ubuntu10.3
This bug is subject to a 90 day disclosure deadline. After 90 days elapse
or a patch has been made broadly available (whichever is earlier), the bug
report will become visible to the public.
Please credit Felix Wilhelm from the Google Security Team in all
releases, patches and advisories related to this issue.
Let me know if you have any questions.
Best,
Felix
To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/systemd/+bug/1795921/+subscriptions
More information about the foundations-bugs
mailing list