¿Qué funciones en la biblioteca estándar de C fomentan comúnmente las malas prácticas? [closed]

11 minutos de lectura

Esto está inspirado en esta pregunta y los comentarios sobre una respuesta particular en la que aprendí que strncpy no es una función de manejo de cadenas muy segura en C y que rellena ceros, hasta que alcanza nalgo que desconocía.

En concreto, para citar a R..

strncpy no termina en nulo, y sí rellena en nulo todo el resto del búfer de destino, lo que es una gran pérdida de tiempo. Puede evitar el primero agregando su propio relleno nulo, pero no el segundo. Nunca fue diseñado para usarse como una función de “manejo seguro de cadenas”, sino para trabajar con campos de tamaño fijo en tablas de directorios y archivos de bases de datos de Unix. snprintf(dest, n, “%s”, src) es el único “strcpy seguro” correcto en C estándar, pero es probable que sea mucho más lento. Por cierto, el truncamiento en sí mismo puede ser un error importante y, en algunos casos, puede conducir a la elevación de privilegios o DoS, por lo que lanzar funciones de cadena “seguras” que truncan su salida en un problema no es una forma de hacerlo “seguro” o ” seguro”. En su lugar, debe asegurarse de que el búfer de destino tenga el tamaño correcto y simplemente use strcpy (o mejor aún, memcpy si ya conoce la longitud de la cadena de origen).

Y de Jonathan Leffler

Tenga en cuenta que strncat() es aún más confuso en su interfaz que strncpy() – ¿cuál es exactamente ese argumento de longitud, de nuevo? No es lo que esperaría en función de lo que proporciona strncpy(), etc., por lo que es más propenso a errores incluso que strncpy(). Para copiar cadenas, soy cada vez más de la opinión de que existe un fuerte argumento de que solo necesita memmove() porque siempre conoce todos los tamaños con anticipación y se asegura de que haya suficiente espacio con anticipación. Utilice memmove() en lugar de strcpy(), strcat(), strncpy(), strncat(), memcpy().

Entonces, claramente estoy un poco oxidado en la biblioteca estándar de C. Por lo tanto, me gustaría plantear la pregunta:

¿Qué funciones de la biblioteca estándar de C se usan de manera inapropiada/de manera que pueden causar/conducir a problemas de seguridad/defectos de código/ineficiencias?

En aras de la objetividad, tengo una serie de criterios para una respuesta:

  • Por favor, si puede, cite las razones de diseño detrás de la función en cuestión, es decir, su propósito previsto.
  • Resalte el uso indebido al que se somete actualmente el código.
  • Indique por qué ese mal uso puede conducir a un problema. Sé que debería ser obvio, pero evita respuestas blandas.

Por favor evite:

  • Debates sobre convenciones de nomenclatura de funciones (excepto cuando esto inequívocamente cause confusión).
  • “Prefiero x sobre y”: la preferencia está bien, todos los tenemos, pero estoy interesado en los efectos secundarios inesperados reales y cómo protegerme de ellos.

Como es probable que esto se considere subjetivo y no tenga una respuesta definitiva, estoy marcando la wiki de la comunidad de inmediato.

También estoy trabajando según C99.

  • Cualquier función se puede usar de manera inapropiada y de formas que pueden conducir a brechas de seguridad.

    – Falmarri

    3 de enero de 2011 a las 21:36

  • @Falmarri, pero algunos se usan con frecuencia de manera inapropiada donde otros no, algunos parecen alentar el uso indebido donde otros no lo hacen.

    usuario180247

    3 de enero de 2011 a las 21:41

¿Qué funciones de la biblioteca estándar de C se usan de manera inapropiada/de manera que pueden causar/conducir a problemas de seguridad/defectos de código/ineficiencias?

Voy a ir con lo obvio:

char *gets(char *s);

