¿Cuál fue el error de progtwigción más peligroso que has cometido en C?

Soy un progtwigdor de C intermedio. Si ha cometido algún error de encoding que llegó a saber más tarde que era el más peligroso / perjudicial para la aplicación total, comparta ese código o descripción. Quiero saber esto porque en el futuro puedo encontrar tales situaciones y quiero tener su consejo para evitar tales errores.

Hace unos años, recibí una llamada de mi ex colega que me contaba el problema que tenía que solucionar con mi código, que era un enrutador para transacciones con tarjeta de crédito.

El prefijo del número de la tarjeta consiste en un BIN (Número de Identificación del Banco) de 6 dígitos y unos pocos dígitos adicionales que los bancos usan a su propia discreción, por ejemplo, el banco tiene un BIN para la tarjeta Visa Classic 456789, y reserva 2 dígitos adicionales para indicar el subproducto, como 01 para el estudiante tarjeta, 02 para tarjeta de marca compartida con grandes almacenes locales y así sucesivamente. En este caso, el prefijo de la tarjeta, que es básicamente el identificador del producto, tiene una longitud de 8 dígitos. Cuando codifiqué esta parte, decidí que 9 dígitos “deberían ser suficientes para todos”. Corrí bien durante 2 años, hasta que un día el banco hizo una nueva tarjeta con prefijo de 10 dígitos (no tengo idea de por qué lo necesitaban). No es demasiado difícil imaginar lo que ha sucedido: el enrutador se ha bloqueado, todo el sistema se detuvo porque no puede funcionar sin el enrutador de la transacción, todos los cajeros automáticos de ese banco (uno de los más grandes del país) dejaron de funcionar durante unas pocas horas, hasta que se encontró el problema y fijo.

No puedo publicar el código aquí en primer lugar porque no lo tengo y en segundo lugar está protegido por los derechos de autor de la compañía, pero no es difícil imaginar el strcpy() sin verificar el tamaño del búfer objective.

Al igual que el man strcpy dice:

Si la cadena de destino de un strcpy () no es lo suficientemente grande (es decir, si el progtwigdor fue estúpido o perezoso y no pudo verificar el tamaño antes de copiar), entonces puede ocurrir cualquier cosa. El desbordamiento de cadenas de longitud fija es una técnica favorita de cracker.

Estaba muy avergonzado. Fue un buen momento para cometer seppuku 🙂

