program story

단일 인수 (변환 지정자 없음)가있는 printf가 더 이상 사용되지 않는 이유는 무엇입니까?

inputbox 2020. 8. 21. 07:42
반응형

단일 인수 (변환 지정자 없음)가있는 printf가 더 이상 사용되지 않는 이유는 무엇입니까?


내가 읽고있는 책 printf에서 단일 인수 (변환 지정자 없음)가 더 이상 사용되지 않는다고 기록되어 있습니다. 대체하는 것이 좋습니다

printf("Hello World!");

puts("Hello World!");

또는

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

누군가가 왜 printf("Hello World!");잘못된 것인지 말해 줄 수 있습니까 ? 책에 취약점이 있다고 기록되어 있습니다. 이 취약점은 무엇입니까?


printf("Hello World!"); IMHO는 취약하지 않지만 다음을 고려하십시오.

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

경우 str문자열을 포함에 포인트로 발생하는 %s형식 지정, 프로그램은 반면에 정의되지 않은 동작 (주로 충돌)를 전시 할 예정 puts(str)이기 때문에 단지 문자열을 표시합니다.

예:

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

printf("Hello world");

괜찮으며 보안 취약점이 없습니다.

문제는 다음과 같습니다.

printf(p);

여기서 p사용자에 의해 제어되는 입력에 대한 포인터이다. 그것은하는 경향이 형식 문자열 공격 : 변환 사양을 삽입 할 수 있습니다 사용자는, 예를 들어, 프로그램 제어 할 %x메모리 나 덤프 %n덮어 쓰기 메모리에 있습니다.

puts("Hello world")동작 printf("Hello world")printf("Hello world\n"). 컴파일러는 일반적으로 후자의 호출을 최적화하여 puts.


다른 답변 외에도 printf("Hello world! I am 50% happy today")쉽게 버그를 만들 수 있으며 잠재적으로 모든 종류의 불쾌한 메모리 문제를 일으킬 수 있습니다 (UB!).

프로그래머가 축 어적 문자열 만 원할 때 절대적으로 명확하게 "요구"하는 것이 더 간단하고 쉽고 강력합니다 .

그리고 그것이 printf("%s", "Hello world! I am 50% happy today")당신을 얻는 것입니다. 완전히 완벽합니다.

(물론 스티브 printf("He has %d cherries\n", ncherries)는 절대적으로 같은 것이 아닙니다.이 경우 프로그래머는 "verbatim string"사고 방식이 아닙니다. 그녀는 "format string"사고 방식에 있습니다.)


여기에 취약성 부분 에 대한 정보 약간 추가 하겠습니다.

printf 문자열 형식 취약점으로 인해 취약한 것으로 알려져 있습니다. 귀하의 예에서 문자열이 하드 코딩 된 경우 무해합니다 (이와 같은 하드 코딩 문자열이 완전히 권장되지 않더라도). 그러나 매개 변수의 유형을 지정하는 것은 좋은 습관입니다. 이 예를 보자 :

누군가가 일반 문자열 대신에 형식 문자열 문자를 printf에 넣으면 (예를 들어 프로그램 stdin을 인쇄하려는 경우) printf는 스택에서 가능한 모든 것을 가져옵니다.

예를 들어 숨겨진 정보에 액세스하거나 인증을 우회하기 위해 스택을 탐색하도록 프로그램을 악용하는 데 매우 사용되었습니다.

예 (C) :

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

이 프로그램의 입력으로 넣으면 "%08x %08x %08x %08x %08x\n"

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

이것은 printf 함수가 스택에서 5 개의 매개 변수를 검색하고 8 자리 패딩 된 16 진수로 표시하도록 지시합니다. 따라서 가능한 출력은 다음과 같습니다.

40012980 080628c4 bffff7a4 00000005 08059c04

더 완전한 설명과 다른 예는 이것을 참조하십시오 .


printf리터럴 형식 문자열로 호출 하는 것은 안전하고 효율적이며 printf사용자 제공 형식 문자열로 호출하는 것이 안전하지 않은 경우 자동으로 경고하는 도구가 있습니다 .

가장 심각한 공격 printf%n형식 지정자를 활용 합니다. 다른 모든 형식 지정자와 달리 예를 들어 %d%n실제로 형식 인수 중 하나에 제공된 메모리 주소에 값을 씁니다. 이는 공격자가 메모리를 덮어 쓸 수 있으므로 잠재적으로 프로그램을 제어 할 수 있음을 의미합니다. Wikipedia 는 더 자세한 정보를 제공합니다.

