Kuangalia FreeRDP kwa kutumia kichambuzi cha PVS-Studio

Kuangalia FreeRDP kwa kutumia kichanganuzi cha PVS-Studio
FreeRDP ni utekelezaji huria wa Itifaki ya Kompyuta ya Mbali (RDP), itifaki iliyotengenezwa na Microsoft kwa ajili ya udhibiti wa kompyuta kwa mbali. Mradi huu unaunga mkono mifumo mingi, ikiwa ni pamoja na Windows, Linux, macOS na hata iOS na AndroidMradi huu ulichaguliwa kama wa kwanza katika mfululizo wa makala zilizotolewa kwa ajili ya kujaribu wateja wa RDP kwa kutumia kichambuzi tuli cha PVS-Studio.

kidogo ya historia

Mradi BureRDP ilikuja baada ya Microsoft kufungua vipimo vya itifaki yake ya umiliki ya RDP. Wakati huo, kulikuwa na mteja wa rdesktop, ambayo utekelezaji wake ulitokana na matokeo ya Uhandisi wa Reverse.

Itifaki ilipotekelezwa, ikawa vigumu zaidi kuongeza utendaji mpya kutokana na usanifu wa mradi uliokuwepo wakati huo. Mabadiliko ndani yake yalizua mzozo kati ya watengenezaji, ambayo ilisababisha kuundwa kwa uma ya rdesktop - FreeRDP. Usambazaji zaidi wa bidhaa ulidhibitiwa na leseni ya GPLv2, kwa sababu hiyo uamuzi ulifanywa wa kuirejesha kwa Apache License v2. Hata hivyo, si kila mtu alikubali kubadilisha leseni ya kanuni zao, kwa hiyo watengenezaji waliamua kuandika upya mradi huo, na kusababisha codebase ya kisasa.

Unaweza kusoma zaidi kuhusu historia ya mradi katika chapisho rasmi la blogi: "Historia ya mradi wa FreeRDP".

Inatumika kama zana ya kutambua makosa na udhaifu unaowezekana katika msimbo. PVS-StudioNi kichambuzi cha msimbo tuli cha C, C++, C# na Java, kinachopatikana kwenye mifumo Windows, Linux и macOS.

Makala hiyo inawasilisha tu makosa ambayo yalionekana kunivutia zaidi.

Kumbukumbu ya kumbukumbu

V773 Chaguo za kukokotoa ziliondolewa bila kuachilia kielekezi cha 'cwd'. Uvujaji wa kumbukumbu unawezekana. mazingira.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;
  ....
}

Kipande hiki kilichukuliwa kutoka kwa mfumo mdogo wa winpr, ambao hutekeleza kifungashio cha WINAPI kwa ajili yaWindows mifumo, yaani, ni analogi nyepesi ya Mvinyo. Hapa unaweza kuona uvujaji: kumbukumbu iliyotengwa na kitendakazi getcwd, hutolewa tu wakati wa kushughulikia kesi maalum. Ili kurekebisha hitilafu unahitaji kuongeza simu bure baada ya kumbukumbu.

Safu nje ya mipaka

V557 Kupanda kwa safu kunawezekana. Thamani ya faharasa ya 'event->EventHandlerCount' inaweza kufikia 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++;
  }
  ....
}

Mfano huu unaongeza kipengele kipya kwenye orodha hata kama idadi ya vipengele imefikia upeo. Hapa ni ya kutosha kuchukua nafasi ya operator <= juu ya <, ili usiende zaidi ya mipaka ya safu.

Hitilafu nyingine ya aina hii ilipatikana:

  • V557 Array overrun inawezekana. Thamani ya faharasa ya 'iBitmapFormat' inaweza kufikia 8. orders.c 2623

Typos

Sehemu ya 1

V547 Usemi '!bomba->Ndani' ni uongo kila wakati. 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;
  ....
}

Hapa tunaona chapa ya kawaida: hali ya pili hukagua utofauti sawa na wa kwanza. Uwezekano mkubwa zaidi, hitilafu ilionekana kama matokeo ya kunakili msimbo bila kufaulu.

Sehemu ya 2

V760 Vitalu viwili vya maandishi yanayofanana vilipatikana. Kizuizi cha pili huanza kutoka mstari wa 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);
  ....
}

Chapa nyingine: maoni ya nambari yanamaanisha kuwa nyuzi inapaswa kuja NdogoVersion, hata hivyo, kusoma hutokea katika kutofautiana kwa jina toleo kuu. Walakini, sijui itifaki, kwa hivyo hii ni nadhani tu.

Sehemu ya 3

