¿Cuáles son los problemas de seguridad en el lenguaje C?

Recientemente leí sobre dos de ellos:

1. Desbordamiento del búfer: sucede cuando los datos escritos en un búfer (fragmento de memoria) se desbordan y se sobrescriben en la memoria adyacente debido a la falta de verificaciones de límites adecuadas. Permíteme mostrarte algo: considera el siguiente código que probé en Compilar y ejecutar C en línea, (Un compilador en línea de C):


Puede ver claramente que he asignado memoria para solo 8 enteros en la matriz ‘a’, pero he tratado de acceder y modificar el valor correspondiente al décimo entero en la matriz ‘a’ (una ubicación de memoria que pertenece a alguna otra entidad) . Ciertamente, no debe permitirse nada tan poco ético, muchachos … pero el resultado es bastante sorprendente, ¿no es así?


Luego, me volví un poco más ambicioso e intenté lo siguiente:


y obtuve la falla de segmentación más temida :


Entonces, ¿qué pasó ?

Bueno, C permitirá que un programa modifique una memoria siempre que pertenezca al mismo programa. En caso de un [9] , la ubicación de la memoria pertenecía al programa, pero en el caso de un [10000] no lo hizo.
(Tenga en cuenta que aunque la ubicación de memoria a [9] pertenecía al programa y podía modificarse, no era una buena práctica hacerlo, porque simplemente significa que está corrompiendo alguna otra información almacenada por el programa).

2. Doble llamada libre: como su nombre lo indica, ocurre cuando intenta liberar la memoria asignada a un puntero, que ya se ha liberado. Veamos algo genial:

(El código anterior se compiló y ejecutó en Compilar y ejecutar C en línea, (Un compilador en línea de C) )

Entonces, ¿cuál es el problema, me parece bastante bien?

El problema, queridos amigos, es que una vez que han liberado el puntero ‘a’, no saben si su nuevo valor es ‘NULL’ o si apunta a alguna otra ubicación. Si apunta a alguna otra ubicación, que puede ser, entonces también puede liberar esa memoria.

Entonces, intenté ver si un puntero liberado siempre se establece automáticamente en ‘NULL’, pero parece que no:



Pero no temas, amigos, porque todo este mundo no está lleno de oscuridad, y todavía hay algo de esperanza. El código donde intenté liberar el mismo puntero dos veces sin reasignación me arrojó un error con el mensaje: “doble libre o corrupción”. ¡Uf! … .., parece que este ha sido atendido.

(ps: me gustaría saber acerca de más vulnerabilidades en C, sería genial si otros pudieran compartirlas aquí).

—–

Editar: después de un comentario de Pawan Kumar, decido que debo agregar a mi respuesta que el segundo error ( doble llamada gratuita ) en realidad se puede evitar restableciendo el puntero una vez liberado a NULL . Por ejemplo, ver a continuación:


y la salida:


(El código anterior se compiló y ejecutó en Compilar y ejecutar C en línea, (Un compilador en línea de C) )

  • Uno de los mayores problemas de seguridad con los programas C y C ++ es el desbordamiento del búfer.

Hay muchos tipos de ataques de desbordamiento de búfer (incluidos los ataques de aplastamiento de pila y de montón ).
Se produce un desbordamiento del búfer cuando escribe un conjunto de valores (generalmente una cadena de caracteres) en un búfer de longitud fija y escribe al menos un valor fuera de los límites de ese búfer (generalmente más allá de su final). Se puede producir un desbordamiento del búfer al leer la entrada del usuario en un búfer, pero también puede ocurrir durante otros tipos de procesamiento en un programa.

Les presento una lista de vulnerabilidades de seguridad:

  • Desbordamiento de pila

Desbordamiento de búfer en la pila
Sobrescribir la dirección del remitente
Localización de la posición de la dirección del remitente

  • Desbordamiento de montón

