Validación de rdesktop y xrdp con el analizador PVS-Studio

Imagen3

Esta es la segunda revisión de una serie de artículos sobre la verificación de programas de código abierto para trabajar con el protocolo RDP. En él, consideraremos el cliente rdesktop y el servidor xrdp.

PVS-Studio se utilizó como herramienta para detectar errores. Es un analizador de código estático para C, C ++, C # y Java, disponible en Windows, Linux y macOS.

El artículo presenta solo aquellos errores que me parecieron interesantes. Sin embargo, los proyectos son pequeños, por lo que hubo pocos errores :).

Nota El artículo anterior sobre cómo verificar el proyecto FreeRDP se puede encontrar aquí .

rdesktop


rdesktop es una implementación gratuita del cliente RDP para sistemas basados ​​en UNIX. También se puede usar en Windows, si crea un proyecto en Cygwin. Licenciado bajo GPLv3.

Este cliente es muy popular: se usa de forma predeterminada en ReactOS y también puede encontrar interfaces gráficas de terceros para él. Sin embargo, es bastante viejo: el primer lanzamiento tuvo lugar el 4 de abril de 2001; en el momento de escribir este artículo, su edad es de 17 años.

Como señalé anteriormente, el proyecto es muy pequeño. Contiene alrededor de 30 mil líneas de código, lo cual es un poco extraño dada su antigüedad. A modo de comparación, FreeRDP contiene 320 mil líneas. Aquí está la salida del programa Cloc:

Imagen1


Código inalcanzable


V779 Código inalcanzable detectado. Es posible que haya un error presente. 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); } 

El error nos encuentra de inmediato en la función principal : vemos el código que viene después de la declaración de devolución : este fragmento limpia la memoria. Sin embargo, el error no representa una amenaza: el sistema operativo limpia toda la memoria asignada después de que finaliza el programa.

Falta de manejo de errores


V557 Array underrun es posible. El valor del índice 'n' podría alcanzar -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); } .... } 

El fragmento de código en este caso se lee desde el archivo al búfer hasta que finaliza el archivo. Sin embargo, no hay manejo de errores aquí: si algo sale mal, entonces leer devolverá -1, y luego la matriz de salida irá más allá de los límites de la matriz.

Usando EOF en char


V739 EOF no debe compararse con un valor del tipo 'char'. El '(c = fgetc (fp))' debe ser del 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++; } .... } 

Aquí vemos un procesamiento incorrecto de llegar al final del archivo: si fgetc devuelve un carácter cuyo código es 0xFF, se interpretará como el final del archivo ( EOF ).

EOF es una constante, generalmente definida como -1. Por ejemplo, al codificar CP1251, la última letra del alfabeto ruso tiene el código 0xFF, que corresponde al número -1, si estamos hablando de una variable de tipo char . Resulta que el carácter 0xFF, como EOF (-1), se percibe como el final del archivo. Para evitar tales errores, el resultado de la función fgetc debe almacenarse en una variable int .

Errores tipográficos


Fragmento 1

V547 La expresión 'write_time' siempre es 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; // <= .... } 

Quizás el autor de este código confundió || y && proporcionado. Considere las posibles opciones para write_time y change_time :

  • Ambas variables son 0: en este caso, llegamos a la rama else : la variable mod_time siempre será 0 independientemente de la siguiente condición.
  • Una de las variables es 0: mod_time será 0 (siempre que la otra variable tenga un valor no negativo), porque MIN elegirá la más pequeña de las dos opciones.
  • Ambas variables no son iguales a 0: seleccionamos el valor mínimo.

Al reemplazar una condición con write_time && change_time, el comportamiento se verá correcto:
  • Una o ambas variables no son iguales a 0: seleccione un valor distinto de cero.
  • Ambas variables no son iguales a 0: seleccionamos el valor mínimo.

Fragmento 2

La expresión V547 es siempre cierta. Probablemente el operador '&&' debería usarse aquí. 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; .... } 

Aparentemente, los operadores || y && , ya sea == y ! = : la variable no puede tomar los valores 20 y 9 al mismo tiempo.

