
Ceci est la deuxiÚme revue d'une série d'articles sur la vérification de programmes ouverts pour le protocole RDP. Dans cet article, nous examinerons le client rdesktop et le serveur xrdp.
Pour identifier les erreurs, l'outil utilisé était . Il s'agit d'un analyseur statique de code pour les langages C, C++, C# et Java, disponible sur les plateformes Windows, Linux et macOS.
L'article présente uniquement les erreurs qui m'ont semblé intéressantes. Cependant, les projets sont petits, donc il y avait peu d'erreurs :).
Remarque. L'article prĂ©cĂ©dent sur l'examen du projet FreeRDP peut ĂȘtre trouvĂ© .
rdesktop
â une implĂ©mentation libre du client RDP pour les systĂšmes basĂ©s sur UNIX. Il peut Ă©galement ĂȘtre utilisĂ© sous Windows si le projet est compilĂ© sous Cygwin. LicencĂ© sous GPLv3.
Ce client est trĂšs populaire â il est utilisĂ© par dĂ©faut dans ReactOS, et il existe Ă©galement des interfaces graphiques tierces disponibles. Cependant, il est assez ancien : la premiĂšre version a Ă©tĂ© publiĂ©e le 4 avril 2001 â au moment de la rĂ©daction de l'article, il a donc 17 ans.
Comme je l'ai déjà mentionné, le projet est vraiment petit. Il contient environ 30 000 lignes de code, ce qui est un peu étrange compte tenu de son ùge. à titre de comparaison, FreeRDP contient 320 000 lignes. Voici la sortie du programme Cloc :

