[Bug 1795921] Re: Out-of-Bounds write in systemd-networkd dhcpv6 option handling
zbyszek
1795921 at bugs.launchpad.net
Wed Oct 31 13:21:16 UTC 2018
On Tue, Oct 30, 2018 at 08:16:27PM -0000, Clemens Fuchslocher wrote:
> Are there any workarounds for Ubuntu 16.04?
>
> Can I set /proc/sys/net/ipv6/conf/all/accept_ra to 0 to ignore the
> Router Advertisements?
There are two settings: the accept_ra sysctl in the kernel, and
IPv6AcceptRa= in systemd. The second setting can override the kernel
setting, but it defaults to "use the kernel default". The kernel
defaults to "enabled if local forwarding is disabled".
So accept_ra=0 is honoured as long as IPv6AcceptRa= is *not* set to true.
But the code where the error is is in the dhcpv6 client part. It can
be triggered in two different ways: upon reception of a RA, or explicitly
by using DHCP=yes or DHCP=ipv6. So both settings (accept_ra=/IPv6AcceptRa=
and DHCP=) have to be set to 0/no to mitigate the issue.
(Note though DHCP=no is the default.)
This is my understanding, but I'm not too familiar with this code, so
it'd be great if somebody could confirm.
Zbyszek
--
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:
Confirmed
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