[libvirt] [PATCH 5/5] qemu: launch bridge helper from libvirtd

Paolo Bonzini pbonzini at redhat.com
Fri Apr 19 21:13:19 UTC 2013


Il 18/04/2013 19:32, Laine Stump ha scritto:
> On 03/25/2013 10:25 AM, Paolo Bonzini wrote:
>> <source type='bridge'> uses a helper application to do the necessary
>> TUN/TAP setup to use an existing network bridge, thus letting
>> unprivileged users use TUN/TAP interfaces.
>>
>> However, libvirt should be preventing QEMU from running any setuid
>> programs at all, which would include this helper program.  From
>> a security POV, any setuid helper needs to be run by libvirtd itself,
>> not QEMU.
>>
>> This is what this patch does.  libvirt now invokes the setuid helper,
>> gets the TAP fd and then passes it to QEMU in the normal manner.
>> The path to the helper is specified in qemu.conf.
>>
>> As a small advantage, this adds a <target dev='tap0'/> element to the
>> XML of an active domain using <interface type='bridge'>.
>>
>> Signed-off-by: Paolo Bonzini <pbonzini at redhat.com>
>> ---
>>  src/qemu/qemu_command.c | 133 +++++++++++++++++++++++++++++++++++-------------
>>  src/qemu/qemu_command.h |   1 -
>>  src/qemu/qemu_hotplug.c |  25 +++------
>>  3 files changed, 106 insertions(+), 53 deletions(-)
>>
>> diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
>> index a0c278f..fa31102 100644
>> --- a/src/qemu/qemu_command.c
>> +++ b/src/qemu/qemu_command.c
>> @@ -28,12 +28,14 @@
>>  #include "qemu_capabilities.h"
>>  #include "qemu_bridge_filter.h"
>>  #include "cpu/cpu.h"
>> +#include "passfd.h"
>>  #include "viralloc.h"
>>  #include "virlog.h"
>>  #include "virarch.h"
>>  #include "virerror.h"
>>  #include "virutil.h"
>>  #include "virfile.h"
>> +#include "virnetdev.h"
>>  #include "virstring.h"
>>  #include "viruuid.h"
>>  #include "c-ctype.h"
>> @@ -46,6 +48,9 @@
>>  #include "base64.h"
>>  #include "device_conf.h"
>>  #include "virstoragefile.h"
>> +#if defined(__linux__)
>> +# include <linux/capability.h>
>> +#endif
>>  
>>  #include <sys/stat.h>
>>  #include <fcntl.h>
>> @@ -193,6 +198,77 @@ error:
>>  }
>>  
>>  
>> +/**
>> + * qemuCreateInBridgePortWithHelper:
>> + * @cfg: the configuration object in which the helper name is looked up
>> + * @brname: the bridge name
>> + * @ifname: the returned interface name
>> + * @macaddr: the returned MAC address
>> + * @tapfd: file descriptor return value for the new tap device
>> + * @flags: OR of virNetDevTapCreateFlags:
>> +
>> + *   VIR_NETDEV_TAP_CREATE_VNET_HDR
>> + *     - Enable IFF_VNET_HDR on the tap device
>> + *
>> + * This function creates a new tap device on a bridge using an external
>> + * helper.  The final name for the bridge will be stored in @ifname.
>> + *
>> + * Returns 0 in case of success or -1 on failure
>> + */
>> +static int qemuCreateInBridgePortWithHelper(virQEMUDriverConfigPtr cfg,
>> +                                            const char *brname,
>> +                                            char **ifname,
>> +                                            int *tapfd,
>> +                                            unsigned int flags)
>> +{
>> +    virCommandPtr cmd;
>> +    int status;
>> +    int pair[2] = { -1, -1 };
>> +
>> +    if ((flags & ~VIR_NETDEV_TAP_CREATE_VNET_HDR) != VIR_NETDEV_TAP_CREATE_IFUP)
>> +        return -1;
>> +
>> +    if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) {
>> +        virReportSystemError(errno, "%s", _("failed to create socket"));
>> +        return -1;
>> +    }
>> +
>> +    cmd = virCommandNew(cfg->bridgeHelperName);
>> +    if (flags & VIR_NETDEV_TAP_CREATE_VNET_HDR)
>> +        virCommandAddArgFormat(cmd, "--use-vnet");
>> +    virCommandAddArgFormat(cmd, "--br=%s", brname);
>> +    virCommandAddArgFormat(cmd, "--fd=%d", pair[1]);
>> +    virCommandTransferFD(cmd, pair[1]);
>> +    virCommandClearCaps(cmd);
>> +#ifdef CAP_NET_ADMIN
>> +    virCommandAllowCap(cmd, CAP_NET_ADMIN);
> 
> 
> I thought you had said that attempts to set caps would fail because
> CAP_SETPCAP was cleared for the non-privileged libvirtd.