Copia de línea ilimitada


V512 Una llamada de la función 'sprintf' conducirá al desbordamiento del búfer '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); .... } 

Al considerar la función, quedará completamente claro que este código no causa problemas. Sin embargo, pueden surgir en el futuro: un cambio descuidado, y obtenemos un desbordamiento del búfer: sprintf no está limitado por nada, por lo que al concatenar rutas, podemos ir más allá de los límites de la matriz. Se recomienda notar esta llamada en snprintf (fullpath, PATH_MAX, ....) .

Condición redundante


V560 Una parte de la expresión condicional siempre es verdadera: add> 0. scard.c 507

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

Comprobar add> 0 es inútil: la variable siempre será mayor que cero, porque leer% 4 devolverá el resto de la división, y nunca será 4.

xrdp


xrdp es una implementación de servidor RDP de código abierto. El proyecto se divide en 2 partes:

  • xrdp: implementación del protocolo. Distribuido bajo la licencia Apache 2.0.
  • xorgxrdp: un conjunto de controladores Xorg para usar con xrdp. Licencia - X11 (como MIT, pero prohíbe su uso en publicidad)

El desarrollo del proyecto se basa en los resultados de rdesktop y FreeRDP. Inicialmente, tuve que usar un servidor VNC separado para trabajar con gráficos, o un servidor X11 especial con soporte RDP - X11rdp, pero con el advenimiento de xorgxrdp, ya no eran necesarios.

En este artículo, no tocaremos xorgxrdp.

El proyecto xrdp, como el anterior, es muy pequeño y contiene alrededor de 80 mil líneas.

Imagen2


Más errores tipográficos


V525 El código contiene la colección de bloques similares. Verifique los elementos 'r', 'g', 'r' en las líneas 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++; } .... } .... } 

Este código fue tomado de la biblioteca librfxcodec que implementa el códec jpeg2000 para que RemoteFX funcione. Aquí, aparentemente, los canales de datos gráficos se mezclan, en lugar de color "azul", se escribe "rojo". Tal error probablemente apareció como resultado de copiar y pegar.

El mismo problema recayó en una función similar rfx_encode_format_argb , que el analizador también nos contó sobre:

V525 El código contiene la colección de bloques similares. Verifique los elementos 'a', 'r', 'g', 'r' en las líneas 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++; } 

Declaración de matriz


V557 Array overrun es posible. El valor del índice 'i - 8' podría alcanzar 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 declaración y la definición de la matriz en estos dos archivos es incompatible: el tamaño difiere en 1. Sin embargo, no se producen errores: el tamaño correcto se especifica en el archivo evdev-map.c, por lo que no hay forma de salir de los límites. Así que esto es solo una falla que es fácil de solucionar.

Comparación inválida


V560 Una parte de la expresión condicional siempre es 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)) { .... } .... } 

En una función, una variable de tipo unsigned short se lee en una variable de tipo int . La verificación no es necesaria aquí, porque leemos una variable de un tipo sin signo y asignamos el resultado a una variable más grande, por lo que la variable no puede tomar un valor negativo.

Controles innecesarios


V560 Una parte de la expresión condicional siempre es verdadera: (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; } .... } 

Las comprobaciones de desigualdad no tienen sentido aquí, porque ya tenemos una comparación al principio. Es probable que se trate de un error tipográfico y el desarrollador quisiera utilizar el || para filtrar argumentos inválidos.

Conclusión


Durante la inspección, no se detectaron errores graves, pero se encontraron muchas deficiencias. Sin embargo, estos proyectos se utilizan en muchos sistemas, aunque de pequeño alcance. Un proyecto pequeño no tiene que tener muchos errores, por lo que no debe juzgar el trabajo del analizador solo en proyectos pequeños. Puede leer más sobre esto en el artículo " Sensaciones, que se confirman con números ".

Puede descargar una versión de prueba de PVS-Studio en nuestro sitio web .



Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Sergey Larin. Comprobación de rdesktop y xrdp con PVS-Studio

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


All Articles