¿Por qué está en desuso printf con un solo argumento (sin especificadores de conversión)?

9 minutos de lectura

avatar de usuario
usuario de pila

En un libro que estoy leyendo, está escrito que printf con un solo argumento (sin especificadores de conversión) está en desuso. Se recomienda sustituir

printf("Hello World!");

con

puts("Hello World!");

o

printf("%s", "Hello World!");

¿Alguien puede decirme por qué? printf("Hello World!"); ¿Está Mal? Está escrito en el libro que contiene vulnerabilidades. ¿Cuáles son estas vulnerabilidades?

  • Nota: printf("Hello World!") es no lo mismo que puts("Hello World!"). puts() agrega un '\n'. en lugar de comparar printf("abc") para fputs("abc", stdout)

    – chux – Reincorporar a Monica

    8 de julio de 2015 a las 12:36


  • ¿Qué es ese libro? no creo printf está en desuso de la misma manera que, por ejemplo gets está en desuso en C99, por lo que puede considerar editar su pregunta para ser más preciso.

    – el.pescado – нет войне

    9 de julio de 2015 a las 18:53

  • Parece que el libro que está leyendo no es muy bueno: un buen libro no debe simplemente decir que algo como esto está “obsoleto” (eso es realmente falso a menos que el autor esté usando la palabra para describir su propia opinión) y debe explicar qué uso es en realidad inválido y peligroso en lugar de mostrar un código seguro/válido como ejemplo de algo que “no debe hacer”.

    – R.. GitHub DEJA DE AYUDAR A ICE

    9 de julio de 2015 a las 23:14

  • ¿Puedes identificar el libro?

    –Keith Thompson

    11 de julio de 2015 a las 9:34

  • Por favor, especifique el título del libro, el autor y la página de referencia. Gracias.

    – Greenonline

    11 de julio de 2015 a las 11:57

avatar de usuario
Jabberwocky

printf("Hello World!"); en mi humilde opinión no es vulnerable, pero considere esto:

const char *str;
...
printf(str);

Si str pasa a apuntar a una cadena que contiene %s especificadores de formato, su programa exhibirá un comportamiento indefinido (principalmente un bloqueo), mientras que puts(str) simplemente mostrará la cadena tal como está.

Ejemplo:

printf("%s");   //undefined behaviour (mostly crash)
puts("%s");     // displays "%s\n"

  • Además de hacer que el programa se bloquee, hay muchos otros exploits posibles con cadenas de formato. Vea aqui para mas informacion: en.wikipedia.org/wiki/Descontrolado_formato_cadena

    – e.dan

    8 de julio de 2015 a las 11:19

  • Otra razón es que puts será presumiblemente más rápido.

    – edmz

    8 de julio de 2015 a las 11:44

  • @negro: puts es “presumiblemente” más rápido, y esta es probablemente otra razón por la que la gente lo recomienda, pero no es Realmente más rápido. acabo de imprimir "Hello, world!" 1.000.000 de veces, en ambos sentidos. Con printf tardó 0,92 segundos. Con puts tardó 0,93 segundos. Hay cosas de las que preocuparse cuando se trata de eficiencia, pero printf contra puts no es uno de ellos.

    – Steve cumbre

    8 de julio de 2015 a las 14:21


  • @KonstantinWeitz: Pero (a) no estaba usando gcc y (b) no importa por qué La reclamación “puts es más rápido” es falso, sigue siendo falso.

    – Steve cumbre

    08/07/2015 a las 16:15


  • gcc convierte automáticamente printf para puts cuando solo hay un único argumento, la cadena de formato no contiene ningún campo % y termina con '\n'. No es necesario activar optimizaciones para eso. Basta con mirar el código ensamblador producido por gcc -S.

    – dlask

    14/07/2015 a las 19:09

printf("Hello world");

está bien y no tiene ninguna vulnerabilidad de seguridad.

El problema radica en:

printf(p);

donde p es un puntero a una entrada controlada por el usuario. es propenso a ataques de cadenas de formato: el usuario puede insertar especificaciones de conversión para tomar el control del programa, por ejemplo, %x para volcar la memoria o %n para sobrescribir la memoria.

Tenga en cuenta que puts("Hello world") no es equivalente en comportamiento a printf("Hello world") sino printf("Hello world\n"). Los compiladores generalmente son lo suficientemente inteligentes como para optimizar la última llamada para reemplazarla con puts.

  • por supuesto printf(p,x) sería igual de problemático si el usuario tiene control sobre p. entonces el problema es no el uso de printf con un solo argumento, sino con una cadena de formato controlada por el usuario.

    –Hagen von Eitzen

    9 de julio de 2015 a las 5:10

  • @HagenvonEitzen Eso es técnicamente cierto, pero pocos usarían deliberadamente una cadena de formato proporcionada por el usuario. cuando la gente escribe printf(p)es porque no se dan cuenta de que es una cadena de formato, simplemente piensan que están imprimiendo un literal.

    – Barmar

    15 de julio de 2015 a las 13:54

Además de las otras respuestas, printf("Hello world! I am 50% happy today") es un error fácil de hacer, que puede causar todo tipo de problemas de memoria desagradables (¡es UB!).

Es más simple, más fácil y más sólido “requerir” que los programadores sean absolutamente claros cuando quieren una cadena textual y nada más.

y eso es lo que printf("%s", "Hello world! I am 50% happy today") te atrapa Es completamente infalible.