Asignación de memoria gestionada por una lista doblemente vinculada
Desbordamiento de búfer en el montón
Pasos para liberar y unir bloques de memoria
Liberación de bloques de memoria asignados
Mezclando borrar y borrar []

  • Problemas de enteros en C / C ++

Tipos de problemas de enteros
Representación de enteros negativos.
Representación entera usando el complemento de dos
Rangos enteros
La regla de promoción de enteros en C / C ++
Promoción entera de enteros con y sin signo

  • Error de cadena de formato Printf
  • Otras vulnerabilidades de seguridad comunes

Indización de matriz: ¡detecta el error!
Error Unicode

  • Defectos varios

Un ejemplo de fuga de información
Errores de serialización (TOCTTOU)
Archivos temporales / a C ++ TOCTOU vulnerabilidad
Riesgos por mecanismos de señalización.

  • Archivo de riesgos de E / S

Vulnerabilidad transversal del directorio
Vulnerabilidad de enlace simbólico

Sin verificación de límites , lo que lleva a, por ejemplo, desbordamientos del búfer (entiendo que se puede agregar, pero creo que no en general, y generalmente cuando se agrega solo en desarrollo y se deshabilita para versiones lanzadas por razones de rendimiento …). Esto significa que puede escribir fuera de las áreas asignadas (incluso en la pila, cambiando el flujo de control). La aritmética del puntero no es realmente un problema de seguridad (sin embargo, puede detener las optimizaciones, lo que hace que C SLOWER no sea más rápido), ese es el problema.

Las cadenas C (y los problemas de formateo de printf en particular), y los problemas con ellos son un síntoma de esto ( Y pueden hacer que C sea más lento).

Gestión manual de la memoria en lugar de recolección de basura, lo que lleva a usar after-free .

Asignaciones de pila; son más rápidos, también son un problema potencial con el uso después de salir del alcance . *

Algunos problemas con C son perjudiciales para la seguridad sin beneficios de [velocidad] (p . Ej., Cambiar con falla automática, el valor predeterminado debe ser la ruptura, la falla solicitada). Algunos también parecen ser peores para los humanos no infalibles , como los códigos de error sobre el manejo de excepciones. Puede que ni siquiera sea más rápido, según la investigación .

Las variables no se inicializan por defecto.

Los desbordamientos no controlados también son potencialmente peligrosos, menos que en el pasado con mayor bitness; este y el anterior, se comparte con mi idioma favorito Julia; pero puede y mitiga esto mejor. Por ejemplo, C realmente no puede cambiar esto, creo.

Estas elecciones hicieron C más rápido (todo esto se aplica a C ++; las cadenas C en menor grado, y RAII también mitiga), y donde está bien, hace décadas. Ahora podemos permitirnos idiomas seguros, por diseño. En la mayoría de los casos, los lenguajes como Java, donde la seguridad no se puede desactivar, están bien. En algunos casos, está bien con idiomas que por defecto son seguros y puede elegir cuándo no ser seguros. El óxido viene a la mente. Julia es segura, excepto por algunas funciones que puede evitar usar. Ambos pueden ser tan rápidos como C / C ++.

Por ejemplo, en Julia tiene una verificación de límites, y aunque puede elegir deshabilitarlo, todavía lo tiene en parte (se aplica a matrices multidimensionales; en la mayoría de los casos). Por ejemplo, los índices de los bucles externos siempre se verifican y casi no tienen impacto en el rendimiento. Si la comprobación de límites en los bucles internos es un problema, siempre puede optar por desactivarlo con @inbounds para obtener la velocidad C completa.

* El análisis de escape le brinda asignaciones de pila (por ejemplo, en Java), sin tener que elegirlas manualmente en su código.

1. El uso incorrecto de strcpy es notorio por datos corruptos, por ejemplo, considere (un ejemplo simplificado para explicar el concepto)

#include
#include
#include
int main () {

char b4 = ‘a’;
char str [6];
char after = ‘x’;
char * dest = “Hola w”;
printf (“b4 =% c, después =% d \ n”, b4, después);
strcpy (str, dest);
printf (“b4 =% c, después =% d \ n”, b4, después);
}