Con su notable particularidad de que es simplemente imposible utilizarlo adecuadamente.

  • MacOS X en realidad imprime una advertencia de tiempo de ejecución cuando lo usa.

    – una masa

    3 de enero de 2011 a las 21:53

  • gets(): el cero absoluto de la seguridad del software.

    – j_random_hacker

    3 de enero de 2011 a las 21:58

  • Tenga en cuenta que C0x eliminará gets() del estándar. Desafortunadamente, pasarán otros 10-20 años después de que se finalice antes de que se elimine de la mayoría de las implementaciones; la compatibilidad con versiones anteriores con la inseguridad lo dicta.

    –Jonathan Leffler

    3 de enero de 2011 a las 23:23

  • @onemasse: ¿realmente? No me había dado cuenta (pero entonces, no lo uso, ¡ni siquiera en código desechable!). Mucho mejor que avise de eso que de mktemp()que veo periódicamente en algunos de los códigos en los que trabajo.

    –Jonathan Leffler

    3 de enero de 2011 a las 23:24

  • MSVC podría. Su trato con el comité es que apoyarán el nuevo estándar si el comité agrega todos sus horribles *_s funciones “seguras” al estándar para obligar a las implementaciones *nix a contaminarse con él. 😉

    – R.. GitHub DEJA DE AYUDAR A ICE

    4 de enero de 2011 a las 2:45

Una trampa común con el strtok() La función es asumir que la cadena analizada se deja sin cambios, mientras que en realidad reemplaza el carácter separador con '\0'.

También, strtok() se usa al realizar llamadas posteriores, hasta que se tokeniza toda la cadena. Algunas implementaciones de biblioteca almacenan strtok()el estado interno de una variable global, lo que puede inducir algunas sorpresas desagradables, si strtok() se llama desde varios subprocesos al mismo tiempo.

los Estándar de codificación segura CERT C enumera muchas de estas trampas sobre las que preguntó.

  • +1 Por reflejar mis pensamientos sobre strtok() y por mencionar el Estándar de codificación segura CERT C.

    –Jonathan Leffler

    3 de enero de 2011 a las 22:28

  • Técnicamente, es la función de biblioteca en lugar del compilador la que almacena el estado. El gran problema es si aísla un token en su cadena y luego llama a una función que, sin que usted lo sepa, llama a sí misma strtok().

    –Jonathan Leffler

    3 de enero de 2011 a las 22:34

  • strtok es requerido para mantener su estado interno globalmente incluso con subprocesos, al menos en un entorno POSIX donde se especifican subprocesos. Esto se debe a que un programa conforme podría comenzar a analizar en un subproceso y terminar en otro. Por supuesto, MS tiene su propia versión de subprocesos donde pueden especificar el comportamiento diferente (subproceso local) como lo hacen, pero entra en conflicto con POSIX.

    – R.. GitHub DEJA DE AYUDAR A ICE

    4 de enero de 2011 a las 2:47

  • Este es ahora un wiki de la comunidad, lo cual es bueno, pero todavía parece que tengo que aceptar una respuesta, así que acepto esta para el Estándar de codificación segura CERT C, que proporciona montones de información útil.

    usuario257111

    4 de enero de 2011 a las 22:42

  • Estoy desconcertado de por qué nadie ha mencionado strtok_r como siendo (ligeramente) menos confuso en el sentido de que no mantiene el estado global.

    – ablando

    11 de enero de 2015 a las 21:46

En casi todos los casos, atoi() no debe utilizarse (esto también se aplica a atof(), atol() y atoll()).

Esto se debe a que estas funciones no detectan ningún error fuera de rango; el estándar simplemente dice “Si el valor del resultado no se puede representar, el comportamiento no está definido”.. Entonces, la única vez que se pueden usar de manera segura es si puede probar que la entrada ciertamente estará dentro del rango (por ejemplo, si pasa una cadena de longitud 4 o menos a atoi()no puede estar fuera de rango).

En su lugar, utilice uno de los strtol() familia de funciones.

  • +1 por señalar el peligro (principalmente teórico, pero aún así) de atoi y UB.

    – R.. GitHub DEJA DE AYUDAR A ICE

    4 de enero de 2011 a las 3:10

  • Excelente punto. No hay razón para usar ato*.

    – Esteban Canon

    4 de enero de 2011 a las 19:09

  • En realidad, es bastante útil si sabe en qué plataforma se ejecutará su código, lo cual, lo más probable es que sepa. Por ejemplo, MSVC dice El valor de retorno es 0 para atoi y _wtoisi la entrada no se puede convertir a un valor de ese tipo., por lo que es bastante bien-defiend. (Además, este es otro ejemplo en el que “indefinido” y “definido por la implementación” en realidad no son exactamente diferentes; ambos pueden ser definidos por la implementación).

    – usuario541686

    12 de noviembre de 2011 a las 5:09


