Trampas Caza bobos y desbordamientos de buffer

El blog del equipo de Visual C++ de Microsoft ha publicado una muy interesante entrada sobre trampas caza bobos para detectar e impedir desbordamientos de buffer en programas escritos en C++. Las técnicas para ello han tenido bastante éxito, llegando a detener una buena cantidad de virus y otro malware a partir de Windows Server 2003, que fue el primer sistema operativo compilado usando estas técnicas. Para aquellos que no lo sepan, los sistemas operativos de Microsoft se compilan con el mismo compilador que trae Visual C++… Bueno, no es exactamente así, ya que ellos tienen una versión interna algo más antigua y aparentemente más estable que la que sale con las ediciones comerciales (en general era –no sé si sigue siendo- la que salía en los SDK del correspondiente Windows). Así que cuando nosotros estamos compilando nuestros proyectos tenemos la certeza de que el sistema operativo tiene la misma buena (o mala) calidad de código generado.

Bueno, pero me he ido un poco por las ramas. Lo que yo llamo caza bobos se introdujo por primera vez en la versión 2002 del compilador, y ha ido evolucionando poco a poco, tal y como se cuenta en la entrada citada.

Básicamente, lo que hace la opción GS del compilador, que viene activada por defecto, es insertar una serie de firmas dentro de nuestro código generado de forma que si alguna de ellas ha cambiado, nos encontramos ante un intento de desbordamiento de buffer. Eso no quiere necesariamente decir que estén intentando inyectar código en nuestro programa, sino que también un bug en nuestro programa esté produciendo un desbordamiento, una variante de tener un puntero loco como habitualmente suele decirse.

No obstante este último problema no suele ser muy común si hemos hecho bien los deberes, ya que las compilaciones debug de Visual C++ (a partir de la versión 2005, creo) son fantásticas a la hora de cazarnos desbordamientos y memoria asignada y no liberada o justo lo contrario. Tan sólo hay que mirar la ventana de Output y ver las barrabasadas que hemos cometido.

Esta característica se hace en detrimento de otros parámetros, ya que aumenta las diferencias entre compilación debug y compilación release, de modo que en muchas situaciones un programa que funcionaba bien en debug tendrá problemas en release, que deberán ser solucionados en esa etapa.

No, no es un inconveniente, sino una ventaja, ya que hemos separado dos de los problemas más comunes en un desarrollo serio: errores en los trasteos con la memoria y errores de sincronización y procesos. No es muy infrecuente partirte los sesos pensando que tienes un puntero loco para luego descubrir que es un tema de sincronización (un hilo esté escribiendo o leyendo cuando no debe en un lugar compartido) o justo lo contrario.

Pues bien, la idea de la opción GS es la de colocar bytes sin sentido dentro de la pila de llamadas o en las fronteras de los bufferes asignados, de modo que cuando se retorne de una función se verifique dicha semilla, y si no es correcta es que tenemos un problema. Una ventaja de esta técnica es que no es necesaria en todas y cada una de las funciones de nuestro programa, sino solamente en aquellas en que la heurística del compilador así lo determine.

Es algo muy sencillo y que apenas usa memoria y tiempo de ejecución, y nos puede salvar de la entrada de un virus o de futuras corrupciones de otras variables que podrían terminar en la completa pérdida de nuestros datos.

Evidentemente todo es saltable en este mundo, pero estamos hablando de palabras mayores, ya que las citadas firmas pueden ser completamente diferentes y estar en lugares diferentes no sólo en cada compilación, sino también en cada ejecución del programa protegido, por lo que su violación es prácticamente imposible.

Quien quiera saber más detalles técnicos sobre esto, que lea el artículo en cuestión, en donde también vienen otros enlaces para seguir investigando el tema.

Actualización: Añado un comentario de Luis Guerrero:

Siguiendo con el articulo de Rafael Ontivero, en Windows Vista el equipo de producto, utilizo una caracteristica muy interesante llamada SAL (Standart Annotation Language) en el que marcaron todos los parametros de todas las funciones de WIN32 con «attributos» como «__in» «__out» «__in_opt» que permiter saber el compilador de c/c++ cual es el sentido del parametro y hacer comprobaciones en la pila de la llamada para asegurar la integridad de la pila y de stackframe de llamadas, esto junto con las cookies de seguridad que se comentan en el articulo hacen del codigo algo mucho más seguro.

Saludos. Luis.

Historia de C++ (Diseño y evolución)

·A History of C++: 1979-1991

·The Design and Evolution of C++

·Evolving a language in and for the real world: C++ 1991-2006

