Vérification de rdesktop et xrdp avec PVS-Studio

Image3

Ceci est le deuxième article de notre série d'articles sur les résultats de la vérification des logiciels open source fonctionnant avec le protocole RDP. Aujourd'hui, nous allons jeter un œil au client rdesktop et au serveur xrdp.

L'analyse a été réalisée par PVS-Studio . Il s'agit d'un analyseur statique de code écrit en C, C ++, C # et Java, et il s'exécute sur Windows, Linux et macOS.

Je ne discuterai que des bugs qui m'ont paru les plus intéressants. En revanche, comme les projets sont assez petits, ils ne contiennent pas beaucoup de bugs de toute façon :).

Remarque L'article précédent sur la vérification de FreeRDP est disponible ici .

rdesktop


rdesktop est un client RDP gratuit pour les systèmes UNIX. Il peut également fonctionner sous Windows s'il est construit sous Cygwin. rdesktop est publié sous GPLv3.

Ceci est un client très populaire. Il est utilisé comme client par défaut sur ReactOS, et vous pouvez également trouver des interfaces graphiques tierces pour l'accompagner. Le projet est assez ancien, cependant: il est sorti pour la première fois le 4 avril 2001 et a 17 ans au moment de la rédaction de ce document.

Comme je l'ai déjà dit, le projet est très petit - environ 30 KLOC, ce qui est un peu étrange compte tenu de son âge. Comparez cela avec FreeRDP avec son 320 KLOC. Voici la sortie de Cloc:

Image1


Code inaccessible


V779 Code inaccessible détecté. Il est possible qu'une erreur soit présente. 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); } 

La première erreur se trouve immédiatement dans la fonction principale : le code suivant l'instruction return était censé libérer la mémoire allouée précédemment. Mais ce défaut n'est pas dangereux car toute la mémoire précédemment allouée sera libérée par le système d'exploitation une fois le programme terminé.

Pas de gestion d'erreur


V557 Le sous-ensemble de la baie est possible. La valeur de l'indice 'n' pourrait atteindre -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); } .... } 

Le contenu du fichier est lu dans le tampon jusqu'à ce que EOF soit atteint. Dans le même temps, ce code n'a pas de mécanisme de gestion des erreurs, et si quelque chose se passe mal, read renverra -1 et l'exécution commencera à lire au-delà des limites du tableau de sortie .

Utilisation d'EOF dans char


V739 EOF ne doit pas être comparé à une valeur de type 'char'. Le '(c = fgetc (fp))' doit être de type '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++; } .... } 

Ce code implémente une gestion EOF incorrecte: si fgetc renvoie un caractère dont le code est 0xFF, il sera interprété comme la fin du fichier ( EOF ).

EOF est une constante généralement définie comme -1. Par exemple, dans le codage CP1251, la dernière lettre de l'alphabet russe est codée comme 0xFF, ce qui correspond au nombre -1 dans le type char . Cela signifie que le caractère 0xFF, tout comme EOF (-1), sera interprété comme la fin du fichier. Pour éviter de telles erreurs, le résultat renvoyé par la fonction fgetc doit être stocké dans une variable de type int .

Typos


Extrait 1

V547 L' expression 'write_time' est toujours fausse. 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; // <= .... } 

L'auteur de ce code doit avoir accidentellement utilisé le || opérateur au lieu de && dans la condition. Voyons quelles valeurs peuvent avoir les variables write_time et change_time :
  • Les deux variables ont 0. Dans ce cas, l'exécution passe à la branche else : la variable mod_time sera toujours évaluée à 0 quelle que soit la condition suivante.
  • L'une des variables a 0. Dans ce cas, mod_time se verra attribuer 0 (étant donné que l'autre variable a une valeur non négative) puisque MIN choisira le moins des deux.
  • Aucune variable n'a 0: la valeur minimale est choisie.

Changer cette ligne en write_time && change_time corrigera le comportement:
  • Une seule variable ou aucune n'a 0: la valeur non nulle est choisie.
  • Aucune variable n'a 0: la valeur minimale est choisie.

Extrait 2

V547 L' expression est toujours vraie. L'opérateur '&&' devrait probablement être utilisé ici. 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; .... } 

Encore une fois, cela ressemble au problème de l'utilisation du mauvais opérateur - soit || au lieu de && ou == au lieu de ! = car la variable ne peut pas stocker les valeurs 20 et 9 en même temps.