Puede corromper la variable antes y / o después en función del crecimiento de la pila. Si b4 / after fuera un puntero, el puntero también se dañará. Esto es simplificado, por ejemplo, pero el punto aquí es que cada construcción de datos en C se asigna a una dirección de memoria. Mientras realiza operaciones de copia, puede dañar la ubicación de la memoria y, por lo tanto, la variable correspondiente asociada

Imagina que estas cosas suceden en el espacio del kernel, la estructura de datos de kerenel puede corromperse en OOPS

por lo tanto, la recomendación genérica se usa para strncpy, donde sabe exactamente la cantidad de datos que está copiando.

2. Para ilustración, considere lo siguiente
int x;
printf (“% s”, & x);

Dado que printf es var-args, todos los argumentos son válidos y el compilador no puede verificar la validez del argumento pasado. (tenga en cuenta que printf no es parte del lenguaje C simplemente una llamada a la biblioteca). Printf intentará acceder a la dirección & x, y leer & x, & x + 1 … y así sucesivamente hasta que encuentre un carácter NULO, que en este caso puede causar acceso a la región de memoria que es ilegal. resultando en una falla de segmentación.

3. Considerar
char * ptr;
ptr = (char *) malloc (sizeof (char) * 10);
ptr ++;
libre (ptr)

Esto podría ocasionar que el montón se corrompa ya que free espera el mismo valor de puntero que malloc devuelto. Por ejemplo, en alguna implementación ptr [-1] almacena el tamaño. La memoria asignada. (en este caso 10, entonces malloc en realidad asignaría una palabra adicional para el tamaño de la memoria asignada) Si ptr ++ se realiza y se intenta liberar, free intentará acceder a ptr [-1] y liberarlo. Tenga en cuenta que ptr [-1] no contiene el tamaño, sino algunos datos de usuario. Por lo tanto, free intentará devolver la memoria “ptr [-1]” al montón, lo que provocará daños en el montón. No se puede predecir cómo la corrupción del montón afecta el comportamiento de su programa. De hecho, el efecto de cualquier corrupción de datos sobre cómo afecta el comportamiento de su programa no se puede predecir. Lo que quiero decir es que con solo mirar el comportamiento del programa, uno no puede adivinar de inmediato que estos datos específicos se están corrompiendo. La depuración de expertos elegidos a través de salidas nocturnas es el único salvador.

4, ni específico para “C” pero para el compilador Microsoft VC ++. Si construye sus dlls con / MT, se asocian múltiples copias de la biblioteca Cruntime con cada dll. Por lo tanto, la asignación de memoria realizada desde una DLL no se puede liberar en otra. Use / MD en su lugar.

5. El error HeartBleed es un caso clásico de buffer over flow. (google para más detalles) En este caso del error, la aplicación no verificó la longitud de los datos enviados. Por ejemplo … cuando
Se utiliza strncpy (dest, src, length)
strncpy verificará si la cadena “src” termina antes de la longitud. Si es así, no accederá a la cadena str después de que llegue al carácter NULL. Por ej.

char * str = “foo”;
char dest [200];
strncpy (dest, str, 100);
No significa que strncpy accederá más allá de str [3] aunque la longitud especificada sea 100. Si lo hiciera, sería un acceso ilegal.

El error de sangrado escuchado hizo algo como lo anterior, donde no validó la fecha de entrada de datos y creyó al proveedor. Un hacker / cracker, por ejemplo, puede suministrar una cadena “foo” y decir que su longitud es 100, y puede obtener acceso a datos seguros.

Desbordamiento de búfer:

Aquí hay un ejemplo simplificado:

int main () {
char correctPassword [] = “hola”;
contraseña de char [10];
contraseña [0] = 0; // borra el búfer de contraseña
obtiene (contraseña); // punto de vulnerabilidad
if (strncmp (contraseña, contraseña correcta, 10) == 0) {
printf (“Ha iniciado sesión correctamente”);
} más {
printf (“acceso denegado”);
}
}