·Design Rationale for C++/CLI (Existe versión en castellano traducida por Octavio Hernández y yo mismo).

Esta entrada iba a ser el comentario del libro The Design and Evolution of C++, pero al hacer referencia éste al primer paper, así como la segunda parte del mismo hacer referencia al libro, he decidido comentarlos todos de un golpe. Y ya de paso, también el Rationale de C++/CLI, que en cierta medida es la evolución de los tres anteriores ya que algunos aspectos de él se van a incluir en el próximo C++0x pero en relación al código nativo.

El primero de todos da un rápido repaso y justificación de C++ hasta su versión 2.0 y ciertos avances de la 3.0 y 3.1, momento en el que dejan de llamarse por un número y pasan a serlo por el año de estandarización. No obstante la antigüedad del documento, todavía conserva cierta frescura y continúa siendo plenamente válido como documento histórico y justificativo del nacimiento del C++. En él también encontramos explicaciones sobre el contenido de C++, desde los moldeos hasta las plantillas, pasando por la gestión de excepciones o la herencia múltiple.

El libro, The Design and Evolution of C++ expande el documento anterior y se centra en dos partes. En la primera vuelve a repasar la historia de C++ con mucho más detalle y atención, aunque curiosamente hay muchos párrafos del documento anterior repetidos al más puro estilo Herbert Schildt…

No obstante, lo mejor del libro es la segunda parte, en la que asistimos a detalladas explicaciones históricas y justificativas de todos y cada uno de los aspectos más importantes de C++, desde por qué no se implementó un recolector automático de basura hasta el motivo por el cual las plantillas no aparecieron hasta la versión 3.0 pese a estar diseñadas y previstas desde el mítico C con clases, pasando por la sobrecarga de operadores y otros muchos conceptos.

El libro se escribió en 1994, y desde entonces ha llovido, y mucho, para C++. El segundo paper, cierra hasta la fecha el ciclo, contándonos qué ha ocurrido desde 1991 hasta 2006, momento en el que se empieza a cerrar el nuevo estándar C++0x. Este documento es el menos técnico de los tres, pero importante para que veamos qué va a ocurrir en el próximo estándar, que si no pasa nada se refrenda este mismo año, 2009.

Por último, y solamente por afán de completitud, incluimos el Rationale de C++/CLI, en el que Sutter nos cuenta los por qué y los cómo de las extensiones CLI que, recordemos, pese a estar implementadas por Microsoft, son un estándar.

No recomiendo estos documentos a nadie que no esté interesado de verdad en C++, ya que son densos y presuponen que el lector está más que familiarizado con lo que se habla. Además, en ciertos momentos podrían llevar a confusión al lector inseguro de C++, ya que son documentos históricos –aunque no obsoletos- y tratan al C++ en sus aspectos evolutivos. Eso no quiere decir que lo que se cuente ya no sea así, sino simplemente que existen formas mucho más didácticas de explicar C++.

Sobre el párrafo anterior, el autor de esta entrada lo ha sufrido en sus carnes, ya que no es muy ducho en el tema de las plantillas (sabe usarlas y quizá crearlas si no son muy complejas) y al leer sobre las mismas en el libro ha terminado más confuso que instruido, por no decir que no se ha enterado mas que del primer párrafo y eso porque no trataba de ellas…

Interop en C# y C++/CLI. Dos puntos de vista de un mismo concepto.

image El número 57 de la revista dotNetManía (que se corresponde a marzo del corriente) publica mi artículo de título homónimo a esta entrada que, como su nombre indica, repasa ciertas formas de Interop entre C# y una DLL nativa, así como C++/CLI y la misma DLL. Entre otras cosas explico cómo convertir un delegado en un callback nativo para ser pasado a una DLL que los requiera, cómo hacer justo lo contrario, que es usar un callback nativo desde código manejado, aparte de otras cosas.

Además, en el artículo se habla sobre las convenciones de llamada y se dan una serie de directivas a la hora de construir programas .NET en diferentes arquitecturas que a su vez interoperen con código nativo también en diferentes arquitecturas.

Este artículo, junto con el del mes anterior titulado Implementando wrappers con C++/CLI escrito por Horacio Núñez Hernández, suman un buen punto de partida para entender cómo envolver código nativo, así como la facilidad con que se realiza mediante C++/CLI frente a C# cuando el escenario es medianamente complejo.

Ya sabéis, no podéis perderos ambos números de la revista, aunque la mejro opción es la suscripción, y así nunca os perderéis ningún artículo interesante.