Estoy haciendo un curso en la universidad, donde uno de los laboratorios es realizar exploits de desbordamiento de búfer en el código que nos dan. Esto abarca desde exploits simples como cambiar la dirección de retorno de una función en una pila para volver a una función diferente, hasta el código que cambia el registro de un programa / estado de memoria pero luego regresa a la función que usted llamó, lo que significa que el La función que llamaste es completamente ajena al exploit.
Investigué un poco sobre esto, y este tipo de exploits se usan prácticamente en todas partes, incluso ahora, en cosas como ejecutar homebrew en Wii y el jailbreak sin ataduras para iOS 4.3.1
Mi pregunta es por qué este problema es tan difícil de solucionar. Es obvio que esta es una de las principales vulnerabilidades utilizadas para hackear cientos de cosas, pero parece que sería bastante fácil de solucionar simplemente truncando cualquier entrada más allá de la longitud permitida y simplemente desinfectando toda la entrada que tome.
EDITAR: Otra perspectiva que me gustaría tener en cuenta las respuestas: ¿por qué los creadores de C no solucionan estos problemas al reimplementar las bibliotecas?
fuente
No es realmente inexacto decir que C es realmente "propenso a errores" por diseño . Aparte de algunos errores graves como
gets
, el lenguaje C no puede ser de otra manera sin perder la característica principal que atrae a las personas a C en primer lugar.C fue diseñado como un lenguaje de sistemas para actuar como una especie de "ensamblaje portátil". Una característica importante del lenguaje C es que, a diferencia de los lenguajes de nivel superior, el código C a menudo se asigna muy de cerca al código de máquina real. En otras palabras,
++i
generalmente es solo unainc
instrucción y, a menudo, puede obtener una idea general de lo que hará el procesador en tiempo de ejecución mirando el código C.Pero la adición de la comprobación implícita de los límites agrega una sobrecarga adicional, una sobrecarga que el programador no solicitó y podría no querer. Esta sobrecarga va más allá del almacenamiento adicional requerido para almacenar la longitud de cada matriz, o las instrucciones adicionales para verificar los límites de la matriz en cada acceso a la matriz. ¿Qué pasa con la aritmética de puntero? ¿O qué pasa si tiene una función que toma un puntero? El entorno de tiempo de ejecución no tiene forma de saber si ese puntero cae dentro de los límites de un bloque de memoria asignado legítimamente. Para realizar un seguimiento de esto, necesitaría una arquitectura de tiempo de ejecución seria que pueda verificar cada puntero contra una tabla de bloques de memoria asignados actualmente, momento en el que ya estamos entrando en el territorio de tiempo de ejecución administrado estilo Java / C #.
fuente
Creo que el problema real no es que este tipo de errores son difíciles de solucionar, pero que son tan fáciles de hacer: si utiliza
strcpy
,sprintf
y amigos en el camino (aparentemente) más simple que el trabajo puede, entonces usted probablemente ha abrió la puerta para un desbordamiento de búfer. Y nadie lo notará hasta que alguien lo explote (a menos que tenga muy buenas revisiones de código). Ahora agregue el hecho de que hay muchos programadores mediocres y que están bajo presión de tiempo la mayor parte del tiempo, y tiene una receta para el código que está tan plagado de desbordamientos de búfer que será difícil solucionarlos todos simplemente porque hay muchos de ellos y se están escondiendo muy bien.fuente
sizeof(ptr)
es 4 u 8, en general. Esa es otra limitación de C: no hay forma de determinar la longitud de una matriz, dado solo el puntero a ella.#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / (sizeof(a) != sizeof(void *))
que desencadenará una división de tiempo de compilación por cero. Otra inteligente que vi por primera vez en Chromium es#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / !(sizeof(a) % sizeof((a)[0]))
que intercambia el puñado de falsos positivos por algunos falsos negativos, desafortunadamente es inútil para char []. Puede usar varias extensiones del compilador para hacerlo aún más confiable, por ejemplo, blogs.msdn.com/b/ce_base/archive/2007/05/08/… .Es difícil reparar los desbordamientos del búfer porque C no proporciona prácticamente herramientas útiles para abordar el problema. Es una falla fundamental del lenguaje que los búferes nativos no brinden protección y es prácticamente, si no completamente, imposible reemplazarlos con un producto superior, como lo hizo C ++ con
std::vector
ystd::array
, y es difícil incluso en modo de depuración encontrar desbordamientos de búfer.fuente
std::vector
se implementen de manera eficiente. Yvector::operator[]
hace la misma elección de velocidad sobre seguridad. La seguridadvector
viene de hacer que sea más fácil cargar alrededor del tamaño, que es el mismo enfoque que toman las bibliotecas C modernas.realloc
(C99 también le permite dimensionar matrices de pila usando un tamaño determinado en tiempo de ejecución pero constante a través de cualquier variable automática, casi siempre preferiblechar buf[1024]
). Segundo, el problema no tiene nada que ver con expandir los buffers, tiene que ver con si los buffers llevan o no el tamaño con ellos y verifican ese tamaño cuando accedes a ellos.vector::operator[]
hace la verificación de límites en modo de depuración, algo que las matrices nativas no pueden hacer, y en segundo lugar, en C no hay forma de cambiar el tipo de matriz nativa por una que pueda hacer la verificación de límites, porque no hay plantillas ni operador sobrecarga En C ++, si quiere pasar deT[]
astd::array
, prácticamente puede cambiar un typedef. En C, no hay forma de lograr eso, y no hay forma de escribir una clase con una funcionalidad equivalente, y mucho menos la interfaz.std::vector<T>
ystd::array<T, N>
se puede hacer en C ++. No habría forma de diseñar y especificar ninguna biblioteca, ni siquiera estándar, que pudiera hacer esto.std::vector
tampoco puede tener un tamaño estático. En cuanto a genérico, puede hacerlo tan genérico como lo necesite C: una pequeña cantidad de operaciones fundamentales en void * (agregar, eliminar, cambiar el tamaño) y todo lo demás escrito específicamente. Si va a quejarse de que C no tiene genéricos de estilo C ++, eso está fuera del alcance del manejo seguro del búfer.El problema no es con la C lenguaje .
En mi opinión, el obstáculo principal que hay que superar es que C simplemente se enseña mal . Se han institucionalizado décadas de malas prácticas e información incorrecta en manuales de referencia y notas de conferencias, envenenando las mentes de cada nueva generación de programadores desde el principio. Los estudiantes reciben una breve descripción de las funciones de E / S "fáciles", como
gets
1 o,scanf
y luego se dejan en sus propios dispositivos. No se les dice dónde ni cómo pueden fallar esas herramientas, ni cómo prevenirlas. No se les dice sobre el usofgets
ystrtol/strtod
porque se consideran herramientas "avanzadas". Luego se desatan en el mundo profesional para causar estragos. No es que muchos de los programadores más experimentados conozcan mejor, porque recibieron la misma educación sobre daño cerebral. Es enloquecedor. Veo muchas preguntas aquí y en Stack Overflow y en otros sitios donde está claro que la persona que hace la pregunta está siendo enseñada por alguien que simplemente no sabe de qué está hablando , y por supuesto, no se puede decir "tu profesor está equivocado", porque él es profesor y tú solo eres un tipo en Internet.Y luego tienes la multitud que desdeña cualquier respuesta que comience con, "bueno, de acuerdo con el estándar del idioma ..." porque están trabajando en el mundo real y según ellos, el estándar no se aplica al mundo real . Puedo tratar con alguien que solo tiene una mala educación, pero cualquiera que insista en ser ignorante es solo una plaga para la industria.
No habría problemas de desbordamiento del búfer si el idioma se enseñara correctamente con énfasis en escribir código seguro. No es "difícil", no es "avanzado", solo es tener cuidado.
Sí, esto ha sido una queja.
1 Que, afortunadamente, finalmente se ha eliminado de la especificación del lenguaje, aunque estará al acecho en 40 años de código heredado para siempre.
fuente
sprintf
, pero eso no significa que el idioma no era la adecuada. C tenía fallas y tiene fallas, como cualquier lenguaje, y es importante que admitamos esas fallas para poder continuar reparándolas.El problema es tanto la miopía gerencial como la incompetencia del programador. Recuerde, una aplicación de 90,000 líneas necesita solo una operación insegura para ser completamente insegura. Está casi más allá de los límites de la posibilidad de que cualquier aplicación escrita sobre el manejo de cadenas fundamentalmente inseguras sea 100% perfecta, lo que significa que será insegura.
El problema es que los costos de la inseguridad no se cargan al destinatario correcto (la empresa que vende la aplicación casi nunca tendrá que reembolsar el precio de compra), o no son claramente visibles en el momento en que se toman las decisiones ("Tenemos que enviar en marzo pase lo que pase "). Estoy bastante seguro de que si considerara los costos a largo plazo y los costos para sus usuarios en lugar de las ganancias de su empresa, escribir en C o lenguajes relacionados sería mucho más costoso, probablemente tan costoso que claramente es la elección incorrecta en muchos campos donde hoy en día la sabiduría convencional dice que es una necesidad. Pero eso no cambiará a menos que se introduzca una responsabilidad de software mucho más estricta, lo que nadie en la industria quiere.
fuente
Uno de los grandes poderes del uso de C es que le permite manipular la memoria de la forma que mejor le parezca.
Una de las grandes debilidades del uso de C es que le permite manipular la memoria de la forma que mejor le parezca.
Hay versiones seguras de cualquier función insegura. Sin embargo, los programadores y el compilador no imponen estrictamente su uso.
fuente
Probablemente porque C ++ ya hizo esto y es compatible con el código C. Entonces, si desea un tipo de cadena seguro en su código C, simplemente use std :: string y escriba su código C usando un compilador C ++.
El subsistema de memoria subyacente puede ayudar a prevenir el desbordamiento del búfer mediante la introducción de bloques de protección y la verificación de validez de ellos, por lo que todas las asignaciones tienen 4 bytes de 'fefefefe' agregados, cuando estos bloques se escriben, el sistema puede arrojar un wobbler. No se garantiza que evite una escritura en la memoria, pero mostrará que algo salió mal y necesita ser reparado.
Creo que el problema es que las viejas rutinas strcpy, etc., todavía están presentes. Si se eliminaron a favor de strncpy, etc., eso ayudaría.
fuente
Es simple entender por qué el problema de desbordamiento no está solucionado. C tenía defectos en un par de áreas. En ese momento, esos defectos eran vistos como tolerables o incluso como una característica. Ahora, décadas después, esos defectos no se pueden solucionar.
Algunas partes de la comunidad de programación no quieren que se tapen esos agujeros. Solo mira todas las guerras de llamas que comienzan con cadenas, matrices, punteros, recolección de basura ...
fuente
memcpy()
disponible y que sea solo un medio estándar para copiar eficientemente un segmento de matriz.