Code inaccessible
Unreachable code detected. It is possible that an error is present. 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);
}L'erreur nous rencontre immĂ©diatement dans la fonction main: nous voyons du code apparaĂźtre aprĂšs l'instruction retourner â ce fragment effectue un nettoyage de la mĂ©moire. Cependant, l'erreur ne reprĂ©sente pas une menace : toute la mĂ©moire allouĂ©e sera nettoyĂ©e par le systĂšme d'exploitation aprĂšs l'achĂšvement du programme.
Absence de gestion des erreurs
Un sous-dimensionnement du tableau est possible. La valeur de l'index '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] = ' '; // <=
str_handle_lines(output, &rest, linehandler, data);
}
....
}Le fragment de code dans ce cas lit un fichier dans un tampon jusqu'à ce que le fichier soit épuisé. Cependant, la gestion des erreurs est absente : si quelque chose ne va pas, alors read retournera -1, ce qui entraßnera un dépassement de limite de tableau output.
Utilisation de EOF dans le type char
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++;
}
....
}Ici nous voyons un traitement incorrect de la fin de fichier : si fgetc renvoie un caractÚre dont la valeur est 0xFF, il sera interprété comme une fin de fichier (EOF).
EOF c'est une constante gĂ©nĂ©ralement dĂ©finie comme -1. Par exemple, dans l'encodage CP1251, la derniĂšre lettre de l'alphabet russe a un code de 0xFF, ce qui correspond au nombre -1, si nous parlons d'une variable de type char. Il en rĂ©sulte que le caractĂšre 0xFF, tout comme EOF (-1) est perçu comme une fin de fichier. Pour Ă©viter de telles erreurs, le rĂ©sultat de la fonction fgetc devrait ĂȘtre stockĂ© dans une variable de type int.
Fautes de frappe
Fragment 1
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; // <=
....
}Il est possible que l'auteur de ce code ait confondu || et && dans la condition. Considérons les valeurs possibles de write_time et change_time:
- Les deux variables sont égales à 0 : dans ce cas, nous entrerons dans la branche sinon: la variable mod_time sera toujours égale à 0 indépendamment de la condition suivante.
- L'une des variables est égale à 0 : mod_time sera égale à 0 (à condition que l'autre variable soit non négative), car MIN choisira le plus petit des deux.
- Les deux variables ne sont pas égales à 0 : choisissons la valeur minimale.
En remplaçant la condition par write_time && change_time le comportement deviendra correct :
- Une ou les deux variables ne sont pas égales à 0 : choisissons une valeur non nulle.
- Les deux variables ne sont pas égales à 0 : choisissons la valeur minimale.
Fragment 2
L'expression est toujours vraie. Il est probable que l'opĂ©rateur '&&' doive ĂȘ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;
....
}Il semble que les opérateurs aient également été confondus ici : || et &&, ou bien == et !=une variable ne peut pas prendre simultanément les valeurs 20 et 9.
Copie de chaßne non limitée
Un appel à la fonction 'sprintf' entraßnera un dépassement du tampon 'fullpath'. disk.c 1257
RD_NTSTATUS
disk_query_directory(....)
{
....
char *dirname, fullpath[PATH_MAX];
....
/* Obtenir les informations pour l'entrée du répertoire */
sprintf(fullpath, "%s/%s", dirname, pdirent->d_name);
....
}Lors de l'examen complet de la fonction, il devient clair que ce code ne pose pas de problĂšmes. Cependant, des problĂšmes peuvent survenir Ă l'avenir : un changement imprudent, et nous avons un dĂ©passement de tampon â sprintf n'est pas limitĂ©, donc lors de la concatĂ©nation des chemins, nous pouvons dĂ©passer les limites du tableau. Il est recommandĂ© de noter cet appel sur snprintf(fullpath, PATH_MAX, âŠ.).
Condition excessive
Une partie de l'expression conditionnelle est toujours vraie : add > 0. scard.c 507
static void
inRepos(STREAM in, unsigned int read)
{
SERVER_DWORD add = 4 - read % 4;
if (add 0)
{
....
}
}Vérification add > 0 ici, cela ne sert à rien : la variable sera toujours supérieure à zéro, car read % 4 renverra le reste de la division, et il ne sera jamais égal à 4.
xrdp
â implĂ©mentation d'un serveur RDP Ă code source ouvert. Le projet est divisĂ© en 2 parties :
- xrdp â implĂ©mentation du protocole. DistribuĂ© sous licence Apache 2.0.
- xorgxrdp â ensemble de pilotes Xorg pour utilisation avec xrdp. Licence â X11 (comme MIT, mais interdit l'utilisation dans la publicitĂ©)
Le dĂ©veloppement du projet est basĂ© sur les rĂ©sultats de rdesktop et FreeRDP. Initialement, pour le travail graphique, il Ă©tait nĂ©cessaire d'utiliser un serveur VNC sĂ©parĂ©, ou un serveur X11 spĂ©cial prenant en charge RDP â X11rdp, mais avec l'arrivĂ©e de xorgxrdp, ce besoin a disparu.
Dans cet article, nous ne traiterons pas de xorgxrdp.
Le projet xrdp, tout comme le précédent, est assez petit et contient environ 80 000 lignes.

Encore des fautes de frappe
Le code contient une collection de blocs similaires. VĂ©rifiez les Ă©lĂ©ments ârâ, âgâ, ârâ aux 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 a Ă©tĂ© extrait de la bibliothĂšque librfxcodec, qui implĂ©mente le codec jpeg2000 pour travailler avec RemoteFX. Ici, il semble que les canaux de donnĂ©es graphiques soient inversĂ©s â au lieu de la couleur « bleue », la « rouge » est enregistrĂ©e. Cette erreur est probablement survenue Ă la suite d'un copier-coller.
Ce mĂȘme problĂšme est Ă©galement prĂ©sent dans une fonction similaire rfx_encode_format_argb, comme nous l'a Ă©galement signalĂ© l'analyseur :
Le code contient une collection de blocs similaires. VĂ©rifiez les Ă©lĂ©ments âaâ, ârâ, âgâ, ârâ aux 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 du tableau
Un dĂ©passement de tableau est possible. La valeur de l'index â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];
....
}
....
}La dĂ©claration et la dĂ©finition du tableau dans ces deux fichiers sont incompatibles â la taille diffĂšre de 1. Cependant, aucune erreur ne se produit â le fichier evdev-map.c indique la taille correcte, donc il n'y a pas de dĂ©passement. C'est juste une nĂ©gligence, qui peut ĂȘtre facilement corrigĂ©e.
Comparaison incorrecte
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 fonction lit une variable de type unsigned short dans une variable de type int. La vérification ici n'est pas nécessaire, car nous lisons une variable de type non signé et assignons le résultat à une variable de taille supérieure, donc la variable ne peut pas prendre de valeur négative.
Vérifications inutiles
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: erreur");
return 1;
}
....
}Les vérifications d'inégalité ici n'ont pas de sens, car nous avons déjà une comparaison au début. Il est trÚs probable qu'il s'agisse d'une faute de frappe et que le développeur voulait utiliser l'opérateur || pour filtrer les arguments incorrects.
Conclusion
La vĂ©rification n'a rĂ©vĂ©lĂ© aucune erreur grave, mais de nombreuses petites erreurs ont Ă©tĂ© trouvĂ©es. NĂ©anmoins, ces projets sont utilisĂ©s dans de nombreux systĂšmes, mĂȘme s'ils sont petits en volume. Il n'est pas nĂ©cessaire qu'un petit projet contienne beaucoup d'erreurs, donc il ne faut pas juger le travail de l'analyseur uniquement sur de petits projets. Vous pouvez en lire plus dans l'article ««.
Vous pouvez télécharger la version d'essai de PVS-Studio chez nous sur .
Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction : Sergey Larin.
Source : habr.com
