[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