Out-of-Bounds write in systemd-networkd dhcpv6 option handling

Bug #1795921 reported by Felix Wilhelm on 2018-10-03
278
This bug affects 4 people
Affects Status Importance Assigned to Milestone
systemd (Ubuntu)
Medium
Unassigned

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

CVE References

Seth Arnold (seth-arnold) wrote :

Hello Felix, thanks for contacting us. Excellent discovery.

Have you contacted anyone else about this issue yet?
Have you allocated a CVE number for this yet?

My first thought to address this is to amend A for sizeof(DHCP6Option); -- does this feel like a sufficient or correct approach to addressing this issue?

Thanks

Felix Wilhelm (fwilhelm01) wrote :

Hi Seth,

Thanks for the quick response.

I haven't contacted anyone else yet (the guidelines at https://github.com/systemd/systemd/blob/master/docs/CONTRIBUTING.md#security-vulnerability-reports recommend one of the big distributions bug trackers as first point of contact) and have not allocated a CVE.

I agree with your proposed fix, that seems to be the easiest way to solve this.

Best,
Felix

Felix Wilhelm (fwilhelm01) wrote :

Hi,

any updates on this?

Best,
Felix

Changed in systemd (Ubuntu):
status: New → Confirmed
Seth Arnold (seth-arnold) wrote :

Hello Felix, we've communicated with our friends at Red Hat about this issue but have nothing concrete to report yet.

Thanks

Seth Arnold (seth-arnold) wrote :

CVE-2018-15688 has been assigned to this issue.

Seth Arnold (seth-arnold) wrote :

The fixes currently under development are at https://github.com/poettering/systemd/commits/dhcp6-size-fixes

Thanks

Felix Wilhelm (fwilhelm01) wrote :

Hi,

any ETA for the public disclosure?
It looks like the fix is already public since Friday (https://github.com/poettering/systemd/commit/49653743f69658aeeebdb14faf1ab158f1f2cb20)

Thanks

[I've expanded the Cc: list a bit to include Jann directly, and a
launchpad bug email address to Cc: Felix indirectly.]

On Tue, Oct 23, 2018 at 05:32:09PM +0200, Lennart Poettering wrote:
> Zbigniew has reviewed all three branches and only found nitpicks. I
> have fixed those in my local branch and added refs to the CVEs/bug
> reports to the commit msgs, as well as attribution to the original
> finders of the bugs.
>
> As mentioned Patryk Flykt (the original author of the dhcp6) code also
> reviewed the DHCPv6 side of things and is happy.
>
> This basically means we are all done from our side.

Excellent.

> I have not pushed my local updated brunches to github yet, since they
> would now make it very obvious that there's a security issue.
>
> If you are ready to go, give us a signal, and I'll push the branches,
> turn them into github PRs, and Zbigniew will merge them quickly after,
> which settles it from our side.
>
> Does that work for you?
>
> Waiting for a signal from you now,

I think the best course of action is for you to push your branches as
soon as it is convenient. We're not ready here, and I can't promise any
specific date when we might be ready.

So unless you've heard something from Jann or Felix that would caution
you to sit on this for a bit longer, I think it's time to go public.

I'll finish the MITRE paperwork tomorrow.

Thanks

Riccardo Schirone (rschiron) wrote :

On 10/23, Seth Arnold wrote:
> [I've expanded the Cc: list a bit to include Jann directly, and a
> launchpad bug email address to Cc: Felix indirectly.]
>
> On Tue, Oct 23, 2018 at 05:32:09PM +0200, Lennart Poettering wrote:
> > Zbigniew has reviewed all three branches and only found nitpicks. I
> > have fixed those in my local branch and added refs to the CVEs/bug
> > reports to the commit msgs, as well as attribution to the original
> > finders of the bugs.
> >
> > As mentioned Patryk Flykt (the original author of the dhcp6) code also
> > reviewed the DHCPv6 side of things and is happy.
> >
> > This basically means we are all done from our side.
>
> Excellent.
>
> > I have not pushed my local updated brunches to github yet, since they
> > would now make it very obvious that there's a security issue.
> >
> > If you are ready to go, give us a signal, and I'll push the branches,
> > turn them into github PRs, and Zbigniew will merge them quickly after,
> > which settles it from our side.
> >
> > Does that work for you?
> >
> > Waiting for a signal from you now,
>
> I think the best course of action is for you to push your branches as
> soon as it is convenient. We're not ready here, and I can't promise any
> specific date when we might be ready.
>
> So unless you've heard something from Jann or Felix that would caution
> you to sit on this for a bit longer, I think it's time to go public.

Can we make our bugzilla public as soon as the patch are public as well?

>
> I'll finish the MITRE paperwork tomorrow.
>
> Thanks

Thanks

--
Riccardo Schirone
Red Hat -- Product Security
Email: <email address hidden>
PGP-Key ID: CF96E110

information type: Private Security → Public Security
Seth Arnold (seth-arnold) wrote :

On Thu, Oct 25, 2018 at 11:35:18AM +0200, Lennart Poettering wrote:
> I guess that settles it from our side.
>
> Thank you all for working on this!

Thanks everybody!

Changed in systemd (Ubuntu):
importance: Undecided → Medium

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?

https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt

Does this prevent the described security problem?

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

Dimitri John Ledkov (xnox) wrote :

Hi,

On Tue, 30 Oct 2018 at 20:31, Clemens Fuchslocher
<email address hidden> wrote:
>
> Are there any workarounds for Ubuntu 16.04?

By default, networkd is not used on 16.04; unless one manually opted
into it, or uses particular SKUs/images that choose to use networkd by
default (e.g. Core images, and some cloud images).

--
Regards,

Dimitri.

Zbyszek: Thank you for your clarification!

Dimitri: You are right, on my Ubuntu 16.04 server installations the systemd-networkd service is disabled and the dhclient from the isc-dhcp-client package is used for the DHCP part:

$ systemctl status systemd-networkd.service
  systemd-networkd.service - Network Service
   Loaded: loaded (/lib/systemd/system/systemd-networkd.service; disabled; vendor preset: enabled)
   Active: inactive (dead)
     Docs: man:systemd-networkd.service(8)

$ journalctl | grep -i dhcp
...
Aug 28 13:47:55 #### dhclient[995]: Internet Systems Consortium DHCP Client 4.3.3
...
Oct 31 14:33:27 #### dhclient[1355]: DHCPREQUEST of ###.###.###.### on ### to ###.###.###.### port 67 (xid=##########)
Oct 31 14:33:27 #### dhclient[1355]: DHCPACK of ###.###.###.### from ###.###.###.###
...

$ dpkg --search /sbin/dhclient
isc-dhcp-client: /sbin/dhclient

Hi,

There is still no updates available on bionic : according to security.gentoo.org affected versions < 'systemd-239', while candidate in bionic-updates is '237-3ubuntu10.3'. Is there any manual method to fix it, while waiting for the updated version ?

Source: https://security.gentoo.org/glsa/201810-10

Alex Murray (alexmurray) wrote :

@yassine-mrabet - In general, Ubuntu does not upgrade major versions of software and instead backports security fixes to the current version - also we track CVEs independently in our own CVE tracker - in this case please see https://people.canonical.com/~ubuntu-security/cve/2018/CVE-2018-15688.html

Here you can see this is resolved for bionic in version 237-3ubuntu10.4 which is available in the bionic-security pocket. So simply doing an 'sudo apt-get update && sudo apt-get upgrade' should make this available.

To post a comment you must log in.
This report contains Public Security information  Edit
Everyone can see this security related information.

Other bug subscribers