Copie de chaînes illimitée


V512 Un appel de la fonction 'sprintf' entraînera un débordement de la mémoire tampon 'fullpath'. 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); .... } 

Si vous pouviez suivre la fonction jusqu'à la fin, vous verriez que le code est OK, mais il peut être cassé un jour: un seul changement imprudent se terminera par un débordement de tampon car sprintf n'est pas limité en aucune façon, donc concaténation les chemins pourraient prendre l'exécution au-delà des limites du tableau. Nous vous recommandons de remplacer cet appel par snprintf (fullpath, PATH_MAX, ....) .

État redondant


V560 Une partie de l'expression conditionnelle est toujours vraie: ajoutez> 0. scard.c 507

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

La vérification add> 0 ne fait aucune différence car la variable sera toujours supérieure à zéro car la lecture% 4 renvoie le reste, qui ne sera jamais égal à 4.

xrdp


xrdp est un serveur RDP open source. Le projet se compose de deux parties:

  • xrdp - l'implémentation du protocole. Il est publié sous Apache 2.0.
  • xorgxrdp - une collection de pilotes Xorg à utiliser avec xrdp. Il est publié sous X11 (tout comme le MIT, mais son utilisation dans la publicité est interdite)

Le développement est basé sur rdesktop et FreeRDP. Initialement, pour pouvoir travailler avec des graphiques, vous auriez dû utiliser un serveur VNC séparé ou un serveur X11 spécial avec prise en charge RDP, X11rdp, mais ceux-ci sont devenus inutiles avec la sortie de xorgxrdp.

Nous ne parlerons pas de xorgxrdp dans cet article.

Tout comme le projet précédent, xrdp est minuscule, composé d'environ 80 KLOC.

Image2


Plus de fautes de frappe


V525 Le code contient la collection de blocs similaires. Vérifiez les éléments «r», «g», «r» dans les lignes 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++; } .... } .... } 

Ce code provient de la bibliothèque librfxcodec, qui implémente le codec jpeg2000 pour fonctionner avec RemoteFX. Le canal de couleur "rouge" est lu deux fois, tandis que le canal "bleu" n'est pas lu du tout. De tels défauts résultent généralement de l'utilisation du copier-coller.

Le même bug a été trouvé dans la fonction similaire rfx_encode_format_argb :

V525 Le code contient la collection de blocs similaires. Vérifiez les éléments «a», «r», «g», «r» dans les lignes 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++; } 

Déclaration de tableau


V557 Le dépassement de matrice est possible. La valeur de l'indice «i - 8» pourrait atteindre 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]; .... } .... } 

Dans le fichier genkeymap.c, le tableau est déclaré 1 élément plus court que ce qu'implique l'implémentation. Aucun bogue ne se produira cependant, car le fichier evdev-map.c stocke la taille correcte, il n'y aura donc pas de dépassement de tableau, ce qui en fait un défaut mineur plutôt qu'une véritable erreur.

Comparaison incorrecte


V560 Une partie de l'expression conditionnelle est toujours fausse: (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)) { .... } .... } 

La valeur d'une variable de type unsigned short est lue dans une variable de type int puis vérifiée pour être négative, ce qui n'est pas nécessaire car une valeur lue d'un type non signé dans un type plus grand ne peut jamais devenir négative.

Contrôles redondants


V560 Une partie de l'expression conditionnelle est toujours vraie: (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; } .... } 

Les vérifications non égales ne sont pas nécessaires car la première vérification fait le travail. Le programmeur allait probablement utiliser le || pour filtrer les arguments incorrects.

Conclusion


La vérification d'aujourd'hui n'a révélé aucun bogue critique, mais elle a révélé un tas de défauts mineurs. Cela dit, ces projets, aussi petits soient-ils, sont toujours utilisés dans de nombreux systèmes et nécessitent donc un certain polissage. Un petit projet ne doit pas nécessairement contenir des tonnes de bogues, donc tester l'analyseur uniquement sur de petits projets n'est pas suffisant pour évaluer de manière fiable son efficacité. Ce sujet est abordé plus en détail dans l'article " Sentiments confirmés par les chiffres ".

La version de démonstration de PVS-Studio est disponible sur notre site Web .

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


All Articles