seguridad - Escribir Secure C y Secure C Idioms
heap overflow (7)
"El hombre promedio no quiere ser libre. Simplemente quiere estar seguro". - HL Menken
Intento escribir C. Muy seguro. A continuación enumero algunas de las técnicas que uso y pregunto si son tan seguras como creo que son. Por favor, no dude en romper mi código / preconcepciones en fragmentos. Cualquier respuesta que encuentre incluso la vulnerabilidad más trivial o que me enseñe una idea nueva será muy valorada .
Lectura de una secuencia:
De acuerdo con GNU C Programming Tutorial getline:
La función getline automáticamente ampliará el bloque de memoria según sea necesario, a través de la función realloc, por lo que nunca hay escasez de espacio, una razón por la cual getline es tan seguro. [..] Tenga en cuenta que getline puede manejar su línea de entrada de forma segura, sin importar cuánto tiempo sea.
Supongo que Getline debería, bajo todas las entradas , evitar que se produzca un desbordamiento de búfer al leer de una secuencia.
- ¿Mi suposición es correcta? ¿Existen insumos y / o esquemas de asignación bajo los cuales esto podría conducir a un exploit? Por ejemplo, ¿qué pasa si el primer personaje de la secuencia es un personaje de control extraño , tal vez 0x08 RETROCESO (ctl-H).
- ¿Se ha hecho algún trabajo para demostrar matemáticamente getline como seguro?
Malloc devuelve nulo en caso de error:
Si malloc encuentra un error, malloc devuelve un puntero NULL. Esto presenta un riesgo de seguridad ya que todavía se puede aplicar la aritmética del puntero a un puntero NULL (0x0), por lo que recommends Wikipedia.
/* Allocate space for an array with ten elements of type int. */
int *ptr = (int*)malloc(10 * sizeof (int));
if (ptr == NULL) {
/* Memory could not be allocated, the program should handle
the error here as appropriate. */
}
Secure sscanf:
Cuando uso sscanf , tengo el hábito de asignar cadenas de tamaño a ser extraídas al tamaño de la cadena de entrada con la esperanza de evitar la posibilidad de un rebasamiento. Por ejemplo:
const char *inputStr = "a01234b4567c";
const char *formatStr = "a%[0-9]b%[0-9]c":
char *str1[strlen(inputStr)];
char *str2[strlen(inputStr)];
sscanf(inputStr, formatStr, str1, str2);
Como str1 y str2 son del tamaño de inputStr y no se pueden leer más caracteres que strlen (inputStr) desde inputStr, parece imposible, dados todos los valores posibles para que inputStr cause un desbordamiento de búfer?
- ¿Estoy en lo correcto? ¿Hay casos extraños en las esquinas que no he pensado?
- ¿Hay mejores formas de escribir esto? ¿Bibliotecas que ya lo han resuelto?
Preguntas generales:
Si bien publiqué una gran cantidad de preguntas, no espero que nadie las responda todas. Las preguntas son más de orientación para el tipo de respuestas que estoy buscando. Realmente quiero aprender la mentalidad C segura.
- ¿Qué otras expresiones de seguridad C están disponibles?
- ¿Qué casos de esquina necesito verificar siempre ?
- ¿Cómo puedo escribir pruebas unitarias para hacer cumplir estas reglas?
- ¿Cómo puedo hacer cumplir las restricciones de forma comprobable o de manera probablemente correcta?
- ¿Alguna técnica o herramienta de análisis estática / dinámica recomendada para C?
- ¿Qué prácticas seguras de C sigues y cómo las justificas para ti y para los demás?
Recursos:
Muchos de los recursos fueron tomados de las respuestas.
- Programación segura para Linux y Unix HOWTO por David Wheeler
- Programación Secure C - SUN Microsystems
- Programación insegura por ejemplo
- Agregue más NOPS - blog que cubre estos problemas
- Iniciativa de codificación segura CERT
- flawfinder - herramienta de análisis estático
- Usando Thm Provers para probar la seguridad de Yannick Moy
- libsafe
- Lectura de una secuencia
El hecho de que getline()
" getline()
automáticamente el bloque de memoria según sea necesario" significa que podría utilizarse como un ataque de denegación de servicio, ya que sería trivial generar una entrada que fuera tan larga que agotaría el acceso disponible. memoria para el proceso (o peor, el sistema!). Una vez que ocurre una condición de falta de memoria, otras vulnerabilidades también pueden entrar en juego. El comportamiento del código en baja / sin memoria rara vez es bueno y muy difícil de predecir. En mi humilde opinión, es más seguro establecer límites superiores razonables para todo, especialmente en aplicaciones sensibles a la seguridad.
Además (como anticipas al mencionar caracteres especiales), getline()
solo te da un buffer; no garantiza el contenido del búfer (ya que la seguridad depende totalmente de la aplicación). Por lo tanto, desinfectar la entrada sigue siendo una parte esencial del procesamiento y la validación de los datos del usuario.
- sscanf
Tiendo a preferir utilizar una biblioteca de expresiones regulares y tener expresiones regulares definidas muy estrechamente para los datos del usuario, en lugar de utilizar sscanf
. De esta manera puede realizar una buena validación en el momento de la entrada.
Comentarios generales
- Hay herramientas de borrado disponibles que generan entradas aleatorias (tanto válidas como no válidas) que se pueden usar para probar su manejo de entrada
- La gestión de búfer es crítica: desbordamientos de búfer, subdesbordamientos, memoria insuficiente
- Las condiciones de carrera pueden explotarse en un código seguro
- Los archivos binarios se pueden manipular para inyectar valores no válidos o valores sobredimensionados en los encabezados, por lo que el código de formato de archivo debe ser sólido como una roca y no suponer que los datos binarios son válidos.
- Los archivos temporales a menudo pueden ser una fuente de problemas de seguridad y deben manejarse con cuidado
- La inyección de código se puede usar para reemplazar las llamadas a la biblioteca del sistema o del tiempo de ejecución con versiones maliciosas
- Los complementos proporcionan un gran vector para el ataque
- Como principio general, sugiero tener interfaces claramente definidas donde los datos del usuario (o cualquier información desde fuera de la aplicación) se asumen inválidos y hostiles hasta que sean procesados, desinfectados y validados, y la única forma de que los datos de usuario ingresen a la aplicación
Creo que tu ejemplo de sscanf es incorrecto. Todavía puede desbordarse cuando se usa de esa manera.
Pruebe esto, que especifica el número máximo de bytes para leer:
void main(int argc, char **argv)
{
char buf[256];
sscanf(argv[0], "%255s", &buf);
}
Eche un vistazo a este artículo de IBM Dev sobre protección contra desbordamientos de búfer.
En términos de prueba, escribiría un programa que genere cadenas aleatorias de longitud aleatoria y las alimente a su programa, y se asegure de que se manejen adecuadamente.
Gday,
Un buen lugar para comenzar a mirar esto es el excelente sitio seguro de codificación de David Wheeler .
Su libro en línea gratuito " Programación segura para Linux y HOWTO de Unix " es un excelente recurso que se actualiza periódicamente.
También es posible que flawfinder ver su excelente analizador estático flawfinder para obtener más pistas. Pero recuerde, ninguna herramienta automatizada es un reemplazo para un buen par de ojos con experiencia, o como David lo pone tan colorido.
Cualquier herramienta de análisis estático, como Flawfinder, es simplemente una herramienta. ¡Ninguna herramienta puede sustituir al pensamiento humano! En resumen, "un tonto con una herramienta sigue siendo un tonto" . Es un error pensar que las herramientas de análisis (como flawfinder) son un sustituto de la capacitación y el conocimiento de seguridad
Personalmente he usado los recursos de David durante varios años y los considero excelentes.
HTH
aclamaciones,
No use gets()
para ingresar, use fgets()
. Para usar fgets()
, si su buffer se asigna automáticamente (es decir, "en la pila"), entonces use este modismo:
char buf[N];
...
if (fgets(buf, sizeof buf, fp) != NULL)
Esto seguirá funcionando si decides cambiar el tamaño de buf
. Prefiero este formulario a:
#define N whatever
char buf[N];
if (fgets(buf, N, fp) != NULL)
porque la primera forma usa buf
para determinar el segundo argumento, y es más clara.
También puede consultar el sitio web de Les Hatton here y su libro Safer C, que puede obtener de Amazon.
Yannick Moy desarrolló un sistema de precondición más débil de Hoare / Floyd para C durante su doctorado y lo aplicó a la biblioteca de cuerdas administradas CERT . Encontró varios errores (vea la página 197 de sus memorias). La buena noticia es que la biblioteca ahora es más segura para su trabajo.