Comprovació de FreeRDP mitjançant l'analitzador PVS-Studio

Comprovació de FreeRDP mitjançant l'analitzador PVS-Studio
FreeRDP és una implementació de codi obert del Remote Desktop Protocol (RDP), un protocol desenvolupat per Microsoft per al control remot d'ordinadors. El projecte admet múltiples plataformes, incloent-hi Windows, Linux, macOS i fins i tot iOS amb AndroidAquest projecte va ser escollit com el primer d'una sèrie d'articles dedicats a provar clients RDP mitjançant l'analitzador estàtic PVS-Studio.

Una mica d'història

Projecte FreeRDP va sorgir després que Microsoft obrís les especificacions del seu protocol RDP propietari. En aquell moment, hi havia un client rdesktop, la implementació del qual es basava en els resultats de l'enginyeria inversa.

A mesura que es va implementar el protocol, es va fer més difícil afegir noves funcionalitats a causa de l'arquitectura del projecte existent. Els canvis en ell van donar lloc a un conflicte entre desenvolupadors, que va portar a la creació d'una bifurcació de rdesktop - FreeRDP. La distribució addicional del producte estava limitada per la llicència GPLv2, com a resultat de la qual cosa es va prendre la decisió de rellicenciar-lo a la llicència d'Apache v2. Tanmateix, no tothom va acceptar canviar la llicència del seu codi, de manera que els desenvolupadors van decidir reescriure el projecte, donant lloc a una base de codi moderna.

Podeu llegir més sobre la història del projecte a la publicació oficial del blog: "La història del projecte FreeRDP".

S'utilitza com a eina per identificar errors i vulnerabilitats potencials en el codi. Estudi PVSÉs un analitzador de codi estàtic per a C, C++, C# i Java, disponible a plataformes Windows, Linux и macOS.

L'article només presenta aquells errors que em van semblar més interessants.

Pèrdua de memòria

V773 S'ha sortit de la funció sense deixar anar el punter 'cwd'. És possible una fuga de memòria. medi ambient.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;
  ....
}

Aquest fragment s'ha extret del subsistema winpr, que implementa un contenidor WINAPI per a no-Windows sistemes, és a dir, és un anàleg lleuger de Wine. Aquí podeu veure una fuita: memòria assignada per la funció getcwd, només s'allibera quan es tracten casos especials. Per corregir l'error, cal afegir una trucada lliure després memcpy.

Array fora dels límits

V557 És possible l'excés de matriu. El valor de l'índex "event->EventHandlerCount" podria arribar a 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++;
  }
  ....
}

Aquest exemple afegeix un nou element a la llista encara que el nombre d'elements hagi arribat al màxim. Aquí n'hi ha prou amb substituir l'operador <= en <, per no anar més enllà dels límits de la matriu.

S'ha trobat un altre error d'aquest tipus:

  • V557 L'excés de matriu és possible. El valor de l'índex "iBitmapFormat" podria arribar a 8. orders.c 2623

Errors tipogràfics

Fragment 1

V547 L'expressió '!pipe->In' sempre és falsa. 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;
  ....
}

Aquí veiem una errada comú: la segona condició verifica la mateixa variable que la primera. El més probable és que l'error hagi aparegut com a resultat d'una còpia de codi sense èxit.

Fragment 2

V760 Es van trobar dos blocs de text idèntic. El segon bloc comença des de la línia 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);
  ....
}

Una altra errada: el comentari del codi implica que el fil hauria d'arribar minorVersion, però, la lectura es produeix en una variable anomenada majorVersió. Tanmateix, no estic familiaritzat amb el protocol, així que això és només una suposició.

Fragment 3

V524 És estrany que el cos de la funció 'trio_index_last' sigui totalment equivalent al cos de la funció 'trio_index'. 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);
}

A jutjar pel comentari, la funció trio_index troba la primera coincidència de caràcter en una cadena quan trio_index_últim - l'última cosa. Però els cossos d'aquestes funcions són idèntics! El més probable és que això sigui una errada d'ortografia i en la funció trio_index_últim cal utilitzar strhrchr en comptes de strchr. Aleshores s'esperarà el comportament.

Fragment 4

V769 El punter "dades" de l'expressió és igual a nullptr. El valor resultant de les operacions aritmètiques en aquest punter no té sentit i no s'ha d'utilitzar. 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;
  ....
}

Sembla que l'operador de negació es va perdre aquí accidentalment ! A prop dades. És estrany que això passi desapercebut.

Fragment 5

V517 S'ha detectat l'ús del patró "si (A) {…} else if (A) {...}". Hi ha una probabilitat de presència d'error lògic. Consulteu les línies: 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);
  }
  ....
}

Les dues últimes condicions són les mateixes: sembla que algú s'ha oblidat de comprovar-les després de copiar-les. Des del codi es nota que l'última part funciona amb valors de quatre bytes, de manera que podem suposar que l'última condició hauria de ser valor <= 0x3FFFFFFFF.

S'ha trobat un altre error d'aquest tipus:

  • V517 S'ha detectat l'ús del patró "si (A) {…} else if (A) {...}". Hi ha una probabilitat de presència d'error lògic. Línies de control: 169, 173. fitxer.c 169

Validació de les dades d'entrada

Fragment 1

V547 L'expressió 'strcat(target, source) != NULL' sempre és certa. 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);
}

Comprovar el resultat de la funció en aquest exemple és incorrecte. Funció strcat retorna un punter a la versió final de la cadena, és a dir. el primer paràmetre passat. En aquest cas ho és target. Tanmateix, si és igual NULL, llavors és massa tard per comprovar-ho, ja que a la funció strcat serà desreferenciat.