I still prefer to note down the capabilities.  This way running libvirt
with CAP_SETPCAP as a permitted file capability (and only that one + the
effective bit set) should do the right thing.

I tested this by forcing this path for the system libvirt.

> 
>> +#endif
>> +    if (virCommandRunAsync(cmd, NULL) < 0) {
>> +        *tapfd = -1;
>> +        goto out;
>> +    }
>> +
>> +    do {
>> +        *tapfd = recvfd(pair[0], 0);
>> +    } while (*tapfd < 0 && errno == EINTR);
>> +    if (*tapfd < 0) {
>> +        virReportSystemError(errno, "%s",
>> +                             _("failed to retrieve file descriptor for interface"));
>> +        goto out;
>> +    }
>> +
>> +    if (virNetDevTapGetName(*tapfd, ifname) < 0 ||
>> +        virCommandWait(cmd, &status) < 0) {
>> +        VIR_FORCE_CLOSE(*tapfd);
>> +        *tapfd = -1;
>> +    }
>> +
>> +out:
> 
> We prefer to name these labels "cleanup" rather that "out" (although
> there are some occurrences of "out" still in the code).

Ok.

Paolo

>> +    virCommandFree(cmd);
>> +    VIR_FORCE_CLOSE(pair[0]);
>> +    return *tapfd < 0 ? -1 : 0;
>> +}
>> +
>>  int
>>  qemuNetworkIfaceConnect(virDomainDefPtr def,
>>                          virConnectPtr conn,
>> @@ -270,11 +346,17 @@ qemuNetworkIfaceConnect(virDomainDefPtr def,
>>          tap_create_flags |= VIR_NETDEV_TAP_CREATE_VNET_HDR;
>>      }
>>  
>> -    err = virNetDevTapCreateInBridgePort(brname, &net->ifname, &net->mac,
>> -                                         def->uuid, &tapfd,
>> -                                         virDomainNetGetActualVirtPortProfile(net),
>> -                                         virDomainNetGetActualVlan(net),
>> -                                         tap_create_flags);
>> +    if (cfg->privileged)
>> +        err = virNetDevTapCreateInBridgePort(brname, &net->ifname, &net->mac,
>> +                                             def->uuid, &tapfd,
>> +                                             virDomainNetGetActualVirtPortProfile(net),
>> +                                             virDomainNetGetActualVlan(net),
>> +                                             tap_create_flags);
>> +    else
>> +        err = qemuCreateInBridgePortWithHelper(cfg, brname,
>> +                                               &net->ifname,
>> +                                               &tapfd, tap_create_flags);
>> +
>>      virDomainAuditNetDevice(def, net, "/dev/net/tun", tapfd >= 0);
>>      if (err < 0) {
>>          if (template_ifname)
>> @@ -3746,7 +3828,6 @@ error:
>>  char *
>>  qemuBuildHostNetStr(virDomainNetDefPtr net,
>>                      virQEMUDriverPtr driver,
>> -                    virQEMUCapsPtr qemuCaps,
> 
> 
> qemuCaps might again become useful for this function in the future, so
> you may want to leave it here (marked as ATTRIBUTE_UNUSED) to reduce
> code churn.
> 
> 
>>                      char type_sep,
>>                      int vlan,
>>                      const char *tapfd,
>> @@ -3755,7 +3836,6 @@ qemuBuildHostNetStr(virDomainNetDefPtr net,
>>      bool is_tap = false;
>>      virBuffer buf = VIR_BUFFER_INITIALIZER;
>>      enum virDomainNetType netType = virDomainNetGetActualType(net);
>> -    const char *brname = NULL;
>>      virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
>>  
>>      if (net->script && netType != VIR_DOMAIN_NET_TYPE_ETHERNET) {
>> @@ -3773,14 +3853,6 @@ qemuBuildHostNetStr(virDomainNetDefPtr net,
>>       * through, -net tap,fd
>>       */
>>      case VIR_DOMAIN_NET_TYPE_BRIDGE:
>> -        if (!cfg->privileged &&
>> -            virQEMUCapsGet(qemuCaps, QEMU_CAPS_NETDEV_BRIDGE)) {
>> -            brname = virDomainNetGetActualBridgeName(net);
>> -            virBufferAsprintf(&buf, "bridge%cbr=%s", type_sep, brname);
>> -            type_sep = ',';
>> -            is_tap = true;
>> -            break;
>> -        }
>>      case VIR_DOMAIN_NET_TYPE_NETWORK:
>>      case VIR_DOMAIN_NET_TYPE_DIRECT:
>>          virBufferAsprintf(&buf, "tap%cfd=%s", type_sep, tapfd);
>> @@ -6681,26 +6753,17 @@ qemuBuildCommandLine(virConnectPtr conn,
>>  
>>              if (actualType == VIR_DOMAIN_NET_TYPE_NETWORK ||
>>                  actualType == VIR_DOMAIN_NET_TYPE_BRIDGE) {
>> -                /*
>> -                 * If type='bridge' then we attempt to allocate the tap fd here only if
>> -                 * running under a privilged user or -netdev bridge option is not
>> -                 * supported.
>> -                 */
>> -                if (actualType == VIR_DOMAIN_NET_TYPE_NETWORK ||
>> -                    cfg->privileged ||
>> -                    (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_NETDEV_BRIDGE))) {
>> -                    int tapfd = qemuNetworkIfaceConnect(def, conn, driver, net,
>> -                                                        qemuCaps);
>> -                    if (tapfd < 0)
>> -                        goto error;
>> +                int tapfd = qemuNetworkIfaceConnect(def, conn, driver, net,
>> +                                                    qemuCaps);
>> +                if (tapfd < 0)
>> +                    goto error;
>>  
>> -                    last_good_net = i;
>> -                    virCommandTransferFD(cmd, tapfd);
>> +                last_good_net = i;
>> +                virCommandTransferFD(cmd, tapfd);
>>  
>> -                    if (snprintf(tapfd_name, sizeof(tapfd_name), "%d",
>> -                                 tapfd) >= sizeof(tapfd_name))
>> -                        goto no_memory;
>> -                }
>> +                if (snprintf(tapfd_name, sizeof(tapfd_name), "%d",
>> +                             tapfd) >= sizeof(tapfd_name))
>> +                    goto no_memory;
>>              } else if (actualType == VIR_DOMAIN_NET_TYPE_DIRECT) {
>>                  int tapfd = qemuPhysIfaceConnect(def, driver, net,
>>                                                   qemuCaps, vmop);
>> @@ -6744,7 +6807,7 @@ qemuBuildCommandLine(virConnectPtr conn,
>>              if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NETDEV) &&
>>                  virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
>>                  virCommandAddArg(cmd, "-netdev");
>> -                if (!(host = qemuBuildHostNetStr(net, driver, qemuCaps,
>> +                if (!(host = qemuBuildHostNetStr(net, driver,
>>                                                   ',', vlan, tapfd_name,
>>                                                   vhostfd_name)))
>>                      goto error;
>> @@ -6768,7 +6831,7 @@ qemuBuildCommandLine(virConnectPtr conn,
>>              if (!(virQEMUCapsGet(qemuCaps, QEMU_CAPS_NETDEV) &&
>>                    virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE))) {
>>                  virCommandAddArg(cmd, "-net");
>> -                if (!(host = qemuBuildHostNetStr(net, driver, qemuCaps,
>> +                if (!(host = qemuBuildHostNetStr(net, driver,
>>                                                   ',', vlan, tapfd_name,
>>                                                   vhostfd_name)))
>>                      goto error;
>> diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h
>> index 17687f4..267a96e 100644
>> --- a/src/qemu/qemu_command.h
>> +++ b/src/qemu/qemu_command.h
>> @@ -71,7 +71,6 @@ qemuBuildChrDeviceStr (virDomainChrDefPtr serial,
>>  /* With vlan == -1, use netdev syntax, else old hostnet */
>>  char * qemuBuildHostNetStr(virDomainNetDefPtr net,
>>                             virQEMUDriverPtr driver,
>> -                           virQEMUCapsPtr qemuCaps,
>>                             char type_sep,
>>                             int vlan,
>>                             const char *tapfd,
>> diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
>> index de9edd4..6891efd 100644
>> --- a/src/qemu/qemu_hotplug.c
>> +++ b/src/qemu/qemu_hotplug.c
>> @@ -729,21 +729,12 @@ int qemuDomainAttachNetDevice(virConnectPtr conn,
>>  
>>      if (actualType == VIR_DOMAIN_NET_TYPE_BRIDGE ||
>>          actualType == VIR_DOMAIN_NET_TYPE_NETWORK) {
>> -        /*
>> -         * If type=bridge then we attempt to allocate the tap fd here only if
>> -         * running under a privilged user or -netdev bridge option is not
>> -         * supported.
>> -         */
>> -        if (actualType == VIR_DOMAIN_NET_TYPE_NETWORK ||
>> -            cfg->privileged ||
>> -            (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_NETDEV_BRIDGE))) {
>> -            if ((tapfd = qemuNetworkIfaceConnect(vm->def, conn, driver, net,
>> -                                                 priv->qemuCaps)) < 0)
>> -                goto cleanup;
>> -            iface_connected = true;
>> -            if (qemuOpenVhostNet(vm->def, net, priv->qemuCaps, &vhostfd) < 0)
>> -                goto cleanup;
>> -        }
>> +        if ((tapfd = qemuNetworkIfaceConnect(vm->def, conn, driver, net,
>> +                                             priv->qemuCaps)) < 0)
>> +            goto cleanup;
>> +        iface_connected = true;
>> +        if (qemuOpenVhostNet(vm->def, net, priv->qemuCaps, &vhostfd) < 0)
>> +            goto cleanup;
>>      } else if (actualType == VIR_DOMAIN_NET_TYPE_DIRECT) {
>>          if ((tapfd = qemuPhysIfaceConnect(vm->def, driver, net,
>>                                            priv->qemuCaps,
>> @@ -803,12 +794,12 @@ int qemuDomainAttachNetDevice(virConnectPtr conn,
>>  
>>      if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_NETDEV) &&
>>          virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE)) {
>> -        if (!(netstr = qemuBuildHostNetStr(net, driver, priv->qemuCaps,
>> +        if (!(netstr = qemuBuildHostNetStr(net, driver,
>>                                             ',', -1, tapfd_name,
>>                                             vhostfd_name)))
>>              goto cleanup;
>>      } else {
>> -        if (!(netstr = qemuBuildHostNetStr(net, driver, priv->qemuCaps,
>> +        if (!(netstr = qemuBuildHostNetStr(net, driver,
>>                                             ' ', vlan, tapfd_name,
>>                                             vhostfd_name)))
>>              goto cleanup;
> 
> I still don't like using qemu-bridge-helper, but this is better than the
> alternative of having qemu call it (although, due to the way that
> process capabilities works, we are unable to prevent a rogue qemu
> started by unprivileged libvirtd from calling it :-(
> 
> ACK to this patch (I think I would prefer you left the qemuCaps arg in,
> but others may disagree with me.)
> 




More information about the libvir-list mailing list