Überprüfen von FreeRDP mit dem PVS-Studio-Analysegerät

Bild 2

FreeRDP ist eine Open Source-Implementierung des Remote Desktop Protocol (RDP), eines Protokolls, das die von Microsoft entwickelte Remotecomputersteuerung implementiert. Das Projekt unterstützt viele Plattformen, einschließlich Windows, Linux, MacOS und sogar iOS mit Android. Dieses Projekt wurde als erstes in einer Reihe von Artikeln ausgewählt, die sich mit der Überprüfung von RDP-Clients mithilfe des statischen Analysators PVS-Studio befassen.

Ein bisschen Geschichte


Das FreeRDP- Projekt entstand, nachdem Microsoft die Spezifikationen seines proprietären RDP-Protokolls enthüllt hatte. Zu dieser Zeit gab es einen rdesktop-Client, dessen Implementierung auf den Ergebnissen von Reverse Engineering basiert.

Bei der Implementierung des Protokolls wurde es aufgrund der damals vorhandenen Projektarchitektur schwieriger, neue Funktionen hinzuzufügen. Änderungen führten zu einem Konflikt zwischen Entwicklern, der zur Erstellung der rdesktop-Gabel - FreeRDP führte. Die weitere Verbreitung des Produkts wurde durch die GPLv2-Lizenz eingeschränkt, weshalb beschlossen wurde, eine erneute Lizenzierung an Apache License v2 vorzunehmen. Da sich jedoch nicht alle bereit erklärten, die Lizenz ihres Codes zu ändern, beschlossen die Entwickler, das Projekt neu zu schreiben, wodurch wir ein modernes Erscheinungsbild der Codebasis haben.

Weitere Details zur Projektgeschichte finden Sie im offiziellen Blog-Hinweis: „Die Geschichte des FreeRDP-Projekts“.

PVS-Studio wurde als Tool zum Erkennen von Fehlern und potenziellen Schwachstellen im Code verwendet. Es ist ein statischer Code-Analysator für C, C ++, C # und Java, der unter Windows, Linux und macOS verfügbar ist.

Der Artikel präsentiert nur die Fehler, die mir am interessantesten erschienen.

Speicherverlust


V773 Die Funktion wurde beendet, ohne den Zeiger 'cwd' loszulassen. Ein Speicherverlust ist möglich. Umwelt.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; .... } 

Dieses Fragment wurde aus dem winpr-Subsystem entnommen, das den WINAPI-Wrapper für Nicht-Windows-Systeme implementiert, d. H. Es ist ein leichtes Gegenstück zu Wein. Hier liegt ein Leck vor: Der von der Funktion getcwd zugewiesene Speicher wird nur in besonderen Fällen freigegeben. Um den Fehler zu beheben, fügen Sie einen Anruf hinzu, um nach memcpy freizugeben .

Über die Grenzen eines Arrays hinausgehen


V557 Array-Überlauf ist möglich. Der Wert des Index 'event-> EventHandlerCount' könnte 32 erreichen. 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 diesem Beispiel wird der Liste ein neues Element hinzugefügt, auch wenn die Anzahl der Elemente das Maximum erreicht hat. Es reicht aus, den Operator <= durch < zu ersetzen, um die Grenzen des Arrays nicht zu überschreiten.

Ein weiterer Fehler dieses Typs wurde gefunden:

  • V557 Array-Überlauf ist möglich. Der Wert des 'iBitmapFormat'-Index könnte 8 Bestellungen erreichen.c 2623

Tippfehler


Fragment 1


V547 Ausdruck '! Pipe-> In' ist immer falsch. 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; .... } 

Hier sehen wir den üblichen Tippfehler: In der zweiten Bedingung wird dieselbe Variable wie in der ersten überprüft. Höchstwahrscheinlich trat der Fehler als Ergebnis eines erfolglosen Kopierens des Codes auf.

Fragment 2


V760 Es wurden zwei identische Textblöcke gefunden. Der zweite Block beginnt in Zeile 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); .... } 

Ein weiterer Tippfehler: Ein Kommentar zum Code impliziert, dass minorVersion aus dem Stream stammen sollte. Das Lesen erfolgt jedoch in einer Variablen namens majorVersion . Ich bin jedoch nicht mit dem Protokoll vertraut, daher ist dies nur eine Annahme.

