[libvirt] [PATCH 11/14] rpc: virnetserver: Fix race on srv->nclients_unauth

Marc Hartmayer mhartmay at linux.vnet.ibm.com
Thu Dec 21 11:29:45 UTC 2017


On Fri, Dec 15, 2017 at 07:16 PM +0100, John Ferlan <jferlan at redhat.com> wrote:
> On 12/12/2017 06:36 AM, Marc Hartmayer wrote:
>> There is a race between virNetServerProcessClients (main thread) and
>> remoteDispatchAuthList/remoteDispatchAuthPolkit/remoteSASLFinish (worker
>> thread) that can lead to decrementing srv->nclients_unauth when it's
>> zero. Since virNetServerCheckLimits relies on the value
>> srv->nclients_unauth the underrun causes libvirtd to stop accepting
>> new connections forever.
>>
>> Example race scenario (assuming libvirtd is using policykit and the
>> client is privileged):
>>   1. The client calls the RPC remoteDispatchAuthList =>
>>      remoteDispatchAuthList is executed on a worker thread (Thread
>>      T1). We're assuming now the execution stops for some time before
>>      the line 'virNetServerClientSetAuth(client, 0)'
>>   2. The client closes the connection irregularly. This causes the
>>      event loop to wake up and virNetServerProcessClient to be
>>      called (on the main thread T0). During the
>>      virNetServerProcessClients the srv lock is hold. The condition
>>      virNetServerClientNeedAuth(client) will be checked and as the
>>      authentication is not finished right now
>>      virNetServerTrackCompletedAuthLocked(srv) will be called =>
>>      --srv->nclients_unauth => 0
>>   3. The Thread T1 continues, marks the client as authenticated, and
>>      calls virNetServerTrackCompletedAuthLocked(srv) =>
>>      --srv->nclients_unauth => --0 => wrap around as nclient_unauth is
>>      unsigned
>>   4. virNetServerCheckLimits(srv) will disable the services forever
>>
>> To fix it, add an auth_pending field to the client struct so that it
>> is now possible to determine if the authentication process has already
>> been handled for this client.
>>
>> Setting the authentication method to none for the client in
>> virNetServerProcessClients is not a proper way to indicate that the
>> counter has been decremented, as this would imply that the client is
>> authenticated.
>>
>> Additionally, adjust the existing test cases for this new field.
>>
>
> In any case, I think perhaps this can be split into two patches - one
> that just sets/clears the auth_pending and then what's leftover as the
> fix.

I’m not sure it’s necessary as the correct usage of auth_pending is
already the fix. But let’s see then.