Pero aprendí bien la lección y no me olvido (normalmente 🙂 de verificar el tamaño del búfer de destino. No te recomendaría que aprendieras de la manera más difícil, simplemente desarrolla un hábito para comprobar el búfer de destino antes de strcpy() y strcat() .

Edición: buena sugerencia de Healthcarel: use strncpy() lugar de strcpy() . No agrega 0 al final, pero normalmente uso la siguiente macro para evitarlo:

#define STRNCPY(A,B,C) do {strncpy(A,B,C); A[C] = 0; } while (0)

 if (c = 1) // insert code here 
 if(a == true); { //Do sth when it is true. But it is allways executed. } 

Edición : Otra variante del mismo error.

 for(i=0; i 

Ha pasado mucho tiempo, pero algunas cosas que nunca olvidas ;-).

  • olvide el \0 al final de una cadena.
  • asignar n caracteres para una cadena con n caracteres.
  • olvidando la ruptura en una sentencia de cambio.
  • Uso de la macro ‘creativa’.
 for(int i = 0; i<10; ++i) //code here //code added later 

Tenga en cuenta que el último código agregado no está en el bucle for.

Datos sin inicializar.

Lo más peligroso que hice en C fue escribir un código que gestionara mi propia memoria. Efectivamente, esto significa que la cosa más peligrosa que hice en C fue escribir el código C. (Escuché que puedes evitarlo en estos días. Cadera, cadera y cordura. ¡Usa esos métodos cuando sea apropiado!)

  • No escribo algoritmos de paginación, los geeks del sistema operativo hacen eso por mí.
  • No escribo esquemas de almacenamiento en caché de la base de datos; los geeks de la base de datos lo hacen por mí.
  • No construyo cachés de procesador L2, los geeks de hardware hacen eso por mí.

Y no manejo la memoria.

Alguien más administra mi memoria por mí, alguien que puede diseñar mejor que yo, y probar mejor que yo, y codificar mejor que yo, y parchear cuando cometen errores críticos que comprometen la seguridad, que solo se notan 10 años más tarde porque Absolutamente todos los que intentan asignar memoria fallan algunas veces.

system () con alguna cadena proporcionada por el usuario en el argumento. Lo mismo ocurre con popen ().

Utilice exec * () en su lugar.

Por supuesto, esto no es exclusivo de C.

Deberías preocuparte más por pequeños errores. Los errores grandes / espectaculares generalmente se documentan en los libros (con razones por las que son malos, enfoques alternativos, etc.).

Son los pequeños errores de diseño / encoding que te llegan, porque tienden a acumularse.

Así que mi consejo es que intente leer los libros que Kernighan escribió o coautor (“El lenguaje de progtwigción C”, “Práctica de la progtwigción”, etc.) porque están llenos de consejos y listas de sentido común (común para progtwigdores de C experimentados) Principios que son muy útiles para evitar tanto errores pequeños como grandes.

También enumeran muchos errores potenciales, por lo que responden a su pregunta inicial.

Tomo la definición de peligroso como “podemos enviar ese error y descubrir solo años más tarde cuando sea demasiado tarde”:

 char* c = malloc(...); . . . free(c); . . . c[...] = ...; 

o

 // char* s is an input string char* c = malloc(strlen(s)); strcpy(c, s); 

Pero si escribe multiplataforma (no limitado a x86 / x64), esto también es genial:

 char* c = ...; int i = *((int*)c); // <-- alignment fault 

Y si su búfer proviene de una fuente no confiable, básicamente la mayoría del código es peligroso.

Pero, de todos modos, en C es tan fácil dispararte a ti mismo en el pie, que un tema sobre pies de tiro podría ir alrededor de las miles de páginas.

Estoy de acuerdo con Pat Mac aquí (a pesar de su voto negativo). Lo más peligroso que puedes hacer en C es simplemente usarlo para algo importante.

Por ejemplo, un lenguaje razonable verificará por defecto los límites de la matriz y detendrá su progtwig inmediatamente (provocará una excepción o algo así) si trata de divagar fuera de él. Ada hace esto. Java hace esto. Toneladas de otros idiomas hacen esto. No C. Hay industrias enteras de piratería construidas alrededor de esta falla en el lenguaje.

Una experiencia personal con esto. Trabajé con una compañía que operaba una red de simuladores de vuelo, unida con hardware de memoria reflexiva (compartida). Tenían un desagradable error de locking que no pudieron rastrear, por lo que nuestros dos mejores ingenieros fueron enviados allí para rastrearlo. Les tomó 2 meses.

Resultó que había un error off-by-one en un bucle C en una de las máquinas. Un lenguaje competente habría detenido las cosas allí, por supuesto, pero C deja que continúe y escriba un dato en la siguiente ubicación más allá del final de la matriz. Esta ubicación de memoria pasó a ser utilizada por otra máquina en la red que la pasó a una tercera máquina, que utilizó el valor (basura) como un índice de matriz. Como este sistema también estaba codificado en C, tampoco le importaba que estuviera indexando fuera de su matriz, y eliminando ubicaciones de memoria semi-aleatorias en su progtwig.

Por lo tanto, debido a la falta de comprobación de los límites de la matriz, ¡un error sencillo de hacer uno por uno provocó fallos aleatorios en una computadora a dos saltos completos de la fuente del error! Costo para la empresa: 4 meses-hombre del tiempo de sus mejores ingenieros, además de lo que gastaron otros ingenieros y personal de soporte, además de que todo el tiempo de inactividad de los simuladores en cuestión no funcionó correctamente.

Cuando se asigna un puntero por primera vez, no tiene un pointee.

El puntero está “sin inicializar”

Una operación de desreferencia en un puntero defectuoso es un error grave en tiempo de ejecución.

Si tiene suerte, la operación de desreferencia se bloqueará o se detendrá inmediatamente (Java se comporta de esta manera).

Si no tiene suerte, la mala referencia del puntero dañará un área aleatoria de la memoria, alterando ligeramente el funcionamiento del progtwig para que salga mal un tiempo indefinido más tarde. Cada puntero debe tener asignado un pointee antes de que pueda admitir operaciones de desreferencia.

Dos cosas vienen a la mente. La primera fue una función en C (MCU) incrustada. Traté de tener algunas restricciones en el valor de un temporizador como una función ingresada. así que escribí

 if(55000 < my_var < 65000) 

Mi ida fue para comprobar así:

 if( (55000 

Pero este es el equivalente o el resultado.

 if( (55000 

Y el resultado final fue que la prueba if era siempre cierta.

El segundo fue un error puntero. (simplemente presentado aquí)

 get_data(BYTE **dataptr) { ubyte* data = malloc(10); ... code ... *dataptr = &data[1]; } main() { BYTE *data get_data(&data); free(data); } 

De este modo, se produce una pérdida de 1 byte de memoria por cada vez get_data() se llama a la función get_data()

 while(a) { // code - where 'a' never reaches 0 :( } 

Usar funciones de cadena no limitadas como strcpy () o strcmp (), en lugar de versiones seguras como strncpy () y strncmp ().

Pasar la dirección virtual al motor DMA fue uno de los peores, no relacionado exactamente con la C, pero supongo que el 99% de las cosas relacionadas con la DMA escritas en C, por lo que es una especie de coincidencia. Este pequeño error llevó a la corrupción de la memoria que me llevó 1,5 meses para encontrar.

cambiar la caja sin interrupción.

Habiendo sido progtwigdor de Lisp, estaba acostumbrado a sangrar llaves de cierre, como en:

 (cond ((eq a foo)(bar ... .... )) ) 

y llevé esto a la progtwigción en C:

 if (a == foo){ bar(...); .... } 

Luego me puse en un proyecto de buen tamaño en C, y otro progtwigdor tuvo que hacer cambios en la vecindad de mi código. Leyó mal mis corchetes de cierre y liberó algo de memoria demasiado pronto. Esto causó un error extremadamente sutil que ocurrió en un momento crítico. Cuando fue encontrado, fue culpado, mal. Pero se podría decir que fue mi culpa. Eso no fue divertido, por decir lo menos.

Olvidando las restricciones de la architecture y felizmente memcpy () en el área de E / S asignada a la memoria en un microcontrolador. El humo mágico fue liberado de la plataforma de prueba.

Estaba tratando con matrices 2D asignadas dinámicamente y en lugar de las filas N libres () ‘n, decidí liberar M columnas. Esto estaba bien para entradas más pequeñas donde N == M pero en entradas grandes, solo libero () el 50% de lo que asigné.

encogimiento de hombros

Vive y aprende.

Este es un famoso ejemplo histórico (no es algo que hice), pero

 double d; // d gets populated with a large number from somewhere short s = d ; // overflow 

Condujo a la explosión y la pérdida total de un cohete Ariane V.

Una cosa que cuidar son los límites de la matriz. Si se sale de los límites, con mala suerte puede terminar sobrescribiendo la memoria que se usa para otros datos.

Un error desagradable relacionado con esto estaba saliendo de los límites para una variable de matriz estática en una función. Eso terminó como una función que cambia los valores de las variables locales de la función de llamada. Eso no fue tan sencillo de depurar …

Recuerdo dos errores:

  1. dirección de retorno de una variable automática desde dentro de una función en la que fue creada;
  2. copiando la cadena a un puntero sin inicializar y sin asignar a char.
 #include  

Me thinking C es compatible con cadenas de forma nativa (utilizando el código de Metroworks, hace unos 8 años).

Hice esto, para un proyecto final de aproximadamente 15,000 líneas de código. Utilicé esta biblioteca para hacer todo lo relacionado con cadenas (adjuntar, dividir, etc.) Sólo para que los TA no puedan recostackr ningún bit de mi asignación (utilizando GCC).

Poco aprendí que metroworks había creado su propia biblioteca de cadenas. Fallé esa clase.

 if (importantvar = importantfunction() == VALID_CODE) 

Esto es cuando quise decir esto:

 if ((important var = importantfunction()) == VALID_CODE) 

Esto llevó a muchas horas de problemas de depuración cuando asumí que funcionaba como el último.

Se olvidó de poner ; al final. Exceso } . Escribe erróneamente una ,

Esto me hace enloquecer por horas para encontrar qué cosas han ido mal con mis códigos.