scanf가 C에서 버퍼 오버플로를 일으키는 것을 방지하는 방법은 무엇입니까?


81

이 코드를 사용합니다.

while ( scanf("%s", buf) == 1 ){

임의 길이의 문자열을 전달할 수 있도록 가능한 버퍼 오버플로를 방지하는 가장 좋은 방법은 무엇입니까?

예를 들어 다음을 호출하여 입력 문자열을 제한 할 수 있다는 것을 알고 있습니다.

while ( scanf("%20s", buf) == 1 ){

하지만 사용자가 입력 한 모든 것을 처리 할 수 ​​있기를 원합니다. 아니면 scanf를 사용하여 안전하게 수행 할 수없고 fgets를 사용해야합니까?

답변:


64

Kernighan과 Pike는 저서 The Practice of Programming (읽을 가치가 있음)에서이 문제를 논의하고 함수 패밀리에 snprintf()전달할 올바른 버퍼 크기를 가진 문자열을 만드는 데 사용하여 문제를 해결합니다 scanf(). 사실상:

int scanner(const char *data, char *buffer, size_t buflen)
{
    char format[32];
    if (buflen == 0)
        return 0;
    snprintf(format, sizeof(format), "%%%ds", (int)(buflen-1));
    return sscanf(data, format, buffer);
}

이것은 여전히 ​​'버퍼'로 제공되는 크기로 입력을 제한합니다. 더 많은 공간이 필요하면 메모리 할당을 수행하거나 메모리 할당을 수행하는 비표준 라이브러리 기능을 사용해야합니다.


참고의 POSIX 2008 (2013) 버전 것으로 scanf()기능의 가족이 형식 수정 지원 m문자열 입력에 대한 (할당 할당 문자) ( %s, %c, %[). char *인수 를받는 대신 char **인수를 사용하고 읽는 값에 필요한 공간을 할당합니다.

char *buffer = 0;
if (sscanf(data, "%ms", &buffer) == 1)
{
    printf("String is: <<%s>>\n", buffer);
    free(buffer);
}

은 IF sscanf()함수는 모든 변환 사양을 만족하지, 모든이에 할당 된 메모리 %ms-like 변환은 함수가 반환하기 전에 해제됩니다.


@ 샘 : 예, 그래야 buflen-1합니다. 감사합니다. 그런 다음 서명되지 않은 언더 플로 (다소 많은 수로 래핑)에 대해 걱정해야합니다 if. 나는 그것을으로 대체 assert()하거나 누군가가 크기로 0을 넘길만큼 부주의하다면 개발 중에 발생 하는 assert()이전으로 백업 하고 싶을 것 if입니다. 의 %0s의미 에 대해 문서를주의 깊게 검토하지 않았습니다 sscanf(). 테스트가 if (buflen < 2).
Jonathan Leffler 2013 년

따라서 snprintf일부 데이터를 문자열 버퍼에 쓰고 sscanf생성 된 문자열에서 읽습니다. scanfstdin에서 읽는다는 점에서 정확히 어디를 대체 합니까?
krb686

또한 결과 문자열에 "format"이라는 단어를 사용하여 "format"을 첫 번째 인수로 전달하는 것은 매우 혼란 스럽지만 snprintf실제 형식 매개 변수가 아닙니다.
krb686

@ krb686 :이 코드는 스캔 할 데이터가 매개 변수 data에 있으므로 sscanf()적절하도록 작성되었습니다. 대신 표준 입력에서 읽으려면 data매개 변수를 삭제하고 scanf()대신 호출하십시오 . format에 대한 호출에서 형식 문자열이되는 변수 의 이름 선택과 관련하여 sscanf()원하는 경우 이름을 바꿀 수 있지만 이름이 정확하지 않습니다. 어떤 대안이 타당한 지 잘 모르겠습니다. 것 in_format그 어떤 명확하게? 이 코드에서 변경할 계획이 없습니다. 이 아이디어를 자신의 코드에서 사용할 수 있습니다.
Jonathan Leffler 2015

1
@mabraham : macOS Sierra 10.12.5 (2017-06-06까지)에서도 여전히 사실 scanf()입니다. macOS에서는을 지원하는 것으로 문서화되어 있지 %ms않지만 유용합니다.
Jonathan Leffler

30

gcc를 사용하는 경우 GNU 확장 a지정자를 사용하여 scanf ()가 입력을 저장할 메모리를 할당하도록 할 수 있습니다.

int main()
{
  char *str = NULL;

  scanf ("%as", &str);
  if (str) {
      printf("\"%s\"\n", str);
      free(str);
  }
  return 0;
}

편집 : Jonathan이 지적했듯이 scanf지정자가 다를 수 있고 ( %m) 컴파일 할 때 특정 정의를 활성화해야 할 수 있으므로 man 페이지를 참조 해야합니다.


8
이는 GNU C 컴파일러를 사용하는 것보다 glibc (GNU C 라이브러리)를 사용하는 데 더 많은 문제입니다.
Jonathan Leffler

3
그리고 POSIX 2008 표준은 m동일한 작업을 수행하는 수정자를 제공합니다 . 을 참조하십시오 scanf(). 사용하는 시스템이이 수정자를 지원하는지 확인해야합니다.
Jonathan Leffler 2014

4
GNU (우분투 13.10에서 찾을 수 있음)는 %ms. 표기법 %a%f(출력시 16 진 부동 소수점 데이터를 요청 함 )의 동의어입니다 . 에 대한 GNU 매뉴얼 페이지 scanf()는 다음과 같습니다. _ 프로그램이 gcc -std=c99또는 gcc -D_ISOC99_SOURCE ( _GNU_SOURCE지정 되지 않은 경우 ) 로 컴파일 된 경우 사용할 수 없습니다 .이 경우는 a부동 소수점 숫자에 대한 지정자로 해석됩니다 (위 참조) ._
조나단 레플러

8

대부분의 경우 fgetssscanf작업을 수행합니다. 다른 것은 입력이 잘 포맷 된 경우 자신 만의 파서를 작성하는 것입니다. 또한 두 번째 예제를 안전하게 사용하려면 약간의 수정이 필요합니다.

#define LENGTH          42
#define str(x)          # x
#define xstr(x)         str(x)

/* ... */ 
int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array); 

위는 줄 바꿈 ( \n) 문자를 포함하지 않고 입력 스트림을 버립니다 . getchar()이것을 사용하려면를 추가해야합니다 . 또한 스트림의 끝에 도달했는지 확인하십시오.

if (!feof(stdin)) { ...

그게 다야.


2
feof코드를 더 큰 컨텍스트에 넣을 수 있습니까? 그 기능은 종종 잘못 사용되기 때문에 묻습니다.
Roland Illig 2016 년

1
array필요char array[LENGTH+1];
jxh

4

직접 사용 scanf(3) 및 그 변형은 많은 문제를 제기합니다. 일반적으로 사용자 및 비대화 형 사용 사례는 입력 라인으로 정의됩니다. 충분한 객체가 발견되지 않으면 더 많은 줄이 문제를 해결할 수있는 경우는 드물지만 이것이 scanf의 기본 모드입니다. (사용자가 첫 번째 줄에 숫자를 입력하는 것을 몰랐다면 두 번째와 세 번째 줄은 도움이되지 않을 것입니다.)

적어도 fgets(3)프로그램에 필요한 입력 라인 수 를 알고 있고 버퍼 오버플로가 발생하지 않으면 ...


1

입력 길이를 제한하는 것이 확실히 더 쉽습니다. 루프를 사용하여 한 번에 조금씩 읽고 필요에 따라 문자열 공간을 다시 할당하여 임의의 긴 입력을 허용 할 수 있습니다.

그러나 그것은 많은 작업이므로 대부분의 C 프로그래머는 임의의 길이로 입력을 잘라냅니다. 이미 알고 있다고 가정하지만 fgets ()를 사용하면 임의의 양의 텍스트를 허용 할 수 없습니다. 여전히 제한을 설정해야합니다.


그럼 아무도 scanf로 어떻게 할 수 있는지 알고 있습니까?
goe

3
루프에서 fget을 사용하면 임의의 양의 텍스트를 허용 할 수 있습니다 realloc(). 버퍼를 계속 유지 하십시오.
bdonlan

1

문자열에 필요한 메모리를 할당하는 함수를 만드는 것은 그리 많은 작업이 아닙니다. 그것은 내가 얼마 전에 작성한 약간의 c- 함수이며, 나는 항상 그것을 문자열로 읽는 데 사용합니다.

읽기 문자열을 반환하거나 메모리 오류가 발생하면 NULL을 반환합니다. 그러나 문자열을 free ()해야하며 항상 반환 값을 확인해야합니다.

#define BUFFER 32

char *readString()
{
    char *str = malloc(sizeof(char) * BUFFER), *err;
    int pos;
    for(pos = 0; str != NULL && (str[pos] = getchar()) != '\n'; pos++)
    {
        if(pos % BUFFER == BUFFER - 1)
        {
            if((err = realloc(str, sizeof(char) * (BUFFER + pos + 1))) == NULL)
                free(str);
            str = err;
        }
    }
    if(str != NULL)
        str[pos] = '\0';
    return str;
}

sizeof (char)정의에 따른 것 1입니다. 여기서 필요하지 않습니다.
RastaJedi

일반적으로 포인터 할당 / 해제를 동일한 수준으로 유지하는 것이 좋습니다. 즉, 호출자가 메모리를 해제해야하므로 함수가 자체적으로 메모리를 할당해서는 안됩니다. 표준 라이브러리 / POSIX 기능의 대부분은 (같은 정적 문자열을 반환하거나하여이 원칙을 준수 strerror(3)) 또는 미리 할당에 전달 된 문자열 ((같은 기대 strerror_r(3)또는 - scanf(3)...)
마이클 맥주를
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.