Extendamos la pregunta a las interfaces en un sentido más amplio.

errno:

¿Técnicamente ni siquiera está claro qué es, una variable, una macro, una llamada de función implícita? En la práctica, en los sistemas modernos, es principalmente una macro que se transforma en una llamada de función para tener un estado de error específico del subproceso. es malvado:

  • porque puede causar una sobrecarga para que la persona que llama acceda al valor, para verificar el “error” (que podría ser solo un evento excepcional)
  • porque incluso impone en algunos lugares que la persona que llama borre esta “variable” antes de hacer una llamada a la biblioteca
  • porque implementa un retorno de error simple al establecer un estado global de la biblioteca.

El próximo estándar obtiene la definición de errno un poco más recto, pero estas fealdades quedan

A menudo hay un strtok_r.

Para realloc, si necesita usar el puntero anterior, no es tan difícil usar otra variable. Si su programa falla con un error de asignación, a menudo no es realmente necesario limpiar el puntero anterior.

  • Iba a decir que esto debería ser un comentario, no una respuesta, pero no puedes comentar sin un representante, así que aquí tienes algunos.

    – Esteban Canon

    3 de enero de 2011 a las 23:09

  • En el momento en que dices “a menudo hay strtok_r()“, te encuentras con “ocasionalmente no hay” y “¿qué vas a hacer cuando no esté disponible?”. El problema secundario es la plataforma supuesta: la pregunta habla de C99, donde strtok_r() no está disponible (ni tampoco strtok_s() en general – de TR 24731-1).

    –Jonathan Leffler

    4 de enero de 2011 a las 0:02

Yo pondría printf y scanf bastante alto en esta lista. El hecho de que tenga que obtener los especificadores de formato exactamente correctos hace que estas funciones sean difíciles de usar y extremadamente fáciles de equivocarse. También es muy difícil evitar el desbordamiento del búfer al leer datos. Además, la “vulnerabilidad de la cadena de formato printf” probablemente ha causado innumerables agujeros de seguridad cuando los programadores bien intencionados especifican cadenas especificadas por el cliente como el primer argumento para printf, solo para descubrir que la pila se rompió y la seguridad se vio comprometida muchos años después.

  • Iba a decir que esto debería ser un comentario, no una respuesta, pero no puedes comentar sin un representante, así que aquí tienes algunos.

    – Esteban Canon

    3 de enero de 2011 a las 23:09

  • En el momento en que dices “a menudo hay strtok_r()“, te encuentras con “ocasionalmente no hay” y “¿qué vas a hacer cuando no esté disponible?”. El problema secundario es la plataforma supuesta: la pregunta habla de C99, donde strtok_r() no está disponible (ni tampoco strtok_s() en general – de TR 24731-1).

    –Jonathan Leffler

    4 de enero de 2011 a las 0:02

Cualquiera de las funciones que manipulan el estado global, como gmtime() o localtime(). Estas funciones simplemente no se pueden usar de manera segura en múltiples subprocesos.

EDITAR: rand() está en la misma categoría que parece. Al menos no hay garantías de seguridad para subprocesos, y en mi sistema Linux, la página del manual advierte que no es reentrante ni es seguro para subprocesos.

  • Hasta donde yo sé, la única forma conforme de hacer rand thread-safe sería sincronizarlo con un mutex, lo que perjudicaría bastante el rendimiento. Para una semilla determinada, se supone que siempre debe devolver la misma secuencia de números pseudoaleatorios, por lo que usar un estado local de subproceso podría romper esta semántica en aplicaciones conformes que usan su propio mutex alrededor de las llamadas a rand.

    – R.. GitHub DEJA DE AYUDAR A ICE

    5 de enero de 2011 a las 16:39

  • … o que utilizan inicialmente srand y rand solo en el subproceso principal, luego, después de la inicialización, continúe usándolo en un subproceso recién creado y nunca más lo use en el subproceso principal.

    – R.. GitHub DEJA DE AYUDAR A ICE

    6 de enero de 2011 a las 16:30

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad