Wireshark 3.x: análise de código do macOS e revisão de erros

Quadro 1

A Wireshark Foundation lançou a versão estável final do popular analisador de tráfego de rede - Wireshark 3.0.0. A nova versão corrigiu vários erros, implementou a capacidade de analisar novos protocolos e substituiu o driver WinPcap pelo Npcap. Aqui a citação do anúncio termina e nosso artigo sobre erros no projeto começa. Antes do lançamento, eles eram claramente corrigidos insuficientemente. Vamos fazer algumas correções para que haja uma razão para fazer um novo lançamento :).

1. Introdução


O Wireshark é uma ferramenta bem conhecida para capturar e analisar o tráfego de rede. O programa trabalha com a grande maioria dos protocolos conhecidos, possui uma interface gráfica clara e lógica, um poderoso sistema de filtro. Wireshark - multiplataforma, funciona em sistemas operacionais como: Windows, Linux, macOS, Solaris, FreeBSD, NetBSD e muitos outros.

Para procurar erros, usamos o analisador estático PVS-Studio . Para analisar o código fonte, você deve primeiro compilar o projeto em algum sistema operacional. A escolha foi ótima, não apenas pela natureza de plataforma cruzada do projeto, mas também pela natureza de plataforma cruzada do analisador. Para a análise do projeto, escolhi o macOS. O analisador também pode ser iniciado no Windows e Linux.

Sobre a qualidade do código que quero contar separadamente. Infelizmente, não posso chamá-lo de bom. Esta é uma avaliação subjetiva, mas, como verificamos regularmente muitos projetos, tenho algo com o que comparar. Nesse caso, um grande número de avisos do PVS-Studio em uma pequena quantidade de código é impressionante. No total, mais de 3.500 avisos de todos os níveis foram emitidos para este projeto. Isso é típico para projetos que não usam ferramentas de análise estática, mesmo gratuitas. Outro fator que indica a qualidade do projeto são os erros repetidos identificados pelo analisador. Os mesmos exemplos de código não serão fornecidos no artigo, mas alguns erros idênticos estão presentes no código em centenas de lugares.

Além disso, essas inserções não adicionam qualidade ao código:

/* Input file: packet-acse-template.c */ #line 1 "./asn1/acse/packet-acse-template.c" 

Existem mais de 1000 deles em todo o projeto. Tais inserções dificultam para o analisador comparar os avisos gerados com o arquivo desejado. Mas tenho certeza de que desenvolvedores comuns não desfrutam do suporte a esse código.

Erros de digitação


Aviso 1

V641 O tamanho do buffer de memória alocado não é um múltiplo do tamanho do elemento. mate_setup.c 100

 extern mate_cfg_gog* new_gogcfg(mate_config* mc, gchar* name) { mate_cfg_gog* cfg = (mate_cfg_gog *)g_malloc(sizeof(mate_cfg_gop)); .... } 

Existem dois tipos de estruturas: mate_cfg_gog e mate_cfg_gop , eles são muito semelhantes, mas não idênticos. Provavelmente, as funções foram misturadas nesse fragmento de código, repleto de erros em potencial no programa durante o acesso à memória pelo ponteiro.

A seguir, trechos de estruturas de dados confusas:

 typedef struct _mate_cfg_gog { gchar* name; GHashTable* items; guint last_id; GPtrArray* transforms; LoAL* keys; AVPL* extra; float expiration; gop_tree_mode_t gop_tree_mode; gboolean show_times; .... } mate_cfg_gog; typedef struct _mate_cfg_gop { gchar* name; guint last_id; GHashTable* items; GPtrArray* transforms; gchar* on_pdu; AVPL* key; AVPL* start; AVPL* stop; AVPL* extra; float expiration; float idle_timeout; float lifetime; gboolean drop_unassigned; gop_pdu_tree_t pdu_tree_mode; gboolean show_times; .... } mate_cfg_gop; 

Aviso 2

