Checking FreeRDP with the PVS-Studio analyzer

    Picture 2

    FreeRDP is an open source implementation of Remote Desktop Protocol (RDP), a protocol that implements remote computer control developed by Microsoft. The project supports many platforms, including Windows, Linux, macOS, and even iOS with Android. This project was selected as the first in a series of articles devoted to checking RDP clients using the PVS-Studio static analyzer.

    A bit of history


    The FreeRDP project came after Microsoft unveiled the specifications of its proprietary RDP protocol. At that time, there was an rdesktop client, the implementation of which is based on the results of Reverse Engineering.

    In the process of implementing the protocol, it became more difficult to add new functionality due to the then existing project architecture. Changes in it created a conflict between developers, which led to the creation of the rdesktop fork - FreeRDP. Further distribution of the product was limited by the GPLv2 license, as a result of which a decision was made to re-license to Apache License v2. However, not everyone agreed to change the license of their code, so the developers decided to rewrite the project, as a result of which we have a modern look of the code base.

    More details about the history of the project can be found in the official blog note: “The history of the FreeRDP project”. PVS-Studio was

    used as a tool for detecting errors and potential vulnerabilities in the code . It is a static code analyzer for C, C ++, C #, and Java, available on Windows, Linux, and macOS. The article presents only those errors that seemed to me the most interesting.



    Memory leak


    V773 The function was exited without releasing the 'cwd' pointer. A memory leak is possible. environment.c 84

    DWORD GetCurrentDirectoryA(DWORD nBufferLength, LPSTR lpBuffer)
    {
      char* cwd;
      ....
      cwd = getcwd(NULL, 0);
      ....
      if (lpBuffer == NULL)
      {
        free(cwd);
        return 0;
      }
      if ((length + 1) > nBufferLength)
      {
        free(cwd);
        return (DWORD) (length + 1);
      }
      memcpy(lpBuffer, cwd, length + 1);
      return length;
      ....
    }

    This fragment was taken from the winpr subsystem that implements the WINAPI wrapper for non-Windows systems, i.e. It is a lightweight counterpart to Wine. There is a leak here: the memory allocated by the getcwd function is freed up only during special cases. To fix the error, add a call to free after memcpy .

    Going beyond the bounds of an array


    V557 Array overrun is possible. The value of 'event-> EventHandlerCount' index could reach 32. PubSub.c 117

    #define MAX_EVENT_HANDLERS  32
    struct _wEventType
    {
      ....
      int EventHandlerCount;
      pEventHandler EventHandlers[MAX_EVENT_HANDLERS];
    };
    int PubSub_Subscribe(wPubSub* pubSub, const char* EventName,
          pEventHandler EventHandler)
    {
      ....
      if (event->EventHandlerCount <= MAX_EVENT_HANDLERS)
      {
        event->EventHandlers[event->EventHandlerCount] = EventHandler;
        event->EventHandlerCount++;
      }
      ....
    }

    In this example, a new item is added to the list, even if the number of items has reached the maximum. It is enough to replace the operator <= with < so as not to go beyond the boundaries of the array.

    Another error of this type was found:

    • V557 Array overrun is possible. The value of 'iBitmapFormat' index could reach 8. orders.c 2623

    Typos


    Fragment 1


    V547 Expression '! Pipe-> In' is always false. MessagePipe.c 63

    wMessagePipe* MessagePipe_New()
    {
      ....
      pipe->In = MessageQueue_New(NULL);
      if (!pipe->In)
        goto error_in;
      pipe->Out = MessageQueue_New(NULL);
      if (!pipe->In) // <=
        goto error_out;
      ....
    }

    Here we see the usual typo: in the second condition, the same variable is checked as in the first. Most likely, the error appeared as a result of unsuccessful copying of the code.

    Fragment 2


    V760 Two identical blocks of text were found. The second block begins from line 771. tsg.c 770

    typedef struct _TSG_PACKET_VERSIONCAPS
    {
      ....
      UINT16 majorVersion;
      UINT16 minorVersion;
      ....
    } TSG_PACKET_VERSIONCAPS, *PTSG_PACKET_VERSIONCAPS;
    static BOOL TsProxyCreateTunnelReadResponse(....)
    {
      ....
      PTSG_PACKET_VERSIONCAPS versionCaps = NULL;
      ....
      /* MajorVersion (2 bytes) */
      Stream_Read_UINT16(pdu->s, versionCaps->majorVersion);
      /* MinorVersion (2 bytes) */
      Stream_Read_UINT16(pdu->s, versionCaps->majorVersion);
      ....
    }

    Another typo: a comment on the code implies that minorVersion should come from the stream , however, reading takes place in a variable named majorVersion . However, I am not familiar with the protocol, so this is just an assumption.

    Fragment 3


    V524 It is odd that the body of 'trio_index_last' function is fully equivalent to the body of 'trio_index' function. triostr.c 933

    /**
       Find first occurrence of a character in a string.
       ....
     */
    TRIO_PUBLIC_STRING char *
    trio_index
    TRIO_ARGS2((string, character),
         TRIO_CONST char *string,
         int character)
    {
      assert(string);
      return strchr(string, character);
    }
    /**
       Find last occurrence of a character in a string.
       ....
     */
    TRIO_PUBLIC_STRING char *
    trio_index_last
    TRIO_ARGS2((string, character),
         TRIO_CONST char *string,
         int character)
    {
      assert(string);
      return strchr(string, character);
    }

    Judging by the comment, the trio_index function finds the first match of a character in a string when trio_index_last is the last. But the bodies of these functions are identical! Most likely, this is a typo, and in the trio_index_last function you need to use strrchr instead of strchr . Then the behavior will be expected.

    Fragment 4


    V769 The 'data' pointer in the expression equals nullptr. The resulting value of arithmetic operations on this pointer is senseless and it should not be used. nsc_encode.c 124

    static BOOL nsc_encode_argb_to_aycocg(NSC_CONTEXT* context,
                                          const BYTE* data,
                                          UINT32 scanline)
    {
      ....
      if (!context || data || (scanline == 0))
        return FALSE;
      ....
      src = data + (context->height - 1 - y) * scanline;
      ....
    }

    Looks like they accidentally missed the negation operator ! next to data . It is strange that this went unnoticed.

    Fragment 5


    V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 213, 222. rdpei_common.c 213

    BOOL rdpei_write_4byte_unsigned(wStream* s, UINT32 value)
    {
      BYTE byte;
      if (value <= 0x3F)
      {
        ....
      }
      else if (value <= 0x3FFF)
      {
        ....
      }
      else if (value <= 0x3FFFFF)
      {
        byte = (value >> 16) & 0x3F;
        Stream_Write_UINT8(s, byte | 0x80);
        byte = (value >> 8) & 0xFF;
        Stream_Write_UINT8(s, byte);
        byte = (value & 0xFF);
        Stream_Write_UINT8(s, byte);
      }
      else if (value <= 0x3FFFFF)
      {
        byte = (value >> 24) & 0x3F;
        Stream_Write_UINT8(s, byte | 0xC0);
        byte = (value >> 16) & 0xFF;
        Stream_Write_UINT8(s, byte);
        byte = (value >> 8) & 0xFF;
        Stream_Write_UINT8(s, byte);
        byte = (value & 0xFF);
        Stream_Write_UINT8(s, byte);
      }
      ....
    }

    The last two conditions are the same: apparently, someone forgot to check them after copying. According to the code, it is noticeable that the last part works with four-byte values, so we can assume that the last condition should be value <= 0x3FFFFFFF .

    Another error of this type was found:

    • V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 169, 173. file.c 169

    Input Validation


    Fragment 1


    V547 Expression 'strcat (target, source)! = NULL' is always true. triostr.c 425

    TRIO_PUBLIC_STRING int
    trio_append
    TRIO_ARGS2((target, source),
         char *target,
         TRIO_CONST char *source)
    {
      assert(target);
      assert(source);
      return (strcat(target, source) != NULL);
    }

    Checking the result of the function in this example is incorrect. The strcat function returns a pointer to the final version of the string, i.e. first parameter passed. In this case, it is target . However, if it is NULL , then it is too late to check, since in the strcat function it will be dereferenced.

    Fragment 2


    V547 Expression 'cache' is always true. glyph.c 730

    typedef struct rdp_glyph_cache rdpGlyphCache;
    struct rdp_glyph_cache
    {
      ....
      GLYPH_CACHE glyphCache[10];
      ....
    };
    void glyph_cache_free(rdpGlyphCache* glyphCache)
    {
      ....
      GLYPH_CACHE* cache = glyphCache->glyphCache;
      if (cache)
      {
        ....
      }
      ....
    }

    In this case, the cache variable is assigned the address of the static array glyphCache-> glyphCache . Thus, the if (cache) check can be omitted.

    Resource Management Error


    V1005 The resource was acquired using 'CreateFileA' function but was released using incompatible 'fclose' function. certificate.c 447

    BOOL certificate_data_replace(rdpCertificateStore* certificate_store,
                                  rdpCertificateData* certificate_data)
    {
      HANDLE fp;
      ....
      fp = CreateFileA(certificate_store->file, GENERIC_READ | GENERIC_WRITE, 0,
                       NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
      ....
      if (size < 1)
      {
        CloseHandle(fp);
        return FALSE;
      }
      ....
      if (!data)
      {
        fclose(fp);
        return FALSE;
      }
      ....
    }

    The fp file descriptor created by calling the CreateFile function is mistakenly closed by the fclose function from the standard library, rather than CloseHandle .

    Same conditions


    V581 The conditional expressions of the 'if' statements located alongside each other are identical. Check lines: 269, 283. ndr_structure.c 283

    void NdrComplexStructBufferSize(PMIDL_STUB_MESSAGE pStubMsg,
          unsigned char* pMemory, PFORMAT_STRING pFormat)
    {
      ....
      if (conformant_array_description)
      {
        ULONG size;
        unsigned char array_type;
        array_type = conformant_array_description[0];
        size = NdrComplexStructMemberSize(pStubMsg, pFormat);
        WLog_ERR(TAG, "warning: NdrComplexStructBufferSize array_type: "
          "0x%02X unimplemented", array_type);
        NdrpComputeConformance(pStubMsg, pMemory + size,
          conformant_array_description);
        NdrpComputeVariance(pStubMsg, pMemory + size,
          conformant_array_description);
        MaxCount = pStubMsg->MaxCount;
        ActualCount = pStubMsg->ActualCount;
        Offset = pStubMsg->Offset;
      }
      if (conformant_array_description)
      {
        unsigned char array_type;
        array_type = conformant_array_description[0];
        pStubMsg->MaxCount = MaxCount;
        pStubMsg->ActualCount = ActualCount;
        pStubMsg->Offset = Offset;
        WLog_ERR(TAG, "warning: NdrComplexStructBufferSize array_type: "
          "0x%02X unimplemented", array_type);
      }
      ....
    }

    Perhaps this example is not a mistake. However, both conditions contain the same message, one of which, most likely, can be removed.

    Clearing Null Pointers


    V575 The null pointer is passed into 'free' function. Inspect the first argument. smartcard_pcsc.c 875

    WINSCARDAPI LONG WINAPI PCSC_SCardListReadersW(
      SCARDCONTEXT hContext,
      LPCWSTR mszGroups,
      LPWSTR mszReaders,
      LPDWORD pcchReaders)
    {
      LPSTR mszGroupsA = NULL;
      ....
      mszGroups = NULL; /* mszGroups is not supported by pcsc-lite */
      if (mszGroups)
        ConvertFromUnicode(CP_UTF8,0, mszGroups, -1, 
                           (char**) &mszGroupsA, 0,
                           NULL, NULL);
      status = PCSC_SCardListReaders_Internal(hContext, mszGroupsA,
                                              (LPSTR) &mszReadersA,
                                              pcchReaders);
      if (status == SCARD_S_SUCCESS)
      {
        ....
      }
      free(mszGroupsA);
      ....
    }

    You can pass a null pointer to the free function and the analyzer knows about this. But if a situation is revealed in which the pointer is always passed zero, as in this fragment, a warning will be issued.

    The mszGroupsA pointer is initially NULL and is not initialized anywhere else. The only branch of code where the pointer could be initialized is unreachable.

    There were other posts like this:

    • V575 The null pointer is passed into 'free' function. Inspect the first argument. license.c 790
    • V575 The null pointer is passed into 'free' function. Inspect the first argument. rdpsnd_alsa.c 575

    Most likely, such forgotten variables arise during refactoring and can be simply deleted.

    Possible overflow


    V1028 Possible overflow. Consider casting operands, not the result. makecert.c 1087

    // openssl/x509.h
    ASN1_TIME *X509_gmtime_adj(ASN1_TIME *s, long adj);
    struct _MAKECERT_CONTEXT
    {
      ....
      int duration_years;
      int duration_months;
    };
    typedef struct _MAKECERT_CONTEXT MAKECERT_CONTEXT;
    int makecert_context_process(MAKECERT_CONTEXT* context, ....)
    {
      ....
      if (context->duration_months)
        X509_gmtime_adj(after, (long)(60 * 60 * 24 * 31 *
          context->duration_months));
      else if (context->duration_years)
        X509_gmtime_adj(after, (long)(60 * 60 * 24 * 365 *
          context->duration_years));
      ....
    }

    Bringing the result to long is not protection against overflow, since the calculation itself is performed using the int type .

    Pointer dereferencing in initialization


    V595 The 'context' pointer was utilized before it was verified against nullptr. Check lines: 746, 748. gfx.c 746

    static UINT gdi_SurfaceCommand(RdpgfxClientContext* context,
                                   const RDPGFX_SURFACE_COMMAND* cmd)
    {
      ....
      rdpGdi* gdi = (rdpGdi*) context->custom;
      if (!context || !cmd)
        return ERROR_INVALID_PARAMETER;
      ....
    }

    Here, the context pointer is dereferenced in the initialization - before it is checked.

    Other errors of this type were found:

    • V595 The 'ntlm' pointer was utilized before it was verified against nullptr. Check lines: 236, 255. ntlm.c 236
    • V595 The 'context' pointer was utilized before it was verified against nullptr. Check lines: 1003, 1007. rfx.c 1003
    • V595 The 'rdpei' pointer was utilized before it was verified against nullptr. Check lines: 176, 180. rdpei_main.c 176
    • V595 The 'gdi' pointer was utilized before it was verified against nullptr. Check lines: 121, 123. xf_gfx.c 121

    Meaningless condition


    V547 Expression 'rdp-> state> = CONNECTION_STATE_ACTIVE' is always true. connection.c 1489

    int rdp_server_transition_to_state(rdpRdp* rdp, int state)
    {
      ....
      switch (state)
      {
        ....
        case CONNECTION_STATE_ACTIVE:
          rdp->state = CONNECTION_STATE_ACTIVE;          // <=
          ....
          if (rdp->state >= CONNECTION_STATE_ACTIVE)     // <=
          {
            IFCALLRET(client->Activate, client->activated, client);
            if (!client->activated)
              return -1;
          }
        ....
      }
      ....
    }

    It is easy to see that the first condition does not make sense due to the assignment of the corresponding value earlier.

    Incorrect line parsing


    V576 Incorrect format. Consider checking the third actual argument of the 'sscanf' function. A pointer to the unsigned int type is expected. proxy.c 220

    V560 A part of conditional expression is always true: (rc> = 0). proxy.c 222
    static BOOL check_no_proxy(....)
    {
      ....
      int sub;
      int rc = sscanf(range, "%u", &sub);
      if ((rc == 1) && (rc >= 0))
      {
        ....
      }
      ....
    }

    The analyzer for this fragment immediately gives 2 warnings. The % u specifier expects a variable of type unsigned int , but the variable sub is of type int . Next, we see a suspicious check: the condition on the right does not make sense, since in the beginning there is a comparison with unity. I don’t know what the author of this code had in mind, but there is clearly something wrong here.

    Unordered Checks


    V547 Expression 'status == 0x00090314' is always false. ntlm.c 299

    BOOL ntlm_authenticate(rdpNtlm* ntlm, BOOL* pbContinueNeeded)
    {
      ....
      if (status != SEC_E_OK)
      {
        ....
        return FALSE;
      }
      if (status == SEC_I_COMPLETE_NEEDED)            // <=
        status = SEC_E_OK;
      else if (status == SEC_I_COMPLETE_AND_CONTINUE) // <=
        status = SEC_I_CONTINUE_NEEDED;
      ....
    }

    The marked conditions will always be false, since the execution will reach the second condition only if status == SEC_E_OK . The correct code might look like this:

    if (status == SEC_I_COMPLETE_NEEDED)
      status = SEC_E_OK;
    else if (status == SEC_I_COMPLETE_AND_CONTINUE)
      status = SEC_I_CONTINUE_NEEDED;
    else if (status != SEC_E_OK)
    {
      ....
      return FALSE;
    }

    Conclusion


    Thus, the verification of the project revealed many problems, but only the most interesting part was described in the article. Project developers can verify the project themselves by requesting a temporary license key on the PVS-Studio website . There were also false positives, work on which will help to improve the analyzer. Nevertheless, static analysis is important if you want to not only improve the quality of the code, but also reduce the time to search for errors, and PVS-Studio can help with this.



    If you want to share this article with an English-speaking audience, then please use the link to the translation: Sergey Larin. Checking FreeRDP with PVS-Studio

    Also popular now: