venta unitario produccion precio metodos indirectos historia fabricacion elementos distribucion costos costo contabilidad c security printf format-specifiers puts

unitario - metodos de distribucion de costos indirectos de fabricacion



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

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 robusto "exigir" a los programadores que sean absolutamente claros cuando quieren una cadena literal y nada más .

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

(Steve, por supuesto, printf("He has %d cherries/n", ncherries) es absolutamente lo mismo; en este caso, el programador no está en la mentalidad de "cadena literal"; ella está en la mentalidad de "cadena de formato". )

En un libro que estoy leyendo, está escrito que printf con un solo argumento (sin especificadores de conversión) está en desuso. 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?


Agregaré un poco de información sobre la parte de vulnerabilidad aquí.

Se dice que es vulnerable debido a la vulnerabilidad del formato de cadena printf. En su ejemplo, donde la cadena está codificada, es inofensiva (incluso si las cadenas de codificación 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 stdin del programa), printf tomará todo lo que pueda en la pila.

Fue (y sigue siendo) muy utilizado para explotar programas en pilas de exploración para acceder a información oculta o evitar 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 de 8 dígitos. Entonces, un posible resultado puede verse así:

40012980 080628c4 bffff7a4 00000005 08059c04

Vea this para una explicación más completa y otros ejemplos.


Como nadie lo ha mencionado, agregaría una nota sobre su desempeño.

En circunstancias normales, suponiendo que no se usen optimizaciones del compilador (es decir, printf() realidad llama a printf() y no a fputs() ), esperaría que printf() funcione de manera menos eficiente, especialmente para cadenas largas. Esto se debe a que printf() tiene que analizar la cadena para verificar si hay algún especificador de conversión.

Para confirmar esto, he realizado algunas pruebas. La prueba se realiza en Ubuntu 14.04, con gcc 4.8.4. Mi máquina usa una CPU Intel i5. El programa que se prueba es el siguiente:

#include <stdio.h> int main() { int count = 10000000; while(count--) { // either printf("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"); // or fputs("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM", stdout); } fflush(stdout); return 0; }

Ambos están compilados con gcc -Wall -O0 . El tiempo se mide usando time ./a.out > /dev/null . El siguiente es el resultado de una ejecución típica (las he ejecutado cinco veces, todos los resultados están dentro de 0.002 segundos).

Para la variante printf() :

real 0m0.416s user 0m0.384s sys 0m0.033s

Para la variante fputs() :

real 0m0.297s user 0m0.265s sys 0m0.032s

Este efecto se amplifica si tiene una cadena muy larga.

#include <stdio.h> #define STR "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM" #define STR2 STR STR #define STR4 STR2 STR2 #define STR8 STR4 STR4 #define STR16 STR8 STR8 #define STR32 STR16 STR16 #define STR64 STR32 STR32 #define STR128 STR64 STR64 #define STR256 STR128 STR128 #define STR512 STR256 STR256 #define STR1024 STR512 STR512 int main() { int count = 10000000; while(count--) { // either printf(STR1024); // or fputs(STR1024, stdout); } fflush(stdout); return 0; }

Para la variante printf() (se ejecutó tres veces, real más / menos 1.5s):

real 0m39.259s user 0m34.445s sys 0m4.839s

Para la variante fputs() (se ejecutó tres veces, real más / menos 0.2s):

real 0m12.726s user 0m8.152s sys 0m4.581s

Nota: Después de inspeccionar el ensamblaje generado por gcc, me di cuenta de que gcc optimiza la llamada fputs() a una llamada fwrite() , incluso con -O0 . (La llamada printf() permanece sin cambios.) No estoy seguro de si esto invalidará mi prueba, ya que el compilador calcula la longitud de la cadena para fwrite() en tiempo de compilación.


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 signo % . Sin embargo, si tiene una cadena constante en tiempo de compilación, no hay nada de malo en

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

(Entre otras cosas, ese es el programa C más clásico de todos los tiempos, literalmente del libro de programación C de Génesis. Por lo tanto, cualquiera que desaproveche ese uso es bastante herético, ¡y por mi parte me ofendería un poco!)


Llamar a 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 segura.

Los ataques más severos en printf aprovechan el especificador de formato %n . A diferencia de todos los demás especificadores de formato, por ejemplo, %d , %n realmente 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. en.wikipedia.org/wiki/Uncontrolled_format_string proporciona más detalles.

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

Si llama a printf con una cadena de formato proporcionada por el usuario, un atacante puede introducir un %n en su cadena de formato y tomar 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 sea ​​segura incluso con cadenas de formato proporcionadas por el usuario, e incluso pueden verificar que pase el número y tipo de argumentos correctos a printf . Por ejemplo, para Java existe el Propenso a errores de Google y el Checker Framework .


Para gcc es posible habilitar advertencias específicas para verificar printf() y scanf() .

La documentación de gcc dice:

-Wformat está incluido en -Wall . Para un mayor control sobre algunos aspectos de la verificación de formato, las opciones -Wformat-y2k , -Wno-format-extra-args , -Wno-format-zero-length , -Wformat-nonliteral -Wformat-security , -Wformat-security y -Wformat=2 están disponibles, pero no están incluidos en -Wall .

El -Wformat que está habilitado dentro de la opción -Wall no habilita varias advertencias especiales que ayudan a encontrar estos casos:

  • -Wformat-nonliteral le avisará si no pasa una cadena literal como especificador de formato.
  • -Wformat-security le avisará si pasa una cadena que podría contener una construcción peligrosa. Es un subconjunto de -Wformat-nonliteral .

Tengo que admitir que habilitar -Wformat-security reveló varios errores que teníamos en nuestra base de código (módulo de registro, módulo de manejo de errores, módulo de salida xml, todos tenían algunas funciones que podrían hacer cosas indefinidas si se hubieran llamado con% caracteres en su parámetro Para obtener información, nuestra base de código tiene ahora alrededor de 20 años e incluso si estuviéramos al tanto de este tipo de problemas, nos sorprendió mucho cuando habilitamos estas advertencias de cuántos de estos errores todavía estaban en la base de código).


Un aspecto bastante desagradable de printf es que incluso en plataformas donde las lecturas de memoria perdida solo pueden 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 ahora se almacene en la variable identificada de ese modo. Nunca he usado esa característica yo mismo, y a veces uso métodos ligeros de estilo printf que he escrito para incluir solo las características que realmente uso (y no incluir esa o algo similar) pero alimentando cadenas de funciones printf estándar recibidas de fuentes no confiables puede exponer vulnerabilidades de seguridad más allá de la capacidad de leer almacenamiento arbitrario.


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

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

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

Ejemplo:

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


printf("Hello world");

está bien y no tiene vulnerabilidad de seguridad.

El problema radica en:

printf(p);

donde p es un puntero a una entrada controlada por el usuario. Es propenso a formatear ataques de cadenas : 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 a printf("Hello world/n") . Los compiladores generalmente son lo suficientemente inteligentes como para optimizar la última llamada para reemplazarla con puts .


printf("Hello World/n")

compila automáticamente al equivalente

puts("Hello World")

puedes verificarlo desarmando tu ejecutable:

push rbp mov rbp,rsp mov edi,str.Helloworld! call dword imp.puts mov eax,0x0 pop rbp ret

utilizando

char *variable; ... printf(variable)

conducirá a problemas de seguridad, ¡nunca use printf de esa manera!

por lo que su libro es correcto, el uso de printf con una variable está en desuso pero aún puede usar printf ("mi cadena / n") porque se convertirá automáticamente en put