V524 Ni ajabu kwamba kipengele cha chaguo za kukokotoa cha 'trio_index_last' ni sawa na kitendakazi cha '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);
}

Kwa kuzingatia maoni, kazi faharisi_ya_tatu hupata herufi inayolingana ya kwanza kwenye mfuatano wakati trio_index_mwisho - jambo la mwisho. Lakini miili ya kazi hizi ni sawa! Uwezekano mkubwa zaidi hii ni typo, na katika kazi trio_index_mwisho haja ya kutumia strhrrchr badala ya strhr. Kisha tabia itatarajiwa.

Sehemu ya 4

V769 Kielekezi cha 'data' katika usemi ni sawa na nullptr. Thamani inayotokana ya shughuli za hesabu kwenye pointer hii haina maana na haipaswi kutumiwa. 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;
  ....
}

Inaonekana kama mwendeshaji wa kukanusha alikosekana hapa kimakosa ! Karibu data. Ni ajabu kwamba hii ilikwenda bila kutambuliwa.

Sehemu ya 5

V517 Matumizi ya 'ikiwa (A) {...} vinginevyo ikiwa (A) {…}' mchoro ulitambuliwa. Kuna uwezekano wa kuwepo kwa makosa ya kimantiki. Angalia mistari: 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);
  }
  ....
}

Masharti mawili ya mwisho ni sawa: inaonekana mtu alisahau kuziangalia baada ya kunakili. Inaonekana kutoka kwa nambari kwamba sehemu ya mwisho inafanya kazi na maadili ya baiti nne, kwa hivyo tunaweza kudhani kuwa hali ya mwisho inapaswa kuwa. thamani <= 0x3FFFFFFFF.

Hitilafu nyingine ya aina hii ilipatikana:

  • V517 Matumizi ya muundo wa 'ikiwa (A) {…} vinginevyo ikiwa (A) {…}' yametambuliwa. Kuna uwezekano wa kuwepo kwa makosa ya kimantiki. Angalia mistari: 169, 173. file.c 169

Uthibitishaji wa data ya pembejeo

Sehemu ya 1

V547 Usemi 'strcat(lengo, chanzo) != NULL' huwa kweli kila wakati. 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);
}

Kuangalia matokeo ya chaguo za kukokotoa katika mfano huu si sahihi. Kazi strcat inarudisha pointer kwenye toleo la mwisho la kamba, i.e. parameta ya kwanza imepita. Katika kesi hii ni lengo. Walakini, ikiwa ni sawa NULL, basi ni kuchelewa sana kuiangalia, kwa kuwa katika kazi strcat itaahirishwa.

Sehemu ya 2

V547 Maneno 'cache' ni kweli kila wakati. 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)
  {
    ....
  }
  ....
}

Katika kesi hii, kutofautiana cache anwani ya safu tuli imepewa glyphCache->glyphCache. Kwa hivyo, angalia ikiwa (cache) inaweza kuachwa.

Hitilafu ya usimamizi wa rasilimali

V1005 Nyenzo hii ilipatikana kwa kutumia chaguo za kukokotoa za 'CreateFileA' lakini ilitolewa kwa kutumia chaguo la kukokotoa la 'fclose' lisilopatana. cheti.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;
  }
  ....
}

Kifafanuzi cha faili fp, iliyoundwa na simu ya kukokotoa UndaFaili imefungwa kwa makosa na chaguo la kukokotoa funga kutoka kwa maktaba ya kawaida, sio CloseHandle.

Masharti sawa

V581 Semi za masharti za taarifa za 'ikiwa' ziko pamoja zinafanana. Angalia mistari: 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);
  }
  ....
}

Mfano huu unaweza usiwe mdudu. Hata hivyo, hali zote mbili zina ujumbe sawa, moja ambayo inaweza uwezekano mkubwa kuondolewa.

Kusafisha viashiria visivyofaa

V575 Kielekezi kisicho na maana kinapitishwa kwenye kitendakazi cha 'bure'. Kagua hoja ya kwanza. 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);
  ....
}

Kufanya kazi bure unaweza kupitisha null pointer na analyzer anajua kuihusu. Lakini ikiwa hali itagunduliwa ambayo pointer inapitishwa kila wakati, kama ilivyo kwenye snippet hii, onyo litatolewa.

Pointer mszVikundiA mwanzoni sawa NULL na haijaanzishwa popote pengine. Tawi pekee la msimbo ambapo pointer inaweza kuanzishwa haliwezi kufikiwa.