Fragment 2

V547 L'expressió "caché" sempre és certa. glif.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)
  {
    ....
  }
  ....
}

En aquest cas la variable cache s'assigna l'adreça d'una matriu estàtica glyphCache->glyphCache. Per tant, comproveu si (caché) es pot ometre.

Error de gestió de recursos

V1005 El recurs es va adquirir mitjançant la funció 'CreateFileA', però es va publicar mitjançant la funció 'fclose' incompatible. certificat.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;
  }
  ....
}

Descriptor de fitxer fp, creat per una crida de funció CreateFile tancat per error per funció f tancar de la biblioteca estàndard, no CloseHandle.

Mateixes condicions

V581 Les expressions condicionals de les declaracions "si" situades una al costat de l'altra són idèntiques. Comproveu les línies: 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);
  }
  ....
}

Aquest exemple pot no ser un error. Tanmateix, ambdues condicions contenen els mateixos missatges, un dels quals probablement es pot eliminar.

Neteja de punters nuls

V575 El punter nul es passa a la funció "lliure". Inspeccioneu el primer argument. targeta intel·ligent_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);
  ....
}

En funció lliure podeu passar un punter nul i l'analitzador ho sap. Però si es detecta una situació en què el punter sempre es passa nul, com en aquest fragment, s'emetrà un avís.

Punter mszGroupsA inicialment iguals NULL i no s'inicia en cap altre lloc. L'única branca de codi on es podria inicialitzar el punter és inaccessible.

Hi havia altres missatges com:

  • V575 El punter nul es passa a la funció "lliure". Inspeccioneu el primer argument. llicència.c 790
  • V575 El punter nul es passa a la funció "lliure". Inspeccioneu el primer argument. rdpsnd_alsa.c 575

Molt probablement, aquestes variables oblidades sorgeixen durant el procés de refactorització i simplement es poden eliminar.

Possible desbordament

V1028 Possible desbordament. Considereu l'emissió d'operands, no el resultat. 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));
  ....
}

Portant el resultat a llarg no és protecció contra desbordament, ja que el càlcul en si es produeix utilitzant el tipus int.

Desreferència del punter en la inicialització

V595 El punter "context" es va utilitzar abans de verificar-lo amb nullptr. Consulteu les línies: 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;
  ....
}

Aquí teniu el punter context es desfereix durant la inicialització, abans de comprovar-ho.

S'han trobat altres errors d'aquest tipus:

  • V595 El punter 'ntlm' es va utilitzar abans de verificar-lo contra nullptr. Consulteu les línies: 236, 255. ntlm.c 236
  • V595 El punter "context" es va utilitzar abans de verificar-lo amb nullptr. Comproveu les línies: 1003, 1007. rfx.c 1003
  • V595 El punter 'rdpei' es va utilitzar abans de verificar-lo contra nullptr. Consulteu les línies: 176, 180. rdpei_main.c 176
  • V595 El punter 'gdi' es va utilitzar abans de verificar-lo contra nullptr. Comproveu les línies: 121, 123. xf_gfx.c 121

Condició sense sentit

V547 L'expressió 'rdp->state >= CONNECTION_STATE_ACTIVE' sempre és certa. connexió.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;
      }
    ....
  }
  ....
}

És fàcil veure que la primera condició no té sentit a causa del valor corresponent que s'ha assignat abans.

Anàlisi de cadenes incorrecta

V576 Format incorrecte. Penseu en comprovar el tercer argument real de la funció 'sscanf'. S'espera un punter al tipus int sense signar. proxy.c 220

V560 Una part de l'expressió condicional sempre és certa: (rc >= 0). proxy.c 222

static BOOL check_no_proxy(....)
{
  ....
  int sub;
  int rc = sscanf(range, "%u", &sub);

  if ((rc == 1) && (rc >= 0))
  {
    ....
  }
  ....
}

L'analitzador emet immediatament 2 advertències per a aquest fragment. Especificador %u espera una variable de tipus int signat, però variable sub té tipus int. A continuació veiem un control sospitós: la condició de la dreta no té sentit, ja que al principi hi ha una comparació amb una. No sé què volia dir l'autor d'aquest codi, però hi ha alguna cosa clarament incorrecte.

Comprovacions fora de comanda

V547 L'expressió 'estat == 0x00090314' sempre és falsa. 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;
  ....
}

Les condicions verificades sempre seran falses, ja que l'execució només arribarà a la segona condició si estat == SEC_E_OK. El codi correcte podria semblar així:

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

Conclusió

Així, la comprovació del projecte va revelar molts problemes, però només la part més interessant es va descriure a l'article. Els desenvolupadors del projecte poden comprovar el projecte ells mateixos sol·licitant una clau de llicència temporal al lloc web Estudi PVS. També hi va haver falsos positius, treballs que ajudaran a millorar l'analitzador. Tanmateix, l'anàlisi estàtica és important si no només voleu millorar la qualitat del vostre codi, sinó també reduir el temps dedicat a trobar errors, i PVS-Studio us pot ajudar.

Comprovació de FreeRDP mitjançant l'analitzador PVS-Studio

Si voleu compartir aquest article amb un públic de parla anglesa, utilitzeu l'enllaç de traducció: Sergey Larin. Comprovació de FreeRDP amb PVS-Studio

Font: www.habr.com

Compreu allotjament fiable per a llocs amb protecció DDoS, servidors VPS VDS 🔥 Compra allotjament web fiable amb protecció DDoS, servidors VPS VDS | ProHoster