[libvirt] [PATCH 2/2] virSecurityManagerMetadataLock: Skip over duplicate paths

Michal Privoznik mprivozn at redhat.com
Thu Jul 18 11:30:51 UTC 2019


On 7/18/19 11:28 AM, Daniel P. Berrangé wrote:
> On Thu, Jul 18, 2019 at 11:14:49AM +0200, Michal Privoznik wrote:
>> If there are two paths on the list that are the same we need to
>> lock it only once. Because when we try to lock it the second time
>> then open() fails. And if it didn't, locking it the second time
>> would fail for sure. After all, it is sufficient to lock all
>> paths just once satisfy the caller.
>>
>> Reported-by: Daniel Henrique Barboza <danielhb413 at gmail.com>
>> Signed-off-by: Michal Privoznik <mprivozn at redhat.com>
>> ---
>>   src/security/security_manager.c | 23 +++++++++++++++++++++--
>>   1 file changed, 21 insertions(+), 2 deletions(-)
> 
> Reviewed-by: Daniel P. Berrangé <berrange at redhat.com>
> 
> 
>>
>> diff --git a/src/security/security_manager.c b/src/security/security_manager.c
>> index ade2c96141..7c905f0785 100644
>> --- a/src/security/security_manager.c
>> +++ b/src/security/security_manager.c
>> @@ -1294,16 +1294,35 @@ virSecurityManagerMetadataLock(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
>>        * paths A B and there's another that is trying to lock them
>>        * in reversed order a deadlock might occur.  But if we sort
>>        * the paths alphabetically then both processes will try lock
>> -     * paths in the same order and thus no deadlock can occur. */
>> +     * paths in the same order and thus no deadlock can occur.
>> +     * Lastly, it makes searching for duplicate paths below
>> +     * simpler. */
>>       qsort(paths, npaths, sizeof(*paths), cmpstringp);
>>   
>>       for (i = 0; i < npaths; i++) {
>>           const char *p = paths[i];
>>           struct stat sb;
>> +        size_t j;
>>           int retries = 10 * 1000;
>>           int fd;
>>   
>> -        if (!p || stat(p, &sb) < 0)
>> +        if (!p)
>> +            continue;
>> +
>> +        /* If there's a duplicate path on the list, skip it over.
>> +         * Not only we would fail open()-ing it the second time,
>> +         * we would deadlock with ourselves trying to lock it the
>> +         * second time. After all, we've locked it when iterating
>> +         * over it the first time. */
> 
> Presumably this deals with the problem at cold boot. Is it possible
> to hit the same problem when we have cold plugged one device and
> then later try to hotplug another device using the same resource,
> or do the SRIOV assignment grouping requirements make the hotplug
> impossible ?

Okay, so digging deeper I think I know what the problem is. In general, 
it is of course possible to open a file multiple times, but that is not 
true for "/dev/vfio/N" which can be opened exactly one time. Indeed, 
looking into vfio_group_fops_open() in kernel.git/drivers/vfio/vfio.c if 
a group is already opened, then -EBUSY is returned (what we've seen in 
the error message). In fact, git log blames v3.11-rc1~46^2~1 which 
explicitly disallowed multiple open()-s.

So, in theory, our code works, because we open() the files we are 
chown()-ing and close them immediatelly after. So the problem in which 
we try to lock /dev/vfio/N multiple times won't happen. However, since 
qemu already has the device opened, we will fail opening it.

I can see two options here:

1) ignore this specific error in virSecurityManagerMetadataLock(). This 
will of course mean that the caller (virSecurityDACTransactionRun()) 
will chown() the /dev/vfio/N file without a lock, but then again, we 
have namespaces and there can't be any other domain using the path. And 
also, we don't really chown() if the file already has the correct owner 
- which is going to be 99.99% cases unless there would be different 
seclabels for two PCI devices (do we even allow <seclabel/> for 
<hostdev/>?).

2) Make virSecurityDACSetHostdevLabel() and 
virSecurityDACRestoreHostdevLabel() be NO-OP if a domain already/still 
has a device from the same IOMMU group like the one we're 
attaching/detaching. If these are NO-OPs then no relabel is done and 
thus the whole code I'm modifying in 1) is not even called.

I have a preference for 1) because the code will be cleaner IMO.

Michal




More information about the libvir-list mailing list