V519 A variável 'HDR_TCP.dest_port' recebe valores duas vezes sucessivamente. Talvez isso seja um erro. Verifique as linhas: 495, 496. text_import.c 496

 void write_current_packet (void) { .... HDR_TCP.source_port =isOutbound ? g_htons(hdr_dest_port):g_htons(hdr_src_port); HDR_TCP.dest_port = isOutbound ? g_htons(hdr_src_port) :g_htons(hdr_dest_port); HDR_TCP.dest_port = g_htons(hdr_dest_port); .... } 

Na última linha, o valor apenas calculado da variável HDR_TCP.dest_port não é confirmado .

Erros lógicos


Nesta seção, darei alguns exemplos de erros em declarações condicionais e todos eles serão fundamentalmente diferentes um do outro.

Aviso 1

V547 A expressão 'direction == 0' é sempre falsa. packet-adb.c 291

 #define P2P_DIR_RECV 1 #define P2P_DIR_SENT 0 static void save_command(....) { .... if ( service_data && service_data->remote_id == 0 && direction == P2P_DIR_RECV) { if (direction == P2P_DIR_SENT) { service_data->remote_id = arg1; // unreachable code } else { service_data->remote_id = arg0; } .... } .... } 

Na condição externa, a variável de direção é comparada com a constante P2P_DIR_RECV . Escrever expressões através do operador AND significa que, quando a condição interna for atingida, o valor da variável de direção não será exatamente igual à outra constante P2P_DIR_SENT .

Aviso 2