printf리터럴 형식 문자열로 호출 하면 공격자가 형식 문자열에 잠입 %n할 수 없으므로 안전합니다. 실제로 gcc는에 대한 호출을 printf에 대한 호출로 변경 puts하므로 별다른 차이가 없습니다 (실행하여 테스트 gcc -O3 -S).

printf사용자가 제공 한 형식 문자열을 사용하여 호출 하면 공격자가 잠재적 %n으로 형식 문자열에 침투하여 프로그램을 제어 할 수 있습니다. 컴파일러는 일반적으로 안전하지 않다고 경고합니다 -Wformat-security.을 참조하십시오 . 또한 printf사용자가 제공 한 형식 문자열을 사용 하여를 호출 해도 안전한지 확인하는 고급 도구가 있으며 올바른 수와 유형의 인수를에 전달하는지 확인할 수도 있습니다 printf. 예를 들어 Java의 경우 Google의 Error ProneChecker Framework가 있습니다.


이것은 잘못된 조언입니다. 예, 인쇄 할 런타임 문자열이있는 경우

printf(str);

매우 위험하므로 항상

printf("%s", str);

instead, because in general you can never know whether str might contain a % sign. However, if you have a compile-time constant string, there's nothing whatsoever wrong with

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

(Among other things, that is the most classic C program ever, literally from the C programming book of Genesis. So anyone deprecating that usage is being rather heretical, and I for one would be somewhat offended!)


A rather nasty aspect of printf is that even on platforms where the stray memory reads could only cause limited (and acceptable) harm, one of the formatting characters, %n, causes the next argument to be interpreted as a pointer to a writable integer, and causes the number of characters output thus far to be stored to the variable identified thereby. I've never used that feature myself, and sometimes I use lightweight printf-style methods which I've written to include only the features I actually use (and don't include that one or anything similar) but feeding standard printf functions strings received from untrustworthy sources may expose security vulnerabilities beyond the ability to read arbitrary storage.


Since no one has mentioned, I'd add a note regarding their performance.

Under normal circumstances, assuming no compiler optimisations are used (i.e. printf() actually calls printf() and not fputs()), I would expect printf() to perform less efficiently, especially for long strings. This is because printf() has to parse the string to check if there are any conversion specifiers.

To confirm this, I have run some tests. The testing is performed on Ubuntu 14.04, with gcc 4.8.4. My machine uses an Intel i5 cpu. The program being tested is as follows:

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

Both are compiled with gcc -Wall -O0. Time is measured using time ./a.out > /dev/null. The following is the result of a typical run (I've run them five times, all results are within 0.002 seconds).

For the printf() variant:

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

For the fputs() variant:

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

This effect is amplified if you have a very long string.

#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;
}

For the printf() variant (ran three times, real plus/minus 1.5s):

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

For the fputs() variant (ran three times, real plus/minus 0.2s):

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

Note: After inspecting the assembly generated by gcc, I realised that gcc optimises the fputs() call to an fwrite() call, even with -O0. (The printf() call remains unchanged.) I am not sure whether this will invalidate my test, as the compiler calculates the string length for fwrite() at compile-time.


printf("Hello World\n")

automatically compiles to the equivalent

puts("Hello World")

you can check it with diassembling your executable:

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

using

char *variable;
... 
printf(variable)

will lead to security issues, don't ever use printf that way!

so your book is actually correct, using printf with one variable is deprecated but you can still use printf("my string\n") because it will automatically become puts


For gcc it is possible to enable specific warnings for checking printf() and scanf().

The gcc documentation states:

-Wformat is included in -Wall. For more control over some aspects of format checking, the options -Wformat-y2k, -Wno-format-extra-args, -Wno-format-zero-length, -Wformat-nonliteral, -Wformat-security, and -Wformat=2 are available, but are not included in -Wall.

The -Wformat which is enabled within the -Wall option does not enable several special warnings that help to find these cases:

  • -Wformat-nonliteral will warn if you do not pass a string litteral as format specifier.
  • -Wformat-security will warn if you pass a string that might contain a dangerous construct. It's a subset of -Wformat-nonliteral.

I have to admit that enabling -Wformat-security revealed several bugs we had in our codebase (logging module, error handling module, xml output module, all had some functions that could do undefined things if they had been called with % characters in their parameter. For info, our codebase is now around 20 years old and even if we were aware of these kind of problems, we were extremely surprised when we enabled these warnings how many of these bugs were still in the codebase).

참고URL : https://stackoverflow.com/questions/31290850/why-is-printf-with-a-single-argument-without-conversion-specifiers-deprecated

반응형