Fragment 3


V524 Es ist seltsam, dass der Hauptteil der Funktion 'trio_index_last' dem Hauptteil der Funktion 'trio_index' vollständig entspricht. 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); } 

Dem Kommentar nach zu urteilen, findet die Funktion trio_index die erste Übereinstimmung eines Zeichens in einer Zeichenfolge, wenn trio_index_last die letzte ist. Aber die Körper dieser Funktionen sind identisch! Dies ist höchstwahrscheinlich ein Tippfehler, und in der Funktion trio_index_last müssen Sie strrchr anstelle von strchr verwenden . Dann wird das Verhalten erwartet.

Fragment 4


V769 Der ' Daten' -Zeiger im Ausdruck entspricht nullptr. Der resultierende Wert von arithmetischen Operationen für diesen Zeiger ist sinnlos und sollte nicht verwendet werden. 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; .... } 

Sieht so aus, als hätten sie versehentlich den Negationsoperator verpasst ! neben Daten . Es ist seltsam, dass dies unbemerkt blieb.

Fragment 5


V517 Die Verwendung des Musters 'if (A) {...} else if (A) {...}' wurde erkannt. Es besteht die Wahrscheinlichkeit eines logischen Fehlers. Überprüfen Sie die Zeilen: 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); } .... } 

Die letzten beiden Bedingungen sind die gleichen: Anscheinend hat jemand vergessen, sie nach dem Kopieren zu überprüfen. Laut Code fällt auf, dass der letzte Teil mit Vier-Byte-Werten arbeitet, sodass wir davon ausgehen können, dass die letzte Bedingung der Wert <= 0x3FFFFFFF sein sollte .

Ein weiterer Fehler dieses Typs wurde gefunden:

  • V517 Die Verwendung des Musters 'if (A) {...} else if (A) {...}' wurde erkannt. Es besteht die Wahrscheinlichkeit eines logischen Fehlers. Überprüfen Sie die Zeilen: 169, 173. file.c 169

Eingabevalidierung


Fragment 1


V547 Der Ausdruck 'strcat (Ziel, Quelle)! = NULL' ist immer wahr. 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); } 

Das Überprüfen des Ergebnisses der Funktion in diesem Beispiel ist falsch. Die strcat- Funktion gibt einen Zeiger auf die endgültige Version der Zeichenfolge zurück, d. H. erster Parameter übergeben. In diesem Fall ist es das Ziel . Wenn es jedoch NULL ist , ist es zu spät, dies zu überprüfen, da es in der strcat- Funktion dereferenziert wird.

Fragment 2


V547 Ausdruck 'Cache' ist immer wahr. Glyphe 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 diesem Fall wird der Cache- Variablen die Adresse des statischen Arrays glyphCache-> glyphCache zugewiesen . Somit kann die if (Cache) -Prüfung weggelassen werden.

Ressourcenverwaltungsfehler


V1005 Die Ressource wurde mit der Funktion 'CreateFileA' erfasst, aber mit der inkompatiblen Funktion 'fclose' freigegeben. Zertifikat.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; } .... } 

Der durch Aufrufen der CreateFile- Funktion erstellte fp- Dateideskriptor wird fälschlicherweise von der fclose- Funktion aus der Standardbibliothek und nicht von CloseHandle geschlossen .

Gleiche Bedingungen


V581 Die bedingten Ausdrücke der nebeneinander angeordneten if-Anweisungen sind identisch. Überprüfen Sie die Zeilen: 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); } .... } 

Vielleicht ist dieses Beispiel kein Fehler. Beide Bedingungen enthalten jedoch dieselbe Nachricht, von der höchstwahrscheinlich eine entfernt werden kann.

Nullzeiger löschen


V575 Der Nullzeiger wird an die Funktion 'frei' übergeben. Überprüfen Sie das erste 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); .... } 

Sie können einen Nullzeiger an die freie Funktion übergeben, und der Analysator weiß davon. Wenn jedoch eine Situation aufgedeckt wird, in der der Zeiger wie in diesem Fragment immer auf Null gesetzt wird, wird eine Warnung ausgegeben.

