resueltos programacion principiantes practicar para lenguaje funciones ejercicios ejemplos dev basicos c sandbox

programacion - lenguaje c



¿Cómo crear un sandbox de código C liviano? (13)

Me gustaría construir un preprocesador / compilador C que permita recopilar funciones de fuentes locales y en línea. es decir:

#fetch MP3FileBuilder http://scripts.com/MP3Builder.gz #fetch IpodDeviceReader http://apple.com/modules/MP3Builder.gz void mymodule_main() { MP3FileBuilder(&some_data); }

Esa es la parte fácil.

La parte difícil es que necesito una forma confiable de "proteger" el código importado del acceso directo o sin restricciones a los recursos del disco o del sistema (incluida la asignación de memoria y la pila) . Quiero una forma de ejecutar de forma segura pequeños fragmentos de código C (módulos) que no sean de confianza sin la sobrecarga de ponerlos en un proceso, máquina virtual o intérprete separados (sin embargo, un hilo separado sería aceptable).

REQUISITOS

  • Tendría que poner cuotas en su acceso a datos y recursos, incluido el tiempo de CPU.
  • Bloquearé el acceso directo a las bibliotecas estándar
  • Quiero detener código malicioso que crea recursión sin fin
  • Quiero limitar la asignación estática y dinámica a límites específicos
  • Quiero capturar todas las excepciones que el módulo pueda generar (como dividir por 0).
  • Los módulos solo pueden interactuar con otros módulos a través de interfaces centrales
  • Los módulos solo pueden interactuar con el sistema (E / S, etc.) a través de interfaces centrales
  • Los módulos deben permitir operaciones de bits, matemáticas, matrices, enumeraciones, bucles y ramificaciones.
  • Los módulos no pueden usar ASM
  • Quiero limitar el acceso del puntero y la matriz a la memoria reservada para el módulo (a través de un safe_malloc personalizado ())
  • Debe ser compatible con ANSI C o un subconjunto (ver a continuación)
  • El sistema debe ser ligero y multiplataforma (incluidos los sistemas integrados).
  • El sistema debe ser compatible con GPL o LGPL.

Me complace conformarme con un subconjunto de C. No necesito cosas como plantillas o clases. Me interesan principalmente las cosas que a los lenguajes de alto nivel no les va bien, como las matemáticas rápidas, las operaciones de bits y la búsqueda y el procesamiento de datos binarios.

No es la intención que el código C existente se pueda reutilizar sin modificación para crear un módulo. La intención es que los módulos se ajusten a un conjunto de reglas y limitaciones diseñadas para limitar el módulo a la lógica básica y las operaciones de transformación (como una transcodificación de video o operaciones de compresión, por ejemplo).

La entrada teórica a tal compilador / preprocesador sería un solo archivo ANSI C (o subconjunto seguro) con una función module_main, NO incluye o directivas de preprocesador, no ASM, permitiría bucles, bifurcaciones, llamadas a funciones, puntero Matemáticas (restringidas a un rango asignado al módulo), cambio de bit, bitfields, cast, enums, arrays, ints, floats, strings y maths. Cualquier otra cosa es opcional.

EJEMPLO DE IMPLEMENTACIÓN

Aquí hay un fragmento de código de pseudo-código para explicar esto mejor. Aquí un módulo excede su cuota de asignación de memoria y también crea una recursión infinita.

