Verificando o rdesktop e o xrdp com o PVS-Studio

Image3

Este é o segundo post da nossa série de artigos sobre os resultados da verificação de software de código aberto que trabalha com o protocolo RDP. Hoje vamos dar uma olhada no cliente rdesktop e no servidor xrdp.

A análise foi realizada pelo PVS-Studio . Este é um analisador estático para código escrito em C, C ++, C # e Java e é executado no Windows, Linux e macOS.

Vou discutir apenas os bugs que me pareciam mais interessantes. Por outro lado, como os projetos são bem pequenos, não há muitos bugs neles :).

Nota O artigo anterior sobre a verificação do FreeRDP está disponível aqui .

rdesktop


O rdesktop é um cliente RDP gratuito para sistemas baseados em UNIX. Também pode ser executado no Windows, se construído com o Cygwin. O rdesktop é lançado sob a GPLv3.

Este é um cliente muito popular. Ele é usado como um cliente padrão no ReactOS e você também pode encontrar front-ends gráficos de terceiros para acompanhá-lo. No entanto, o projeto é bastante antigo: foi lançado pela primeira vez em 4 de abril de 2001 e tem 17 anos, até o momento.

Como eu já disse, o projeto é muito pequeno - cerca de 30 KLOC, o que é um pouco estranho, considerando sua idade. Compare isso com o FreeRDP com seu 320 KLOC. Aqui está a saída de Cloc:

Image1


Código inacessível


V779 Código inacessível detectado. É possível que haja um erro. rdesktop.c 1502

int main(int argc, char *argv[]) { .... return handle_disconnect_reason(deactivated, ext_disc_reason); if (g_redirect_username) xfree(g_redirect_username); xfree(g_username); } 

O primeiro erro é encontrado imediatamente na função principal : o código após a declaração de retorno foi criado para liberar a memória alocada anteriormente. Mas esse defeito não é perigoso, porque toda a memória alocada anteriormente será liberada pelo sistema operacional assim que o programa terminar.

Sem manipulação de erros