>
>> diff --git a/src/libvirt_remote.syms b/src/libvirt_remote.syms
>> index 7fcae970484d..cef2794e1122 100644
>> --- a/src/libvirt_remote.syms
>> +++ b/src/libvirt_remote.syms
>> @@ -138,6 +138,7 @@ virNetServerClientGetUNIXIdentity;
>>  virNetServerClientImmediateClose;
>>  virNetServerClientInit;
>>  virNetServerClientInitKeepAlive;
>> +virNetServerClientIsAuthPendingLocked;
>>  virNetServerClientIsClosedLocked;
>>  virNetServerClientIsLocal;
>>  virNetServerClientIsSecure;
>> @@ -152,6 +153,7 @@ virNetServerClientRemoteAddrStringURI;
>>  virNetServerClientRemoveFilter;
>>  virNetServerClientSendMessage;
>>  virNetServerClientSetAuthLocked;
>> +virNetServerClientSetAuthPendingLocked;
>>  virNetServerClientSetCloseHook;
>>  virNetServerClientSetDispatcher;
>>  virNetServerClientStartKeepAlive;
>> diff --git a/src/rpc/virnetserver.c b/src/rpc/virnetserver.c
>> index 72105cd9318f..6dd673ff3b23 100644
>> --- a/src/rpc/virnetserver.c
>> +++ b/src/rpc/virnetserver.c
>> @@ -286,7 +286,7 @@ int virNetServerAddClient(virNetServerPtr srv,
>>      srv->clients[srv->nclients-1] = virObjectRef(client);
>>
>>      virObjectLock(client);
>> -    if (virNetServerClientNeedAuthLocked(client))
>> +    if (virNetServerClientIsAuthPendingLocked(client))
>>          virNetServerTrackPendingAuthLocked(srv);
>>      virObjectUnlock(client);
>>
>> @@ -738,6 +738,25 @@ int virNetServerSetTLSContext(virNetServerPtr srv,
>>
>>
>>  /**
>> + * virNetServerSetClientAuthCompletedLocked:
>> + * @srv: server must be locked by the caller
>> + * @client: client must be locked by the caller
>> + *
>> + * Sets on the client that the authentication is no longer pending and
>> + * tracks on @srv that the authentication of this @client has been
>> + * completed.
>
> If the client authentication was pending, clear that pending and update
> the server tracking.

Changed it.

>
>> + */
>> +static void
>> +virNetServerSetClientAuthCompletedLocked(virNetServerPtr srv, virNetServerClientPtr client)
>
> One line for each argument.

Changed.

>
>> +{
>> +    if (virNetServerClientIsAuthPendingLocked(client)) {
>> +        virNetServerClientSetAuthPendingLocked(client, false);
>> +        virNetServerTrackCompletedAuthLocked(srv);
>> +    }
>> +}
>> +
>> +
>> +/**
>>   * virNetServerSetClientAuthenticated:
>>   * @srv: server must be unlocked
>>   * @client: client must be unlocked
>> @@ -752,7 +771,7 @@ virNetServerSetClientAuthenticated(virNetServerPtr srv, virNetServerClientPtr cl
>>      virObjectLock(srv);
>>      virObjectLock(client);
>>      virNetServerClientSetAuthLocked(client, VIR_NET_SERVER_SERVICE_AUTH_NONE);
>> -    virNetServerTrackCompletedAuthLocked(srv);
>> +    virNetServerSetClientAuthCompletedLocked(srv, client);
>>      virNetServerCheckLimits(srv);
>>      virObjectUnlock(client);
>>      virObjectUnlock(srv);
>> @@ -870,8 +889,9 @@ virNetServerProcessClients(virNetServerPtr srv)
>>          if (virNetServerClientIsClosedLocked(client)) {
>>              VIR_DELETE_ELEMENT(srv->clients, i, srv->nclients);
>>
>> -            if (virNetServerClientNeedAuthLocked(client))
>> -                virNetServerTrackCompletedAuthLocked(srv);
>> +            /* Mark the authentication for this client as no longer
>> +             * pending */
>
> Some may say a superfluous comment... I'd probably say make it one line
>
> /* Update server authentication tracking */

Changed.

>
>> +            virNetServerSetClientAuthCompletedLocked(srv, client);
>>              virObjectUnlock(client);
>>
>>              virNetServerCheckLimits(srv);
>> diff --git a/src/rpc/virnetserverclient.c b/src/rpc/virnetserverclient.c
>> index 6adc3ec3ec01..5224bc7de1d5 100644
>> --- a/src/rpc/virnetserverclient.c
>> +++ b/src/rpc/virnetserverclient.c
>> @@ -71,6 +71,7 @@ struct _virNetServerClient
>>      bool delayedClose;
>>      virNetSocketPtr sock;
>>      int auth;
>> +    bool auth_pending;
>>      bool readonly;
>>  #if WITH_GNUTLS
>>      virNetTLSContextPtr tlsCtxt;
>> @@ -375,6 +376,7 @@ static virNetServerClientPtr
>>  virNetServerClientNewInternal(unsigned long long id,
>>                                virNetSocketPtr sock,
>>                                int auth,
>> +                              bool auth_pending,
>>  #ifdef WITH_GNUTLS
>>                                virNetTLSContextPtr tls,
>>  #endif
>> @@ -384,6 +386,12 @@ virNetServerClientNewInternal(unsigned long long id,
>>  {
>>      virNetServerClientPtr client;
>>
>> +    /* If the used authentication method implies that the new client
>> +     * is automatically authenticated, the authentication cannot be
>> +     * pending */
>> +    if (auth_pending && virNetServerClientAuthMethodImpliesAuthenticated(auth))
>                            ^^^^^^
> Why check twice? I'm not clear on the need for this check.  The setting
> of auth_pending is based on @auth in the caller or perhaps the value of
> auth_pending already. If there's some sort of check to be made perhaps
> it's in ExecRestart.

Normally I would prefer an assert here… The intent behind this guard was
that it should prevent the wrong usage of this function.

But I’ll move the check.

>
> Also, if we returned NULL here without some sort of error, then couldn't
> we get the libvirt failed for some reason error message which is always
> not fun to track down.

Does it makes sense to leave the check here (and additionally add it to
ExecRestart) and throw an error message here?

>
>> +        return NULL;
>> +
>>      if (virNetServerClientInitialize() < 0)
>>          return NULL;
>>
>> @@ -393,6 +401,7 @@ virNetServerClientNewInternal(unsigned long long id,
>>      client->id = id;
>>      client->sock = virObjectRef(sock);
>>      client->auth = auth;
>> +    client->auth_pending = auth_pending;
>>      client->readonly = readonly;
>>  #ifdef WITH_GNUTLS
>>      client->tlsCtxt = virObjectRef(tls);
>> @@ -440,6 +449,7 @@ virNetServerClientPtr virNetServerClientNew(unsigned long long id,
>>  {
>>      virNetServerClientPtr client;
>>      time_t now;
>> +    bool auth_pending = !virNetServerClientAuthMethodImpliesAuthenticated(auth);
>
> I think it's much easier for readability to have result be auth !=
> VIR_NET_SERVER_SERVICE_AUTH_NONE instead of this call... Perhaps even
> easier to find all instances of AUTH_NONE.

This function/condition is used in four places. But sure, if you’re
still saying it’s easier to read and to understand that a user is
authenticated (and therefore the authentication is no longer pending) if
auth_method == VIR_NET_SERVER_SERVICE_AUTH_NONE then I’ll replace this
function at all places :)

>
>>
>>      VIR_DEBUG("sock=%p auth=%d tls=%p", sock, auth,
>>  #ifdef WITH_GNUTLS
>> @@ -454,7 +464,7 @@ virNetServerClientPtr virNetServerClientNew(unsigned long long id,

[…snip…]

>> @@ -1545,6 +1569,22 @@ virNetServerClientNeedAuth(virNetServerClientPtr client)
>>  }
>>
>>
>> +/* The caller must hold the lock for @client */
>> +void
>> +virNetServerClientSetAuthPendingLocked(virNetServerClientPtr client, bool auth_pending)
>
> One line per argument

Changed.

>
> John
>
>> +{
>> +    client->auth_pending = auth_pending;

[…snip]

--
Beste Grüße / Kind regards
   Marc Hartmayer

IBM Deutschland Research & Development GmbH
Vorsitzende des Aufsichtsrats: Martina Koederitz
Geschäftsführung: Dirk Wittkopp
Sitz der Gesellschaft: Böblingen
Registergericht: Amtsgericht Stuttgart, HRB 243294





More information about the libvir-list mailing list