buffer* transcodeToAVI_main( &in_buffer ) { int buffer[1000000000]; // allocation exceeding quota while(true) {} // infinite loop return buffer; }

Aquí hay una versión transformada donde nuestro preprocesador ha agregado puntos de control para verificar el uso de la memoria y la recursión, y envuelve todo en un manejador de excepciones.

buffer* transcodeToAVI_main( &in_buffer ) { try { core_funcStart(__FILE__,__FUNC__); // tell core we''re executing this function buffer = core_newArray(1000000000, __FILE__, __FUNC__); // memory allocation from quota while(true) { core_checkLoop(__FILE__, __FUNC__, __LINE__) && break; // break loop on recursion limit } core_moduleEnd(__FILE__,__FUNC__); } catch { core_exceptionHandler(__FILE__, __FUNC__); } return buffer; }

Me doy cuenta de que la realización de estas comprobaciones afecta el rendimiento del módulo, pero sospecho que aún superará a los lenguajes VM o de alto nivel para las tareas que se pretende resolver. No estoy tratando de evitar que los módulos hagan cosas peligrosas directamente, solo estoy tratando de forzar esas cosas peligrosas a suceder de una manera controlada (como a través de los comentarios de los usuarios). es decir: "¿El módulo X ha excedido su asignación de memoria, continúa o aborta?".

ACTUALIZAR

Lo mejor que tengo hasta ahora es usar un compilador personalizado (como un TCC pirateado) con comprobación de límites y alguna función personalizada y código de bucle para captar las recursiones. Todavía me gustaría saber qué más debo verificar o qué soluciones hay disponibles. Me imagino que eliminar ASM y consultar los indicadores antes de usar resuelve muchas de las preocupaciones expresadas en las respuestas anteriores a continuación. Agregué una recompensa para obtener más retroalimentación de la comunidad SO.

Por la recompensa que estoy buscando:

  • Detalles de exploits potenciales contra el sistema teórico definido anteriormente
  • Posibles optimizaciones sobre la verificación de punteros en cada acceso
  • Implementaciones experimentales de código abierto de los conceptos (como Google Native Client)
  • Soluciones que admiten una amplia gama de sistemas operativos y dispositivos (sin soluciones basadas en SO / hardware)
  • Soluciones que admiten la mayoría de las operaciones C, o incluso C ++ (si eso es posible)

Crédito adicional por un método que puede funcionar con GCC (es decir, un preprocesador o parche pequeño de GCC).

También daré consideración a cualquier persona que pueda demostrar de manera concluyente que lo que estoy intentando no se puede hacer en absoluto. Sin embargo, tendrá que ser bastante convincente porque ninguna de las objeciones hasta ahora ha esclarecido realmente los aspectos técnicos de por qué creen que es imposible. En defensa de aquellos que dijeron que no, esta pregunta se planteó originalmente como una forma de ejecutar C ++ de manera segura. Ahora reduje el requisito a un subconjunto limitado de C.

Mi comprensión de C podría clasificarse como "intermedia", mi comprensión del hardware para PC es tal vez un paso por debajo de "avanzado". Intenta entrenar tus respuestas para ese nivel si puedes. Como no soy un experto en C, iré en gran medida en función de los votos que se den a una respuesta y de cuán cercana sea la respuesta a mis requerimientos. Puede ayudar proporcionando evidencia suficiente para sus reclamos (encuestados) y votando (a todos los demás). Asignaré una respuesta una vez que la cuenta atrás del botín alcance 6 horas.

Finalmente, creo que resolver este problema sería un gran paso para mantener la relevancia de C en un mundo cada vez más enredado y enredado. A medida que otros idiomas cierren la brecha en términos de rendimiento y aumente el poder de computación, será cada vez más difícil justificar el riesgo adicional de desarrollo de C (como lo es ahora con ASM). Creo que sus respuestas tendrán una relevancia mucho mayor que la puntuación de algunos puntos SO, así que contribuya con lo que pueda, incluso si la recompensa ha expirado.


8 años después y descubrí una nueva plataforma que cumple con todos mis requisitos originales. Web Assembly le permite ejecutar un subconjunto C / C ++ de forma segura dentro de un navegador y viene con restricciones de seguridad similares a mis requisitos, como restringir el acceso a la memoria y evitar operaciones inseguras en el sistema operativo y el proceso principal. Se implementó en Firefox 52 y hay señales prometedoras de que otros navegadores lo admitirán en el futuro.


Buena idea, pero estoy bastante seguro de que lo que estás tratando de hacer es imposible con C o C ++. Si eliminaste la idea de la caja de arena, podría funcionar.

Java ya tiene un sistema similar (como en una gran biblioteca de código de terceros) en Maven2


Como el estándar C es demasiado amplio para permitirlo, necesitaría ir al revés: especifique el subconjunto mínimo de C que necesita e intente implementarlo. Incluso ANSI C ya es demasiado complicado y permite un comportamiento no deseado.

El aspecto de C que es más problemático son los indicadores: el lenguaje C requiere un puntero aritmítico y no se controlan. Por ejemplo:

char a[100]; printf("%p %p/n", a[10], 10[a]);

ambos imprimirán la misma dirección. Desde a[10] == 10[a] == *(10 + a) == *(a + 10) .

Todos estos accesos al puntero no se pueden verificar en tiempo de compilación. Esa es la misma complejidad que pedir al compilador ''todos los errores en un programa'' que requeriría resolver el problema de detención.

Dado que desea que esta función pueda ejecutarse en el mismo proceso (potencialmente en un hilo diferente), usted comparte memoria entre su aplicación y el módulo ''seguro'' ya que ese es el punto de tener un hilo: compartir datos para un acceso más rápido. Sin embargo, esto también significa que ambos hilos pueden leer y escribir la misma memoria.

Y como no puede probar el tiempo de compilación en el que terminan los punteros, debe hacerlo en tiempo de ejecución. Lo que significa que el código como ''a [10]'' debe traducirse a algo como ''get_byte (a + 10)'', en cuyo punto ya no lo llamaría C.

Google Native Client

Entonces, si eso es cierto, ¿cómo lo hace Google entonces? Bueno, a diferencia de los requisitos aquí (multiplataforma (incluidos los sistemas integrados)), Google se concentra en x86, que además de paginar con protecciones de página también segmenta los registros. Lo que le permite crear un entorno limitado donde otro subproceso no comparte la misma memoria de la misma manera: el entorno limitado es por segmentación limitada a cambiar solo su propio rango de memoria. Además:

  • se ensambla una lista de construcciones de ensamblaje x86 seguras
  • gcc se cambia para emitir esos constructos seguros
  • esta lista está construida de una manera que es verificable.
  • después de cargar un módulo, esta verificación se hace

Así que esto es específico de la plataforma y no es una solución ''simple'', aunque es una solución funcional. Lea más en su trabajo de investigación .

Conclusión

Entonces, sea cual sea la ruta que elija, debe comenzar con algo nuevo que sea verificable y solo entonces puede comenzar por adaptar un compilador existente o generar uno nuevo. Sin embargo, tratar de imitar a ANSI C requiere que uno piense en el problema del puntero. Google modeló su arenero no en ANSI C sino en un subconjunto de x86, lo que les permitió usar los compiladores existentes en gran medida con la desventaja de estar vinculado a x86.


Creo que obtendríamos muchas lecturas sobre algunas de las preocupaciones y opciones de implementación que Google hizo al diseñar Native Client , un sistema para ejecutar código x86 (de manera segura, esperamos) en el navegador. Es posible que necesite hacer una compilación de reescritura de fuente o fuente a fuente para hacer que el código sea seguro si no es así, pero debería poder confiar en el entorno limitado de NaCL para captar el código ensamblador generado si intenta hacer algo demasiado funky. .


Es imposible crear un verificador de código estático que pueda determinar, para todos los códigos posibles, que un conjunto de códigos es seguro o inseguro, si el lenguaje está completo. Es equivalente al problema de detención.

Por supuesto, este punto es irrelevante si tiene un código de supervisor ejecutándose en un nivel de timbre inferior o si es un lenguaje interpretado (es decir, emula los recursos de la máquina).

La mejor forma de hacerlo es iniciar el código en otro proceso (ipc no es tan malo) y atrapar llamadas al sistema como Ptrace en linuxes http://linux.die.net/man/2/ptrace


Esto no es trivial, pero no es tan difícil.

Puede ejecutar código binario en un cuadro de arena. Cada sistema operativo hace esto todo el día.

Tendrán que usar su biblioteca estándar (frente a una biblioteca C genérica). Su biblioteca estándar aplicará los controles que quiera imponer.

A continuación, querrá asegurarse de que no puedan crear un "código ejecutable" en tiempo de ejecución. Es decir, la pila no es ejecutable, no pueden asignar ninguna memoria que sea ejecutable, etc. Eso significa que solo el código generado por el compilador (SU compilador) será ejecutable.

Si su compilador firma su ejecutable criptográficamente, su tiempo de ejecución podrá detectar binarios alterados y simplemente no cargarlos. Esto les impide "meter" cosas en los binarios que simplemente no quieres que tengan.

Con un compilador controlado que genere código "seguro" y una biblioteca de sistema controlada, eso debería proporcionar un entorno limitado razonablemente controlado, incluso con el código de lenguaje de máquina real.

¿Quieres imponer límites de memoria? Pon un cheque en malloc. ¿Desea restringir cuánta pila se asigna? Limite el segmento de pila.

Los sistemas operativos crean este tipo de entornos restringidos utilizando sus administradores de memoria virtual durante todo el día, por lo que puede hacer estas cosas fácilmente en sistemas operativos modernos.

Si el esfuerzo para hacer esto vale la pena versus el uso de una máquina virtual y el tiempo de ejecución del código de bytes, no puedo decirlo.


Liran señaló codepad.org en un comentario anterior. No es adecuado porque depende de un entorno muy pesado (que consiste en ptrace, chroot y un cortafuegos de salida). Sin embargo, encontré algunos interruptores de seguridad de g ++ que pensé que compartiría aquí:

gcc 4.1.2 flags: -O -fmessage-length = 0 -fno-merge-constants -fstrict-aliasing -fstack-protector-all

g ++ 4.1.2 flags: -O -std = c ++ 98 -pedantic-errors -Wfatal-errors -Werror -Wall -Wextra -Wno-missing-field-initializers -Wwrite-strings -Wno-deprecated -Wno-unused - Wno-non-virtual-dtor -Wno-variadic-macros -fmessage-length = 0 -ftemplate-depth-128 -fno-merge-constants -fno-nonansi-builtins -fno-gnu-keywords -fno-elide-constructors - fstrict-aliasing -fstack-protector-all -Winvalid-pch

Las opciones se explican en el manual de GCC

Lo que realmente me llamó la atención fue la bandera protectora de la pila. Creo que es una fusión de este proyecto de investigación de IBM ( Stack-Smashing Protector ) con el GCC oficial.

La protección se realiza mediante la detección de desbordamiento del búfer y la función de reordenación variable para evitar la corrupción de los punteros. La idea básica de la detección de desbordamiento de búfer proviene del sistema StackGuard .

Las características novedosas son (1) el reordenamiento de variables locales para colocar búferes después de punteros para evitar la corrupción de punteros que podrían usarse para corromper aún más las ubicaciones de memoria arbitrarias, (2) la copia de punteros en argumentos de funciones a un área anterior a la variable local búferes para evitar la corrupción de punteros que podrían usarse para corromper aún más las ubicaciones de memoria arbitrarias, y la (3) omisión del código de instrumentación de algunas funciones para disminuir la sobrecarga del rendimiento.


Me encontré con Tiny C Compiler (TCC) . Esto puede ser lo que necesito:

* SMALL! You can compile and execute C code everywhere, for example on rescue disks (about 100KB for x86 TCC executable, including C preprocessor, C compiler, assembler and linker). * FAST! tcc generates x86 code. No byte code overhead. Compile, assemble and link several times faster than GCC. * UNLIMITED! Any C dynamic library can be used directly. TCC is heading torward full ISOC99 compliance. TCC can of course compile itself. * SAFE! tcc includes an optional memory and bound checker. Bound checked code can be mixed freely with standard code. * Compile and execute C source directly. No linking or assembly necessary. Full C preprocessor and GNU-like assembler included. * C script supported : just add ''#!/usr/local/bin/tcc -run'' at the first line of your C source, and execute it directly from the command line. * With libtcc, you can use TCC as a backend for dynamic code generation.

Es un programa muy pequeño que hace que piratearlo sea una opción viable (¿hackear GCC ?, ¡no en esta vida!). Sospecho que será una base excelente para construir mi propio compilador restringido. Eliminaré la compatibilidad con funciones de idioma que no puedo proteger y ajustar o reemplazar la asignación de memoria y el manejo de bucles.

TCC ya puede hacer límites verificando los accesos a la memoria , que es uno de mis requisitos.

libtcc es también una gran característica, ya que puedo administrar la compilación de código internamente.

No espero que sea fácil, pero me da esperanza de que pueda obtener un rendimiento cercano a C con menos riesgos.

Todavía quiero escuchar otras ideas sin embargo.



Parece que estás tratando de resolver dos no problemas. En mi propio código no tengo problemas de asignación de memoria o problemas con recursividad o bucles infinitos.

Lo que parece proponer es un lenguaje diferente y más limitado que C ++. Esto es algo que puedes seguir, por supuesto, pero como otros han notado, tendrás que escribir un compilador: el simple procesamiento textual no te dará lo que deseas.


Perfectamente imposible. El lenguaje simplemente no funciona de esta manera. El concepto de clases se pierde muy temprano en la mayoría de los compiladores, incluido GCC. Incluso si lo fuera, no habría forma de asociar cada asignación de memoria con un objeto activo, y mucho menos un "módulo".


Si fuera a hacer esto, investigaría uno de dos enfoques:

  • Use el CINT de CERN para ejecutar un código de espacio aislado en un intérprete y vea cómo restringir lo que el intérprete permite. Esto probablemente no daría un rendimiento terriblemente bueno.
  • Use LLVM para crear una representación intermedia del código C ++ y luego vea si es factible ejecutar ese bytecode en una máquina virtual de tipo Java con espacio aislado.

Sin embargo, estoy de acuerdo con otros en que este es probablemente un proyecto terriblemente complicado. Observe los problemas que han tenido los navegadores web con complementos defectuosos o bloqueados que desestabilizan todo el navegador. O mira las notas de la versión para el proyecto Wireshark ; parece que casi todas las versiones contienen correcciones de seguridad para problemas en uno de sus disectores de protocolo que luego afectan a todo el programa. Si un arenero C / C ++ fuera factible, esperaría que estos proyectos se hayan aferrado a uno por ahora.


Si quiere estar realmente seguro, creo que la mejor y quizás la única forma de hacerlo es ir por la línea de procesos separados y dejar que el O / S maneje el control de acceso. No es tan doloroso escribir un cargador roscado genérico y una vez que lo tiene, puede anular algunas funciones para cargar bibliotecas específicas.