Kulikuwa na ujumbe mwingine kama vile:

  • V575 Kielekezi batili kinapitishwa kwenye kitendakazi cha 'bure'. Kagua hoja ya kwanza. leseni.c 790
  • V575 Kielekezi batili kinapitishwa kwenye kitendakazi cha 'bure'. Kagua hoja ya kwanza. rdpsnd_alsa.c 575

Uwezekano mkubwa zaidi, vigezo vile vilivyosahaulika hutokea wakati wa mchakato wa kurejesha upya na vinaweza kuondolewa tu.

Uwezekano wa kufurika

V1028 Uwezekano wa kufurika. Fikiria uchezaji wa kuigiza, sio matokeo. 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));
  ....
}

Kuleta matokeo kwa muda mrefu si ulinzi wa kufurika, kwani hesabu yenyewe hutokea kwa kutumia aina int.

Kuachwa kwa pointer katika uanzishaji

V595 Kielekezi cha 'muktadha' kilitumika kabla ya kuthibitishwa dhidi ya nullptr. Kagua mistari: 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;
  ....
}

Hapa kuna pointer muktadha inaahirishwa wakati wa uanzishaji - kabla ya kuangaliwa.

Makosa mengine ya aina hii yalipatikana:

  • V595 Kielekezi cha 'ntlm' kilitumika kabla ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 236, 255. ntlm.c 236
  • V595 Kielekezi cha 'muktadha' kilitumika kabla ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 1003, 1007. rfx.c 1003
  • V595 Kielekezi cha 'rdpei' kilitumika kabla ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 176, 180. rdpei_main.c 176
  • V595 Kielekezi cha 'gdi' kilitumika kabla ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 121, 123. xf_gfx.c 121

Hali isiyo na maana

V547 Usemi 'rdp->state >= CONNECTION_STATE_ACTIVE' huwa kweli kila wakati. muunganisho.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;
      }
    ....
  }
  ....
}

Ni rahisi kuona kuwa hali ya kwanza haina maana kwa sababu ya dhamana inayolingana iliyopewa mapema.

Uchanganuzi wa mfuatano usio sahihi

V576 Umbizo lisilo sahihi. Fikiria kuangalia hoja halisi ya tatu ya chaguo la kukokotoa la 'sscanf'. Kielekezi kwa aina ya int ambayo haijasainiwa inatarajiwa. wakala.c 220

V560 Sehemu ya usemi wa masharti ni kweli kila wakati: (rc>= 0). wakala.c 222

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

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

Kichanganuzi hutoa maonyo 2 mara moja kwa kipande hiki. Kielezi %u inatarajia kutofautisha kwa aina int isiyotiwa saini, lakini kutofautiana ndogo ina aina int. Ifuatayo tunaona ukaguzi wa tuhuma: hali ya kulia haina maana, kwani mwanzoni kuna kulinganisha na moja. Sijui mwandishi wa kanuni hii alimaanisha nini, lakini kuna kitu kibaya hapa.

Ukaguzi wa nje ya agizo

V547 Usemi 'status == 0x00090314' sio kweli kila wakati. 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;
  ....
}

Masharti yaliyoangaliwa yatakuwa ya uwongo kila wakati, kwani utekelezaji utafikia hali ya pili ikiwa hali == SEC_E_OK. Nambari sahihi inaweza kuonekana kama hii:

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

Hitimisho

Kwa hivyo, kuangalia mradi ulifunua shida nyingi, lakini ni sehemu ya kupendeza tu iliyoelezewa katika kifungu hicho. Wasanidi wa mradi wanaweza kuangalia mradi wenyewe kwa kuomba ufunguo wa leseni ya muda kwenye tovuti PVS-Studio. Pia kulikuwa na chanya za uwongo, kazi ambayo itasaidia kuboresha analyzer. Hata hivyo, uchambuzi wa tuli ni muhimu ikiwa unataka sio tu kuboresha ubora wa msimbo wako, lakini pia kupunguza muda uliotumiwa kutafuta makosa, na PVS-Studio inaweza kusaidia kwa hili.

Kuangalia FreeRDP kwa kutumia kichanganuzi cha PVS-Studio

Ikiwa ungependa kushiriki makala hii na hadhira inayozungumza Kiingereza, tafadhali tumia kiungo cha kutafsiri: Sergey Larin. Kuangalia FreeRDP na PVS-Studio

Chanzo: mapenzi.com

Nunua upangishaji wa kuaminika wa tovuti zilizo na ulinzi wa DDoS, seva za VPS VDS 🔥 Nunua upangishaji wa tovuti unaoaminika kwa ulinzi wa DDoS, seva za VPS VDS | ProHoster