El programa es correcto en lógica. Hay una contraseña codificada (esto expone a un tipo diferente de vulnerabilidad) pero el problema está más allá de eso.

Al darle a este programa una entrada de más de 10 caracteres con una cadena diseñada, puedo hacer que el programa crea que tengo la contraseña correcta. Esto sucederá si el compilador coloca la cadena “correctPassword” cerca de “contraseña”. Al proporcionar una cadena grande, los caracteres más allá del límite de la contraseña se extenderán a la siguiente memoria disponible:

Ejemplo:

En mi humilde opinión, hablar de problemas de seguridad en C es como hablar de seguridad VIP para un viajero de rickshaw.
No hay capas de abstracción entre el sistema operativo de su computadora y el ejecutable en C. Por lo tanto, realmente no tiene sentido hablar de “seguridad” en el contexto de C.
C es “en bruto” y le da la “velocidad en bruto”. ¡Nada mas!
Por cierto, soy un gran admirador de C (¡en caso de que tengas la idea equivocada de que odio C!) Y también he publicado un libro de texto sobre C. 🙂

La única deficiencia de C es la falta de programadores capaces de tener la disciplina para usarlo. Eso puede sonar mal, pero realmente se trata de cuánto entiendes sobre las computadoras en general (qué es la memoria, qué es una interrupción, etc.) junto con cuánto tiempo programas en C que determina qué tan bueno serás con el lenguaje.

C fue escrito a propósito para permitir que el programador tenga la máxima flexibilidad, incluso realizando acciones que son absolutamente cuestionables en todos los sentidos. Los creadores de C sintieron que un programador debería tener la capacidad de hacer exactamente lo que él quería, después de todo, era responsabilidad del programador hacerlo de manera segura. Si hubieran incorporado muchos controles y muros para evitar que los programadores hagan cosas extrañas, no tendríamos formas de hacer cosas como calcular la raíz cuadrada inversa extremadamente rápido:

float Q_rsqrt (número flotante)
{
largo i;
flotador x2, y;
const float threehalfs = 1.5F;

x2 = número * 0.5F;
y = número;
i = * (largo *) & y; // piratería de nivel de bits de coma flotante malvada
i = 0x5f3759df – (i >> 1); // que carajo?
y = * (flotante *) & i;
y = y * (tres medias – (x2 * y * y)); // primera iteración
// y = y * (tres medias – (x2 * y * y)); // 2da iteración, esto se puede eliminar

volver y;
}

Raíz cuadrada inversa rápida – Wikipedia

Esto no es una deficiencia de C sino una ventaja.

No hay problemas de seguridad con un lenguaje como C. Es el codificador que crea problemas de seguridad. C es un lenguaje para alguien que entiende cómo funciona el sistema operativo y no para principiantes. Le da un control total de lo que desea hacer con la máquina y proporciona un rendimiento absoluto. Es como un palo de fósforo. ¡Puedes crear fuego para encender una vela o quemar una casa! Depende del programador. Match-stick no tiene la culpa.

#CIstheBestLanguage

No hay “deficiencias” en C, está diseñado de cierta manera para ser flexible y muy cercano al hardware. Es el programador el que decide qué y cómo escribir.

En nuestra empresa, tenemos un subconjunto “seguro” de C que se utilizará para el software incorporado (por ejemplo, una de esas reglas es no usar el montón), sin embargo, independientemente de esa regla, puede hacer algunas cosas bastante jodidas si es un programador de mierda. independientemente de si es C o algún otro lenguaje.

No es una deficiencia, sino una fortaleza en C que conduce a la mayoría de los problemas de seguridad en el código C: punteros.

Esto es asombroso
Rompiendo la pila por diversión y ganancias por Aleph One

esto también podría mostrar algunos puntos más

Programación segura en C (página en sans.org)