nodedev: fix race in API usage vs initial device enumeration

During startup the udev node device driver impl uses a background thread
to populate the list of devices to avoid blocking the daemon startup
entirely. There is no synchronization to the public APIs, so it is
possible for an application to start calling APIs before the device
initialization is complete.

This was not a problem in the old approach where libvirtd was started
on boot, as initialization would easily complete before any APIs were
called.

With the use of socket activation, however, APIs are invoked from the
very moment the daemon starts. This is easily seen by doing a

  'virsh -c nodedev:///system list'

the first time it runs it will only show one or two devices. The second
time it runs it will show all devices. The solution is to introduce a
flag and condition variable for APIs to synchronize against before
returning any data.

Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
Daniel P. Berrangé 2020-03-13 11:53:25 +00:00
parent 530ac28861
commit 008abeb03c
4 changed files with 74 additions and 0 deletions

View File

@ -36,6 +36,8 @@ typedef struct _virNodeDeviceDriverState virNodeDeviceDriverState;
typedef virNodeDeviceDriverState *virNodeDeviceDriverStatePtr; typedef virNodeDeviceDriverState *virNodeDeviceDriverStatePtr;
struct _virNodeDeviceDriverState { struct _virNodeDeviceDriverState {
virMutex lock; virMutex lock;
virCond initCond;
bool initialized;
/* pid file FD, ensures two copies of the driver can't use the same root */ /* pid file FD, ensures two copies of the driver can't use the same root */
int lockFD; int lockFD;

View File

@ -156,6 +156,22 @@ nodeDeviceUnlock(void)
} }
static int
nodeDeviceWaitInit(void)
{
nodeDeviceLock();
while (!driver->initialized) {
if (virCondWait(&driver->initCond, &driver->lock) < 0) {
virReportSystemError(errno, "%s",
_("failed to wait on condition"));
nodeDeviceUnlock();
return -1;
}
}
nodeDeviceUnlock();
return 0;
}
int int
nodeNumOfDevices(virConnectPtr conn, nodeNumOfDevices(virConnectPtr conn,
const char *cap, const char *cap,
@ -166,6 +182,9 @@ nodeNumOfDevices(virConnectPtr conn,
virCheckFlags(0, -1); virCheckFlags(0, -1);
if (nodeDeviceWaitInit() < 0)
return -1;
return virNodeDeviceObjListNumOfDevices(driver->devs, conn, cap, return virNodeDeviceObjListNumOfDevices(driver->devs, conn, cap,
virNodeNumOfDevicesCheckACL); virNodeNumOfDevicesCheckACL);
} }
@ -183,6 +202,9 @@ nodeListDevices(virConnectPtr conn,
virCheckFlags(0, -1); virCheckFlags(0, -1);
if (nodeDeviceWaitInit() < 0)
return -1;
return virNodeDeviceObjListGetNames(driver->devs, conn, return virNodeDeviceObjListGetNames(driver->devs, conn,
virNodeListDevicesCheckACL, virNodeListDevicesCheckACL,
cap, names, maxnames); cap, names, maxnames);
@ -199,6 +221,9 @@ nodeConnectListAllNodeDevices(virConnectPtr conn,
if (virConnectListAllNodeDevicesEnsureACL(conn) < 0) if (virConnectListAllNodeDevicesEnsureACL(conn) < 0)
return -1; return -1;
if (nodeDeviceWaitInit() < 0)
return -1;
return virNodeDeviceObjListExport(conn, driver->devs, devices, return virNodeDeviceObjListExport(conn, driver->devs, devices,
virConnectListAllNodeDevicesCheckACL, virConnectListAllNodeDevicesCheckACL,
flags); flags);
@ -228,6 +253,9 @@ nodeDeviceLookupByName(virConnectPtr conn,
virNodeDeviceDefPtr def; virNodeDeviceDefPtr def;
virNodeDevicePtr device = NULL; virNodeDevicePtr device = NULL;
if (nodeDeviceWaitInit() < 0)
return NULL;
if (!(obj = nodeDeviceObjFindByName(name))) if (!(obj = nodeDeviceObjFindByName(name)))
return NULL; return NULL;
def = virNodeDeviceObjGetDef(obj); def = virNodeDeviceObjGetDef(obj);
@ -256,6 +284,9 @@ nodeDeviceLookupSCSIHostByWWN(virConnectPtr conn,
virCheckFlags(0, NULL); virCheckFlags(0, NULL);
if (nodeDeviceWaitInit() < 0)
return NULL;
if (!(obj = virNodeDeviceObjListFindSCSIHostByWWNs(driver->devs, if (!(obj = virNodeDeviceObjListFindSCSIHostByWWNs(driver->devs,
wwnn, wwpn))) wwnn, wwpn)))
return NULL; return NULL;
@ -470,6 +501,10 @@ nodeDeviceCreateXML(virConnectPtr conn,
const char *virt_type = NULL; const char *virt_type = NULL;
virCheckFlags(0, NULL); virCheckFlags(0, NULL);
if (nodeDeviceWaitInit() < 0)
return NULL;
virt_type = virConnectGetType(conn); virt_type = virConnectGetType(conn);
if (!(def = virNodeDeviceDefParseString(xmlDesc, CREATE_DEVICE, virt_type))) if (!(def = virNodeDeviceDefParseString(xmlDesc, CREATE_DEVICE, virt_type)))
@ -512,6 +547,9 @@ nodeDeviceDestroy(virNodeDevicePtr device)
g_autofree char *wwpn = NULL; g_autofree char *wwpn = NULL;
unsigned int parent_host; unsigned int parent_host;
if (nodeDeviceWaitInit() < 0)
return -1;
if (!(obj = nodeDeviceObjFindByName(device->name))) if (!(obj = nodeDeviceObjFindByName(device->name)))
return -1; return -1;
def = virNodeDeviceObjGetDef(obj); def = virNodeDeviceObjGetDef(obj);
@ -564,6 +602,9 @@ nodeConnectNodeDeviceEventRegisterAny(virConnectPtr conn,
if (virConnectNodeDeviceEventRegisterAnyEnsureACL(conn) < 0) if (virConnectNodeDeviceEventRegisterAnyEnsureACL(conn) < 0)
return -1; return -1;
if (nodeDeviceWaitInit() < 0)
return -1;
if (virNodeDeviceEventStateRegisterID(conn, driver->nodeDeviceEventState, if (virNodeDeviceEventStateRegisterID(conn, driver->nodeDeviceEventState,
device, eventID, callback, device, eventID, callback,
opaque, freecb, &callbackID) < 0) opaque, freecb, &callbackID) < 0)
@ -580,6 +621,9 @@ nodeConnectNodeDeviceEventDeregisterAny(virConnectPtr conn,
if (virConnectNodeDeviceEventDeregisterAnyEnsureACL(conn) < 0) if (virConnectNodeDeviceEventDeregisterAnyEnsureACL(conn) < 0)
return -1; return -1;
if (nodeDeviceWaitInit() < 0)
return -1;
if (virObjectEventStateDeregisterID(conn, if (virObjectEventStateDeregisterID(conn,
driver->nodeDeviceEventState, driver->nodeDeviceEventState,
callbackID, true) < 0) callbackID, true) < 0)

View File

@ -611,6 +611,15 @@ nodeStateInitialize(bool privileged G_GNUC_UNUSED,
VIR_FREE(driver); VIR_FREE(driver);
return VIR_DRV_STATE_INIT_ERROR; return VIR_DRV_STATE_INIT_ERROR;
} }
if (virCondInit(&driver->initCond) < 0) {
virReportSystemError(errno, "%s",
_("Unable to initialize condition variable"));
virMutexDestroy(&driver->lock);
VIR_FREE(driver);
return VIR_DRV_STATE_INIT_ERROR;
}
nodeDeviceLock(); nodeDeviceLock();
if (privileged) { if (privileged) {
@ -701,6 +710,11 @@ nodeStateInitialize(bool privileged G_GNUC_UNUSED,
} }
VIR_FREE(udi); VIR_FREE(udi);
nodeDeviceLock();
driver->initialized = true;
nodeDeviceUnlock();
virCondBroadcast(&driver->initCond);
return VIR_DRV_STATE_INIT_COMPLETE; return VIR_DRV_STATE_INIT_COMPLETE;
failure: failure:
@ -733,6 +747,7 @@ nodeStateCleanup(void)
VIR_FREE(driver->stateDir); VIR_FREE(driver->stateDir);
nodeDeviceUnlock(); nodeDeviceUnlock();
virCondDestroy(&driver->initCond);
virMutexDestroy(&driver->lock); virMutexDestroy(&driver->lock);
VIR_FREE(driver); VIR_FREE(driver);
return 0; return 0;

View File

@ -1477,6 +1477,7 @@ nodeStateCleanup(void)
virPidFileRelease(driver->stateDir, "driver", driver->lockFD); virPidFileRelease(driver->stateDir, "driver", driver->lockFD);
VIR_FREE(driver->stateDir); VIR_FREE(driver->stateDir);
virCondDestroy(&driver->initCond);
virMutexDestroy(&driver->lock); virMutexDestroy(&driver->lock);
VIR_FREE(driver); VIR_FREE(driver);
@ -1744,6 +1745,11 @@ nodeStateInitializeEnumerate(void *opaque)
if (udevEnumerateDevices(udev) != 0) if (udevEnumerateDevices(udev) != 0)
goto error; goto error;
nodeDeviceLock();
driver->initialized = true;
nodeDeviceUnlock();
virCondBroadcast(&driver->initCond);
return; return;
error: error:
@ -1806,6 +1812,13 @@ nodeStateInitialize(bool privileged,
VIR_FREE(driver); VIR_FREE(driver);
return VIR_DRV_STATE_INIT_ERROR; return VIR_DRV_STATE_INIT_ERROR;
} }
if (virCondInit(&driver->initCond) < 0) {
virReportSystemError(errno, "%s",
_("Unable to initialize condition variable"));
virMutexDestroy(&driver->lock);
VIR_FREE(driver);
return VIR_DRV_STATE_INIT_ERROR;
}
driver->privileged = privileged; driver->privileged = privileged;