V557 Array underrun é possível. O valor do índice 'n' pode chegar a -1. rdesktop.c 1872

 RD_BOOL subprocess(char *const argv[], str_handle_lines_t linehandler, void *data) { int n = 1; char output[256]; .... while (n > 0) { n = read(fd[0], output, 255); output[n] = '\0'; // <= str_handle_lines(output, &rest, linehandler, data); } .... } 

O conteúdo do arquivo é lido no buffer até que o EOF seja alcançado. Ao mesmo tempo, esse código não possui um mecanismo de tratamento de erros e, se algo der errado, a leitura retornará -1 e a execução começará a ler além dos limites da matriz de saída .

Usando EOF em char


O V739 EOF não deve ser comparado com um valor do tipo 'char'. O '(c = fgetc (fp))' deve ser do tipo 'int'. ctrl.c 500

 int ctrl_send_command(const char *cmd, const char *arg) { char result[CTRL_RESULT_SIZE], c, *escaped; .... while ((c = fgetc(fp)) != EOF && index < CTRL_RESULT_SIZE && c != '\n') { result[index] = c; index++; } .... } 

Esse código implementa manipulação incorreta de EOF : se fgetc retornar um caractere cujo código é 0xFF, ele será interpretado como o final do arquivo ( EOF ).

EOF é uma constante normalmente definida como -1. Por exemplo, na codificação CP1251, a última letra do alfabeto russo é codificada como 0xFF, que corresponde ao número -1 no tipo char . Isso significa que o caractere 0xFF, assim como o EOF (-1), será interpretado como o final do arquivo. Para evitar erros como esse, o resultado retornado pela função fgetc deve ser armazenado em uma variável do tipo int .

Erros de digitação


Snippet 1

A expressão V547 'write_time' é sempre falsa. disk.c 805

 RD_NTSTATUS disk_set_information(....) { time_t write_time, change_time, access_time, mod_time; .... if (write_time || change_time) mod_time = MIN(write_time, change_time); else mod_time = write_time ? write_time : change_time; // <= .... } 

O autor deste código deve ter usado acidentalmente o || operador em vez de && na condição. Vamos ver quais valores as variáveis write_time e change_time podem ter:
  • Ambas as variáveis ​​possuem 0. Nesse caso, a execução passa para o ramo else : a variável mod_time sempre será avaliada como 0, independentemente da próxima condição.
  • Uma das variáveis ​​possui 0. Nesse caso, mod_time receberá 0 (dado que a outra variável possui um valor não negativo), pois MIN escolherá o menor dos dois.
  • Nenhuma variável possui 0: o valor mínimo é escolhido.

Alterar essa linha para write_time && change_time corrigirá o comportamento:
  • Somente uma ou nenhuma variável possui 0: o valor diferente de zero é escolhido.
  • Nenhuma variável possui 0: o valor mínimo é escolhido.

Snippet 2

A expressão V547 é sempre verdadeira. Provavelmente o operador '&&' deve ser usado aqui. disk.c 1419

 static RD_NTSTATUS disk_device_control(RD_NTHANDLE handle, uint32 request, STREAM in, STREAM out) { .... if (((request >> 16) != 20) || ((request >> 16) != 9)) return RD_STATUS_INVALID_PARAMETER; .... } 

Novamente, parece que o problema de usar o operador errado - ou || em vez de && ou == em vez de ! = porque a variável não pode armazenar os valores 20 e 9 ao mesmo tempo.

Cópia ilimitada de strings


V512 Uma chamada da função 'sprintf' levará ao estouro do buffer 'caminho completo'. disk.c 1257

 RD_NTSTATUS disk_query_directory(....) { .... char *dirname, fullpath[PATH_MAX]; .... /* Get information for directory entry */ sprintf(fullpath, "%s/%s", dirname, pdirent->d_name); .... } 

Se você pudesse seguir a função até o fim, veria que o código está OK, mas pode ser quebrado um dia: apenas uma mudança descuidada acabará com um estouro de buffer, pois o sprintf não é limitado de forma alguma, concatenando os caminhos podem levar a execução além dos limites da matriz. Recomendamos substituir esta chamada por snprintf (caminho completo, PATH_MAX, ....) .

Condição redundante


V560 Uma parte da expressão condicional é sempre verdadeira: add> 0. scard.c 507

 static void inRepos(STREAM in, unsigned int read) { SERVER_DWORD add = 4 - read % 4; if (add < 4 && add > 0) { .... } } 

A verificação add> 0 não faz nenhuma diferença, pois a variável sempre será maior que zero, porque read% 4 retorna o restante, que nunca será igual a 4.

xrdp


xrdp é um servidor RDP de código aberto. O projeto consiste em duas partes:

  • xrdp - a implementação do protocolo. É lançado no Apache 2.0.
  • xorgxrdp - uma coleção de drivers Xorg a serem usados ​​com o xrdp. É lançado no X11 (assim como o MIT, mas o uso de publicidade é proibido)

O desenvolvimento é baseado no rdesktop e no FreeRDP. Inicialmente, para poder trabalhar com gráficos, você precisaria usar um servidor VNC separado ou um servidor X11 especial com suporte a RDP, X11rdp, mas esses se tornaram desnecessários com o lançamento do xorgxrdp.

Não falaremos sobre xorgxrdp neste artigo.

Assim como o projeto anterior, o xrdp é pequeno, consistindo em cerca de 80 KLOC.

Image2


Mais erros de digitação


V525 O código contém a coleção de blocos semelhantes. Verifique os itens 'r', 'g', 'r' nas linhas 87, 88, 89. rfxencode_rgb_to_yuv.c 87

 static int rfx_encode_format_rgb(const char *rgb_data, int width, int height, int stride_bytes, int pixel_format, uint8 *r_buf, uint8 *g_buf, uint8 *b_buf) { .... switch (pixel_format) { case RFX_FORMAT_BGRA: .... while (x < 64) { *lr_buf++ = r; *lg_buf++ = g; *lb_buf++ = r; // <= x++; } .... } .... } 

Esse código vem da biblioteca librfxcodec, que implementa o codec jpeg2000 para funcionar com o RemoteFX. O canal colorido "vermelho" é lido duas vezes, enquanto o canal "azul" não é lido. Defeitos como esse geralmente resultam do uso de copiar e colar.

O mesmo bug foi encontrado na função semelhante rfx_encode_format_argb :

V525 O código contém a coleção de blocos semelhantes. Verifique os itens 'a', 'r', 'g', 'r' nas linhas 260, 261, 262, 263. rfxencode_rgb_to_yuv.c 260

 while (x < 64) { *la_buf++ = a; *lr_buf++ = r; *lg_buf++ = g; *lb_buf++ = r; x++; } 

Declaração de matriz


A saturação da matriz V557 é possível. O valor do índice 'i - 8' pode chegar a 129. genkeymap.c 142

 // evdev-map.c int xfree86_to_evdev[137-8+1] = { .... }; // genkeymap.c extern int xfree86_to_evdev[137-8]; int main(int argc, char **argv) { .... for (i = 8; i <= 137; i++) /* Keycodes */ { if (is_evdev) e.keycode = xfree86_to_evdev[i-8]; .... } .... } 

No arquivo genkeymap.c, a matriz é declarada 1 elemento menor que o implícito na implementação. Porém, nenhum erro ocorrerá, porque o arquivo evdev-map.c armazena o tamanho correto, portanto não haverá excesso de matriz, o que o torna um defeito menor e não um erro verdadeiro.

Comparação incorreta


V560 Uma parte da expressão condicional é sempre falsa: (cap_len <0). xrdp_caps.c 616

 // common/parse.h #if defined(B_ENDIAN) || defined(NEED_ALIGN) #define in_uint16_le(s, v) do \ .... #else #define in_uint16_le(s, v) do \ { \ (v) = *((unsigned short*)((s)->p)); \ (s)->p += 2; \ } while (0) #endif int xrdp_caps_process_confirm_active(struct xrdp_rdp *self, struct stream *s) { int cap_len; .... in_uint16_le(s, cap_len); .... if ((cap_len < 0) || (cap_len > 1024 * 1024)) { .... } .... } 

O valor de uma variável do tipo curto não assinado é lido em uma variável do tipo int e, em seguida, verificado como negativo, o que não é necessário porque um valor lido de um tipo não assinado em um tipo maior nunca pode se tornar negativo.

Verificações redundantes


V560 Uma parte da expressão condicional é sempre verdadeira: (bpp! = 16). libxrdp.c 704

 int EXPORT_CC libxrdp_send_pointer(struct xrdp_session *session, int cache_idx, char *data, char *mask, int x, int y, int bpp) { .... if ((bpp == 15) && (bpp != 16) && (bpp != 24) && (bpp != 32)) { g_writeln("libxrdp_send_pointer: error"); return 1; } .... } 

As verificações não iguais não são necessárias porque a primeira verificação faz o trabalho. O programador provavelmente usaria o || operador para filtrar argumentos incorretos.

Conclusão


O cheque de hoje não revelou nenhum erro crítico, mas revelou vários defeitos menores. Dito isto, esses projetos, por mais pequenos que sejam, ainda são usados ​​em muitos sistemas e, portanto, precisam de polimento. Um projeto pequeno não deve necessariamente conter muitos bugs; portanto, testar o analisador apenas em projetos pequenos não é suficiente para avaliar com segurança sua eficácia. Este assunto é discutido em mais detalhes no artigo " Sentimentos confirmados por números ".

A versão demo do PVS-Studio está disponível em nosso site .

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


All Articles