V590 Considere inspecionar o '(tipo == 0x1) || (tipo! = 0x4) 'expressão. A expressão é excessiva ou contém uma impressão incorreta. packet-fcsb3.c 686

 static int dissect_fc_sbccs (....) { .... else if ((type == FC_SBCCS_IU_CMD_HDR) || (type != FC_SBCCS_IU_CMD_DATA)) { .... } 

O erro desse trecho de código é que o resultado da condição depende de apenas uma expressão:

 (type != FC_SBCCS_IU_CMD_DATA) 

Aviso 3

V590 Considere inspecionar esta expressão. A expressão é excessiva ou contém uma impressão incorreta. snort-config.c 40

 static char *skipWhiteSpace(char *source, int *accumulated_offset) { int offset = 0; /* Skip any leading whitespace */ while (source[offset] != '\0' && source[offset] == ' ') { offset++; } *accumulated_offset += offset; return source + offset; } 

O resultado da instrução condicional dependerá apenas dessa parte da expressão (origem [deslocamento] == '') . A verificação (origem [deslocamento]! = '\ 0') é redundante e pode ser removida com segurança. Este não é um erro real, mas o código redundante dificulta a leitura e a compreensão do programa, por isso é melhor simplificá-lo.

Aviso 4

A expressão V547 'eras_pos! = NULL' sempre é verdadeira. reedsolomon.c 659

 int eras_dec_rs(dtype data[NN], int eras_pos[NN-KK], int no_eras) { .... if(eras_pos != NULL){ for(i=0;i<count;i++){ if(eras_pos!= NULL) eras_pos[i] = INDEX_TO_POS(loc[i]); } } .... } 

Talvez estejamos lidando com verificações desnecessárias e, possivelmente, com erros de digitação. Em um dos ifs, algo mais deve ser verificado.

Ativos estranhos


Aviso 1

A expressão V547 'sub_dissectors! = NULL' sempre é verdadeira. capture_dissectors.c 129

 void capture_dissector_add_uint(....) { .... sub_dissectors = (struct capture_dissector_table*)g_hash_table_lookup(....); if (sub_dissectors == NULL) { fprintf(stderr, "OOPS: Subdissector \"%s\" not found ... \n", name); if (getenv("WIRESHARK_ABORT_ON_DISSECTOR_BUG") != NULL) abort(); return; } g_assert(sub_dissectors != NULL); // <= .... } 

A verificação do ponteiro em g_assert neste momento é supérflua, pois o ponteiro é verificado antes disso. Talvez nesta função houvesse apenas g_assert antes , e eles se esqueceram de excluí-la, mas talvez aqui você deva verificar algum campo de estrutura.

Aviso 2

A expressão V547 'i <count' sempre é verdadeira. pacote-netflow.c 10363

 static int dissect_v9_v10_template_fields(....) { .... count = tmplt_p->field_count[fields_type]; for(i=0; i<count; i++) { .... if (tmplt_p->fields_p[fields_type] != NULL) { DISSECTOR_ASSERT (i < count); // <= tmplt_p->fields_p[fields_type][i].type = type; tmplt_p->fields_p[fields_type][i].length = length; tmplt_p->fields_p[fields_type][i].pen = pen; tmplt_p->fields_p[fields_type][i].pen_str = pen_str; if (length != VARIABLE_LENGTH) {/ tmplt_p->length += length; } } .... } .... } 

Não está totalmente claro por que uma declaração está presente na função, que duplica a condição do loop. O contador de ciclos no corpo não muda.

Erros com ponteiros


Aviso 1

V595 O ponteiro 'si-> conv' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 2135, 2144. packet-smb2.c 2135

 static int dissect_smb2_fid(....) { .... g_hash_table_insert(si->conv->fids, sfi, sfi); // <= si->file = sfi; if (si->saved) { si->saved->file = sfi; si->saved->policy_hnd = policy_hnd; } if (si->conv) { // <= eo_file_info = (.... *)g_hash_table_lookup(si->conv->files,&policy_hnd); .... } .... } 

O ponteiro si-> conv é desreferenciado várias linhas antes que seja verificado se é igual a zero ou não.

Aviso 2

V774 O ponteiro 'protos' foi usado após o lançamento da memória. pacote-k12.c 311

 static gboolean k12_update_cb(void* r, char** err) { gchar** protos; .... for (i = 0; i < num_protos; i++) { if ( ! (h->handles[i] = find_dissector(protos[i])) ) { h->handles[i] = data_handle; h->handles[i+1] = NULL; g_strfreev(protos); *err = g_strdup_printf("Could not find dissector for: '%s'", protos[i]); return FALSE; } } .... } 

protos é uma variedade de strings. Durante o processamento de uma exceção no programa, essa matriz é limpa primeiro pela função g_strfreev e, em seguida, uma das linhas dessa matriz é usada na mensagem de erro. Provavelmente, essas linhas no código devem ser trocadas:

 *err = g_strdup_printf("Could not find dissector for: '%s'", protos[i]); g_strfreev(protos); 

Vazamentos de memória


V773 O ponteiro 'ptmpstr' recebeu valores duas vezes sem liberar a memória. É possível um vazamento de memória. idl2wrs.c 2436

 static void parsetypedefunion(int pass) { char tmpstr[BASE_BUFFER_SIZE], *ptmpstr; .... while(num_pointers--){ g_snprintf(tmpstr, BASE_BUFFER_SIZE, "%s_%s", ptmpstr, "unique"); FPRINTF(eth_code, "static int\n"); FPRINTF(eth_code, "....", tmpstr); FPRINTF(eth_code, "{\n"); FPRINTF(eth_code, " ....", ptmpstr, ti->str); FPRINTF(eth_code, " return offset;\n"); FPRINTF(eth_code, "}\n"); FPRINTF(eth_code, "\n"); ptmpstr=g_strdup(tmpstr); } .... } 

Após a função g_strdup , você precisa chamar a função g_free em algum momento. No fragmento de código apresentado, isso não é feito e, no loop a cada iteração, uma nova parte da RAM é alocada. Vários vazamentos de memória ocorrem.

Mais alguns avisos para trechos de código semelhantes:

  • V773 O ponteiro 'ptmpstr' recebeu valores duas vezes sem liberar a memória. É possível um vazamento de memória. idl2wrs.c 2447
  • V773 O ponteiro 'ptmpstr' recebeu valores duas vezes sem liberar a memória. É possível um vazamento de memória. idl2wrs.c 2713
  • V773 O ponteiro 'ptmpstr' recebeu valores duas vezes sem liberar a memória. É possível um vazamento de memória. idl2wrs.c 2728
  • V773 O ponteiro 'ptmpstr' recebeu valores duas vezes sem liberar a memória. É possível um vazamento de memória. idl2wrs.c 2732
  • V773 O ponteiro 'ptmpstr' recebeu valores duas vezes sem liberar a memória. É possível um vazamento de memória. idl2wrs.c 2745

Infelizmente, existem muitos outros lugares semelhantes no código em que a memória não é liberada.

Diversos


Aviso 1

V535 A variável 'i' está sendo usada para esse loop e para o loop externo. Verifique as linhas: 7716, 7798. packet-opa-mad.c 7798

 /* Parse GetVFInfo MAD from the Performance Admin class. */ static gint parse_GetVFInfo(....) { .... for (i = 0; i < records; i++) { // <= line 7716 .... for (i = 0; i < PM_UTIL_BUCKETS; i++) { // <= line 7748 GetVFInfo_Util_Stats_Bucket_item = proto_tree_add_item(....); proto_item_set_text(....); local_offset += 4; } .... for (i = 0; i < PM_ERR_BUCKETS; i++) { // <= line 7798 GetVFInfo_Error_Stats_Bucket_item = proto_tree_add_item(....); proto_item_set_text(....); local_offset += 4; .... } .... } .... } 

Em uma função muito longa, os desenvolvedores alteram ousadamente o valor do contador de loop e fazem isso várias vezes. É difícil dizer se isso é um erro ou não, mas existem cerca de 10 desses ciclos no projeto.

Aviso 2

V763 O parâmetro 'item' é sempre reescrito no corpo da função antes de ser usado. packet-cdma2k.c 1324

 static void cdma2k_message_ORDER_IND(proto_item *item, ....) { guint16 addRecLen = -1, ordq = -1, rejectedtype = -1; guint16 l_offset = -1, rsc_mode_ind = -1, ordertype = -1; proto_tree *subtree = NULL, *subtree1 = NULL; item = proto_tree_add_item(tree,hf_cdma2k_OrderIndMsg, tvb, ....); // <= subtree = proto_item_add_subtree(item, ett_cdma2k_subtree1); .... } 

O ponteiro do item que a função utiliza é imediatamente desfiado por outro valor. Isso é muito suspeito. Além disso, o código contém várias dezenas de lugares, por isso é difícil dizer se isso é um erro ou não. Eu conheci um código semelhante anteriormente em outro projeto grande, lá estava o código correto, apenas ninguém se atreveu a mudar a interface da função.

Aviso 3

V762 É possível que uma função virtual tenha sido substituída incorretamente. Consulte o terceiro argumento da função 'headerData' na classe derivada 'PacketListModel' e na classe base 'QAbstractItemModel'. packet_list_model.h 48

 QVariant QAbstractItemModel::headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const // <= class PacketListModel : public QAbstractItemModel { Q_OBJECT public: .... QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole | Qt::ToolTipRole) const; // <= .... }; 

O analisador detectou uma sobrecarga incorreta da função headerData . As funções têm um valor padrão diferente para o parâmetro role . Isso pode não levar ao comportamento esperado pelo programador.

Aviso 4

V610 Comportamento indefinido. Verifique o operador de turno '>>'. O operando direito ('deslocamento de bits' = [0..64]) é maior ou igual ao comprimento em bits do operando esquerdo promovido. proto.c 10941

 static gboolean proto_item_add_bitmask_tree(...., const int len, ....) { .... if (len < 0 || len > 8) g_assert_not_reached(); bitshift = (8 - (guint)len)*8; available_bits = G_GUINT64_CONSTANT(0xFFFFFFFFFFFFFFFF) >> bitshift; .... } 

Uma mudança de 64 bits resultará em um comportamento indefinido de acordo com o padrão de idioma.

Em vez disso, o código correto deve ser assim:

 if (bitshift == 64) available_bits = 0; else available_bits = G_GUINT64_CONSTANT(0xFFFFFFFFFFFFFFFF) >> bitshift; 

Conclusão


Pode parecer que há poucos exemplos de erros na revisão, mas no relatório completo os casos apresentados são repetidos dezenas e centenas de vezes. As visões gerais de aviso do PVS-Studio são apenas para fins de demonstração. Isso é uma contribuição para a qualidade dos projetos de código aberto, mas as verificações únicas são a maneira mais ineficiente de aplicar a metodologia de análise estática.

Você pode obter e analisar o relatório completo por conta própria. Para fazer isso, basta baixar e executar o analisador PVS-Studio.



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Svyatoslav Razmyslov. Wireshark 3.x: análise de código no macOS e revisão de erros

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


All Articles