Der Zeiger mszGroupsA ist anfangs NULL und wird nirgendwo anders initialisiert. Der einzige Codezweig, in dem der Zeiger initialisiert werden könnte, ist nicht erreichbar.

Es gab andere Beiträge wie diesen:

  • V575 Der Nullzeiger wird an die Funktion 'frei' übergeben. Überprüfen Sie das erste Argument. Lizenz.c 790
  • V575 Der Nullzeiger wird an die Funktion 'frei' übergeben. Überprüfen Sie das erste Argument. rdpsnd_alsa.c 575

Solche vergessenen Variablen entstehen höchstwahrscheinlich beim Refactoring und können einfach gelöscht werden.

Möglicher Überlauf


V1028 Möglicher Überlauf. Betrachten Sie das Casting von Operanden, nicht das Ergebnis. 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)); .... } 

Ein zu langes Ergebnis ist kein Schutz vor Überlauf, da die Berechnung selbst mit dem Typ int durchgeführt wird .

Zeiger-Dereferenzierung bei der Initialisierung


V595 Der ' Kontext' -Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 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; .... } 

Hier wird der Kontextzeiger bei der Initialisierung dereferenziert - bevor er überprüft wird.

Andere Fehler dieses Typs wurden gefunden:

  • V595 Der Zeiger 'ntlm' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 236, 255. ntlm.c 236
  • V595 Der 'Kontext'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 1003, 1007. rfx.c 1003
  • V595 Der Zeiger 'rdpei' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 176, 180. rdpei_main.c 176
  • V595 Der Zeiger 'gdi' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 121, 123. xf_gfx.c 121

Sinnloser Zustand


V547 Der Ausdruck 'rdp-> state> = CONNECTION_STATE_ACTIVE' ist immer wahr. Verbindung.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; } .... } .... } 

Es ist leicht zu erkennen, dass die erste Bedingung aufgrund der früheren Zuweisung des entsprechenden Wertes keinen Sinn ergibt.

Falsche Zeilenanalyse


V576 Falsches Format. Überprüfen Sie das dritte tatsächliche Argument der Funktion 'sscanf'. Ein Zeiger auf den vorzeichenlosen int-Typ wird erwartet. proxy.c 220

V560 Ein Teil des bedingten Ausdrucks ist immer wahr: (rc> = 0). proxy.c 222
 static BOOL check_no_proxy(....) { .... int sub; int rc = sscanf(range, "%u", &sub); if ((rc == 1) && (rc >= 0)) { .... } .... } 

Der Analysator für dieses Fragment gibt sofort 2 Warnungen aus. Der % u- Bezeichner erwartet eine Variable vom Typ unsigned int , aber die Variable sub ist vom Typ int . Als nächstes sehen wir eine verdächtige Überprüfung: Die Bedingung auf der rechten Seite macht keinen Sinn, da am Anfang ein Vergleich mit der Einheit besteht. Ich weiß nicht, was der Autor dieses Codes vorhatte, aber hier stimmt eindeutig etwas nicht.

Ungeordnete Schecks


V547 Der Ausdruck 'status == 0x00090314' ist immer falsch. 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; .... } 

Die markierten Bedingungen sind immer falsch, da die Ausführung nur dann die zweite Bedingung erreicht, wenn status == SEC_E_OK . Der richtige Code könnte folgendermaßen aussehen:

 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; } 

Fazit


Die Überprüfung des Projekts ergab daher viele Probleme, aber nur der interessanteste Teil wurde in dem Artikel beschrieben. Projektentwickler können das Projekt selbst überprüfen, indem sie auf der PVS-Studio- Website einen temporären Lizenzschlüssel anfordern. Es gab auch falsch positive Ergebnisse, an denen gearbeitet wurde, um den Analysator zu verbessern. Eine statische Analyse ist jedoch wichtig, wenn Sie nicht nur die Qualität des Codes verbessern, sondern auch die Zeit für die Suche nach Fehlern verkürzen möchten. PVS-Studio kann Ihnen dabei helfen.



Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Sergey Larin. Überprüfen von FreeRDP mit PVS-Studio

Source: https://habr.com/ru/post/de444242/


All Articles