(Steven, por supuesto printf("He has %d cherries\n", ncherries) no es en absoluto lo mismo; en este caso, el programador no tiene la mentalidad de “cadena textual”; ella tiene la mentalidad de “cadena de formato”).

  • No vale la pena discutir esto, y entiendo lo que dices sobre la mentalidad de cadena de formato frente a palabra por palabra, pero, bueno, no todo el mundo piensa de esa manera, que es una de las razones por las que las reglas de talla única pueden molestar. Decir “nunca imprimir cadenas constantes con printfes casi exactamente como decir “siempre escribe if(NULL == p). Estas reglas pueden ser útiles para algunos programadores, pero no para todos. Y en ambos casos (no coincidentes printf formatos y condicionales de Yoda), los compiladores modernos advierten sobre errores de todos modos, por lo que las reglas artificiales son aún menos importantes.

    – Steve cumbre

    08/07/2015 a las 13:55


  • @Steve Si hay exactamente cero ventajas para usar algo, pero bastantes desventajas, entonces sí, realmente no hay razón para usarlo. Condiciones de Yoda por otro lado. hacer tienen la desventaja de que hacen que el código sea más difícil de leer (intuitivamente dirías “si p es cero” y no “si cero es p”).

    – Vu

    9 de julio de 2015 a las 13:31

  • @Voo printf("%s", "hello") va a ser mas lento que printf("hello"), por lo que hay un inconveniente. Uno pequeño, porque IO casi siempre es mucho más lento que un formateo tan simple, pero tiene una desventaja.

    – Yakk – Adam Nevraumont

    9 de julio de 2015 a las 14:41

  • @Yakk dudo que sea más lento

    –MM

    22 de septiembre de 2017 a las 3:44

  • gcc -Wall -W -Werror evitará las malas consecuencias de tales errores.

    – chqrlie

    14 de febrero de 2020 a las 20:14

avatar de usuario
P1kachu

Solo añadiré un poco de información. en cuanto a la vulnerabilidad parte aquí.

Se dice que es vulnerable debido a la vulnerabilidad de formato de cadena printf. En su ejemplo, donde la cadena está codificada, es inofensiva (incluso si las cadenas codificadas como esta nunca se recomiendan por completo). Pero especificar los tipos de parámetros es un buen hábito. Toma este ejemplo:

Si alguien pone un carácter de cadena de formato en su printf en lugar de una cadena normal (por ejemplo, si desea imprimir el programa stdin), printf tomará todo lo que pueda en la pila.

Fue (y sigue siendo) muy utilizado para explotar programas en la exploración de pilas para acceder a información oculta o eludir la autenticación, por ejemplo.

Ejemplo (C):

int main(int argc, char *argv[])
{
    printf(argv[argc - 1]); // takes the first argument if it exists
}

si pongo como entrada de este programa "%08x %08x %08x %08x %08x\n"

printf ("%08x %08x %08x %08x %08x\n"); 

Esto le indica a la función printf que recupere cinco parámetros de la pila y los muestre como números hexadecimales con relleno de 8 dígitos. Entonces, una posible salida puede verse así:

40012980 080628c4 bffff7a4 00000005 08059c04

Ver esta para una explicación más completa y otros ejemplos.

avatar de usuario
Konstantin Weitz

Vocación printf con cadenas de formato literal es seguro y eficiente, y existen herramientas para advertirle automáticamente si su invocación de printf con cadenas de formato proporcionadas por el usuario no es seguro.

Los ataques más severos contra printf aprovechar la %n especificador de formato. A diferencia de todos los demás especificadores de formato, p. %d, %n en realidad escribe un valor en una dirección de memoria proporcionada en uno de los argumentos de formato. Esto significa que un atacante puede sobrescribir la memoria y, por lo tanto, potencialmente tomar el control de su programa. Wikipedia
proporciona más detalles.

si llamas printf con una cadena de formato literal, un atacante no puede escabullirse %n en su cadena de formato, y por lo tanto está seguro. De hecho, gcc cambiará su llamada a printf en una llamada a putspor lo que literalmente no hay ninguna diferencia (pruebe esto ejecutando gcc -O3 -S).

si llamas printf con una cadena de formato proporcionada por el usuario, un atacante puede colarse potencialmente %n en su cadena de formato y tome el control de su programa. Su compilador generalmente le advertirá que el suyo no es seguro, vea
-Wformat-security. También hay herramientas más avanzadas que aseguran que una invocación de printf es seguro incluso con cadenas de formato proporcionadas por el usuario, y es posible que incluso verifiquen que pase el número y el tipo correctos de argumentos a
printf. Por ejemplo, para Java hay Propenso a errores de Google
y el marco del verificador.

Este es un consejo equivocado. Sí, si tiene una cadena de tiempo de ejecución para imprimir,

printf(str);

es bastante peligroso, y siempre debes usar

printf("%s", str);

en cambio, porque en general nunca se puede saber si str podría contener un % firmar. Sin embargo, si tiene un tiempo de compilación constante cadena, no hay nada malo con

printf("Hello, world!\n");

(Entre otras cosas, ese es el programa C más clásico de la historia, literalmente del libro de programación C de Génesis. Entonces, cualquiera que desprecie ese uso está siendo bastante herético, ¡y yo por mi parte me ofendería un poco!)

avatar de usuario
Super gato

Un aspecto bastante desagradable de printf es que incluso en plataformas donde las lecturas de memoria extraviadas solo podrían causar un daño limitado (y aceptable), uno de los caracteres de formato, %n, hace que el siguiente argumento se interprete como un puntero a un entero escribible y hace que el número de caracteres de salida hasta el momento se almacene en la variable identificada de ese modo. Nunca he usado esa función yo mismo, y a veces uso métodos ligeros de estilo printf que he escrito para incluir solo las funciones que realmente uso (y no incluyo esa ni nada similar) pero alimentando las cadenas de funciones estándar de printf recibidas de fuentes no confiables puede exponer vulnerabilidades de seguridad más allá de la capacidad de leer almacenamiento arbitrario.

¿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