[libvirt] [PATCH 3/3] network: use dnsmasq --bind-dynamic when available

Doug Goldstein cardoe at cardoe.com
Thu Nov 22 06:13:21 UTC 2012


On Nov 21, 2012, at 8:55 PM, Laine Stump <laine at laine.org> wrote:

> This bug resolves CVE-2012-3411, which is described in the following
> bugzilla report:
> 
>  https://bugzilla.redhat.com/show_bug.cgi?id=833033
> 
> The following report is specifically for libvirt on Fedora:
> 
>  https://bugzilla.redhat.com/show_bug.cgi?id=874702
> 
> In short, a dnsmasq instance run with the intention of listening for
> DHCP/DNS requests only on a libvirt virtual network (which is
> constructed using a Linux host bridge) would also answer queries sent
> from outside the virtualization host.
> 
> This patch takes advantage of a new dnsmasq option "--bind-dynamic",
> which will cause the listening socket to be setup such that it will
> only receive those requests that actually come in via the bridge
> interface. In order for this behavior to actually occur, not only must
> "--bind-interfaces" be replaced with "--bind-dynamic", but also all
> "--listen-address" options must be replaced with a single
> "--interface" option. Fully:
> 
>   --bind-interfaces --except-interface lo --listen-address x.x.x.x ...
> 
> (with --listen-address possibly repeated) is replaced with:
> 
>   --bind-dynamic --interface virbrX
> 
> Of course libvirt can't use this new option if the host's dnsmasq
> doesn't have it, but we still want libvirt to function (because the
> great majority of libvirt installations, which only have mode='nat'
> networks using RFC1918 private address ranges (e.g. 192.168.122.0/24),
> are immune to this vulnerability from anywhere beyond the local subnet
> of the host), so we use the new dnsmasqCaps API to check if dnsmasq
> supports the new option and, if not, we use the "old" option style
> instead. In order to assure that this permissiveness doesn't lead to a
> vulnerable system, we do check for non-private addresses in this case,
> and refuse to start the network if both a) we are using the old-style
> options, and b) the network has a publicly routable IP
> address. Hopefully this will provide the proper balance of not being
> disruptive to those not practically affected, and making sure that
> those who *are* affected get their dnsmasq upgraded.
> 
> (--bind-dynamic was added to dnsmasq in upstream commit
> 54dd393f3938fc0c19088fbd319b95e37d81a2b0, which was included in
> dnsmasq-2.63)
> ---
> src/network/bridge_driver.c                        | 77 +++++++++++++++-------
> tests/networkxml2argvdata/isolated-network.argv    |  5 +-
> .../networkxml2argvdata/nat-network-dns-hosts.argv |  5 +-
> .../nat-network-dns-srv-record-minimal.argv        |  9 ++-
> .../nat-network-dns-srv-record-minimal.xml         |  4 +-
> .../nat-network-dns-srv-record.argv                |  8 +--
> .../nat-network-dns-txt-record.argv                |  8 +--
> tests/networkxml2argvdata/nat-network.argv         |  6 +-
> tests/networkxml2argvdata/netboot-network.argv     |  4 +-
> .../networkxml2argvdata/netboot-proxy-network.argv |  5 +-
> tests/networkxml2argvdata/routed-network.argv      |  4 +-
> 11 files changed, 80 insertions(+), 55 deletions(-)
> 
> diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
> index 34923ea..b3eb11c 100644
> --- a/src/network/bridge_driver.c
> +++ b/src/network/bridge_driver.c
> @@ -658,7 +658,7 @@ networkBuildDnsmasqArgv(virNetworkObjPtr network,
>      * Needed to ensure dnsmasq uses same algorithm for processing
>      * multiple namedriver entries in /etc/resolv.conf as GLibC.
>      */
> -    virCommandAddArgList(cmd, "--strict-order", "--bind-interfaces", NULL);
> +    virCommandAddArg(cmd, "--strict-order");
> 
>     if (network->def->domain)
>         virCommandAddArgPair(cmd, "--domain", network->def->domain);
> @@ -673,9 +673,60 @@ networkBuildDnsmasqArgv(virNetworkObjPtr network,
>     /* *no* conf file */
>     virCommandAddArg(cmd, "--conf-file=");
> 
> -    virCommandAddArgList(cmd,
> -                         "--except-interface", "lo",
> -                         NULL);
> +    if (dnsmasqCapsGet(caps, DNSMASQ_CAPS_BIND_DYNAMIC)) {
> +        /* using --bind-dynamic with only --interface (no
> +         * --listen-address) prevents dnsmasq from responding to dns
> +         * queries that arrive on some interface other than our bridge
> +         * interface (in other words, requests originating somewhere
> +         * other than one of the virtual guests connected directly to
> +         * this network). This was added in response to CVE 2012-3411.
> +         */
> +        virCommandAddArgList(cmd,
> +                             "--bind-dynamic",
> +                             "--interface", network->def->bridge,
> +                             NULL);
> +    } else {
> +        virCommandAddArgList(cmd,
> +                             "--bind-interfaces",
> +                             "--except-interface", "lo",
> +                             NULL);
> +        /*
> +         * --interface does not actually work with dnsmasq < 2.47,
> +         * due to DAD for ipv6 addresses on the interface.
> +         *
> +         * virCommandAddArgList(cmd, "--interface", network->def->bridge, NULL);
> +         *
> +         * So listen on all defined IPv[46] addresses
> +         */
> +        for (ii = 0;
> +             (tmpipdef = virNetworkDefGetIpByIndex(network->def, AF_UNSPEC, ii));
> +             ii++) {
> +            char *ipaddr = virSocketAddrFormat(&tmpipdef->address);
> +
> +            if (!ipaddr)
> +                goto cleanup;
> +            /* also part of CVE 2012-3411 - if the host's version of
> +             * dnsmasq doesn't have --bind-dynamic, only allow listening on
> +             * private/local IP addresses (see RFC1918/RFC4193)
> +             */
> +            if (!virSocketAddrIsPrivate(&tmpipdef->address)) {
> +                unsigned long version = dnsmasqCapsGetVersion(caps);
> +
> +                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
> +                               _("Publicly routable address %s is prohibited. "
> +                                 "The version of dnsmasq on this host (%d.%d) doesn't "
> +                                 "support the --bind-dynamic option, which is required "
> +                                 "for safe operation on a publicly routable subnet "
> +                                 "(see CVE-2012-3411). You must either upgrade dnsmasq, "
> +                                 "or use a private/local subnet range for this network "
> +                                 "(as described in RFC1918/RFC4193)."), ipaddr,
> +                               (int)version / 1000000, (int)(version % 1000000) / 1000);
> +                goto cleanup;
> +            }
> +            virCommandAddArgList(cmd, "--listen-address", ipaddr, NULL);
> +            VIR_FREE(ipaddr);
> +        }
> +    }
> 
>     /* If this is an isolated network, set the default route option
>      * (3) to be empty to avoid setting a default route that's
> @@ -741,24 +792,6 @@ networkBuildDnsmasqArgv(virNetworkObjPtr network,
>         }
>     }
> 
> -    /*
> -     * --interface does not actually work with dnsmasq < 2.47,
> -     * due to DAD for ipv6 addresses on the interface.
> -     *
> -     * virCommandAddArgList(cmd, "--interface", ipdef->bridge, NULL);
> -     *
> -     * So listen on all defined IPv[46] addresses
> -     */
> -    for (ii = 0;
> -         (tmpipdef = virNetworkDefGetIpByIndex(network->def, AF_UNSPEC, ii));
> -         ii++) {
> -        char *ipaddr = virSocketAddrFormat(&tmpipdef->address);
> -        if (!ipaddr)
> -            goto cleanup;
> -        virCommandAddArgList(cmd, "--listen-address", ipaddr, NULL);
> -        VIR_FREE(ipaddr);
> -    }
> -
>     if (ipdef) {
>         for (r = 0 ; r < ipdef->nranges ; r++) {
>             char *saddr = virSocketAddrFormat(&ipdef->ranges[r].start);
> diff --git a/tests/networkxml2argvdata/isolated-network.argv b/tests/networkxml2argvdata/isolated-network.argv
> index 13e77b2..3d8601e 100644
> --- a/tests/networkxml2argvdata/isolated-network.argv
> +++ b/tests/networkxml2argvdata/isolated-network.argv
> @@ -1,7 +1,8 @@
> - at DNSMASQ@ --strict-order --bind-interfaces \
> + at DNSMASQ@ --strict-order \
> --local=// --domain-needed --conf-file= \
> ---except-interface lo --dhcp-option=3 --no-resolv \
> +--bind-interfaces --except-interface lo \
> --listen-address 192.168.152.1 \
> +--dhcp-option=3 --no-resolv \
> --dhcp-range 192.168.152.2,192.168.152.254 \
> --dhcp-leasefile=/var/lib/libvirt/dnsmasq/private.leases --dhcp-lease-max=253 \
> --dhcp-no-override \
> diff --git a/tests/networkxml2argvdata/nat-network-dns-hosts.argv b/tests/networkxml2argvdata/nat-network-dns-hosts.argv
> index 03a0676..e5143ac 100644
> --- a/tests/networkxml2argvdata/nat-network-dns-hosts.argv
> +++ b/tests/networkxml2argvdata/nat-network-dns-hosts.argv
> @@ -1,4 +1,5 @@
> - at DNSMASQ@ --strict-order --bind-interfaces --domain=example.com \
> + at DNSMASQ@ --strict-order --domain=example.com \
> --local=/example.com/ --domain-needed \
> ---conf-file= --except-interface lo --listen-address 192.168.122.1 \
> +--conf-file= \
> +--bind-dynamic --interface virbr0 \
> --expand-hosts --addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts\
> diff --git a/tests/networkxml2argvdata/nat-network-dns-srv-record-minimal.argv b/tests/networkxml2argvdata/nat-network-dns-srv-record-minimal.argv
> index 210a60c..031da3f 100644
> --- a/tests/networkxml2argvdata/nat-network-dns-srv-record-minimal.argv
> +++ b/tests/networkxml2argvdata/nat-network-dns-srv-record-minimal.argv
> @@ -1,14 +1,13 @@
> @DNSMASQ@ \
> --strict-order \
> ---bind-interfaces \
> --local=// --domain-needed --conf-file= \
> ---except-interface lo \
> ---srv-host=name.tcp.,,,, \
> +--bind-interfaces --except-interface lo \
> --listen-address 192.168.122.1 \
> --listen-address 192.168.123.1 \
> ---listen-address 2001:db8:ac10:fe01::1 \
> ---listen-address 2001:db8:ac10:fd01::1 \
> +--listen-address fc00:db8:ac10:fe01::1 \
> +--listen-address fc00:db8:ac10:fd01::1 \
> --listen-address 10.24.10.1 \
> +--srv-host=name.tcp.,,,, \
> --dhcp-range 192.168.122.2,192.168.122.254 \
> --dhcp-leasefile=/var/lib/libvirt/dnsmasq/default.leases \
> --dhcp-lease-max=253 \
> diff --git a/tests/networkxml2argvdata/nat-network-dns-srv-record-minimal.xml b/tests/networkxml2argvdata/nat-network-dns-srv-record-minimal.xml
> index e9b7680..f6f24e1 100644
> --- a/tests/networkxml2argvdata/nat-network-dns-srv-record-minimal.xml
> +++ b/tests/networkxml2argvdata/nat-network-dns-srv-record-minimal.xml
> @@ -17,9 +17,9 @@
>   </ip>
>   <ip family='ipv4' address='192.168.123.1' netmask='255.255.255.0'>
>   </ip>
> -  <ip family='ipv6' address='2001:db8:ac10:fe01::1' prefix='64'>
> +  <ip family='ipv6' address='fc00:db8:ac10:fe01::1' prefix='64'>
>   </ip>
> -  <ip family='ipv6' address='2001:db8:ac10:fd01::1' prefix='64'>
> +  <ip family='ipv6' address='fc00:db8:ac10:fd01::1' prefix='64'>
>   </ip>
>   <ip family='ipv4' address='10.24.10.1'>
>   </ip>
> diff --git a/tests/networkxml2argvdata/nat-network-dns-srv-record.argv b/tests/networkxml2argvdata/nat-network-dns-srv-record.argv
> index 833d3cd..beff591 100644
> --- a/tests/networkxml2argvdata/nat-network-dns-srv-record.argv
> +++ b/tests/networkxml2argvdata/nat-network-dns-srv-record.argv
> @@ -1,14 +1,8 @@
> @DNSMASQ@ \
> --strict-order \
> ---bind-interfaces \
> --local=// --domain-needed --conf-file= \
> ---except-interface lo \
> +--bind-dynamic --interface virbr0 \
> --srv-host=name.tcp.test-domain-name,.,1024,10,10 \
> ---listen-address 192.168.122.1 \
> ---listen-address 192.168.123.1 \
> ---listen-address 2001:db8:ac10:fe01::1 \
> ---listen-address 2001:db8:ac10:fd01::1 \
> ---listen-address 10.24.10.1 \
> --dhcp-range 192.168.122.2,192.168.122.254 \
> --dhcp-leasefile=/var/lib/libvirt/dnsmasq/default.leases \
> --dhcp-lease-max=253 \
> diff --git a/tests/networkxml2argvdata/nat-network-dns-txt-record.argv b/tests/networkxml2argvdata/nat-network-dns-txt-record.argv
> index 3481507..fc164f6 100644
> --- a/tests/networkxml2argvdata/nat-network-dns-txt-record.argv
> +++ b/tests/networkxml2argvdata/nat-network-dns-txt-record.argv
> @@ -1,9 +1,7 @@
> - at DNSMASQ@ --strict-order --bind-interfaces \
> + at DNSMASQ@ --strict-order \
> --local=// --domain-needed --conf-file= \
> ---except-interface lo '--txt-record=example,example value' \
> ---listen-address 192.168.122.1 --listen-address 192.168.123.1 \
> ---listen-address 2001:db8:ac10:fe01::1 \
> ---listen-address 2001:db8:ac10:fd01::1 --listen-address 10.24.10.1 \
> +--bind-dynamic --interface virbr0 \
> +'--txt-record=example,example value' \
> --dhcp-range 192.168.122.2,192.168.122.254 \
> --dhcp-leasefile=/var/lib/libvirt/dnsmasq/default.leases \
> --dhcp-lease-max=253 --dhcp-no-override \
> diff --git a/tests/networkxml2argvdata/nat-network.argv b/tests/networkxml2argvdata/nat-network.argv
> index 37fd2fc..6303f76 100644
> --- a/tests/networkxml2argvdata/nat-network.argv
> +++ b/tests/networkxml2argvdata/nat-network.argv
> @@ -1,8 +1,6 @@
> - at DNSMASQ@ --strict-order --bind-interfaces \
> + at DNSMASQ@ --strict-order \
> --local=// --domain-needed --conf-file= \
> ---except-interface lo --listen-address 192.168.122.1 \
> ---listen-address 192.168.123.1 --listen-address 2001:db8:ac10:fe01::1 \
> ---listen-address 2001:db8:ac10:fd01::1 --listen-address 10.24.10.1 \
> +--bind-dynamic --interface virbr0 \
> --dhcp-range 192.168.122.2,192.168.122.254 \
> --dhcp-leasefile=/var/lib/libvirt/dnsmasq/default.leases \
> --dhcp-lease-max=253 --dhcp-no-override \
> diff --git a/tests/networkxml2argvdata/netboot-network.argv b/tests/networkxml2argvdata/netboot-network.argv
> index 5408eb7..9699e5c 100644
> --- a/tests/networkxml2argvdata/netboot-network.argv
> +++ b/tests/networkxml2argvdata/netboot-network.argv
> @@ -1,6 +1,6 @@
> - at DNSMASQ@ --strict-order --bind-interfaces --domain=example.com \
> + at DNSMASQ@ --strict-order --domain=example.com \
> --local=/example.com/ --domain-needed --conf-file= \
> ---except-interface lo --listen-address 192.168.122.1 \
> +--bind-interfaces --except-interface lo --listen-address 192.168.122.1 \
> --dhcp-range 192.168.122.2,192.168.122.254 \
> --dhcp-leasefile=/var/lib/libvirt/dnsmasq/netboot.leases \
> --dhcp-lease-max=253 --dhcp-no-override --expand-hosts \
> diff --git a/tests/networkxml2argvdata/netboot-proxy-network.argv b/tests/networkxml2argvdata/netboot-proxy-network.argv
> index 21e01e3..9ac3018 100644
> --- a/tests/networkxml2argvdata/netboot-proxy-network.argv
> +++ b/tests/networkxml2argvdata/netboot-proxy-network.argv
> @@ -1,6 +1,7 @@
> - at DNSMASQ@ --strict-order --bind-interfaces --domain=example.com \
> + at DNSMASQ@ --strict-order --domain=example.com \
> --local=/example.com/ --domain-needed --conf-file= \
> ---except-interface lo --listen-address 192.168.122.1 \
> +--bind-interfaces --except-interface lo \
> +--listen-address 192.168.122.1 \
> --dhcp-range 192.168.122.2,192.168.122.254 \
> --dhcp-leasefile=/var/lib/libvirt/dnsmasq/netboot.leases \
> --dhcp-lease-max=253 --dhcp-no-override --expand-hosts \
> diff --git a/tests/networkxml2argvdata/routed-network.argv b/tests/networkxml2argvdata/routed-network.argv
> index 9fedb2b..700c904 100644
> --- a/tests/networkxml2argvdata/routed-network.argv
> +++ b/tests/networkxml2argvdata/routed-network.argv
> @@ -1,4 +1,4 @@
> - at DNSMASQ@ --strict-order --bind-interfaces \
> + at DNSMASQ@ --strict-order \
> --local=// --domain-needed --conf-file= \
> ---except-interface lo --listen-address 192.168.122.1 \
> +--bind-dynamic --interface virbr1 \
> --addn-hosts=/var/lib/libvirt/dnsmasq/local.addnhosts\
> -- 
> 1.7.11.7
> 
> --
> libvir-list mailing list
> libvir-list at redhat.com
> https://www.redhat.com/mailman/listinfo/libvir-list

ACK. This all looks good.

--
Doug




More information about the libvir-list mailing list