[c] gets 함수가 왜 그렇게 사용되어서는 안되는 위험한가요?

gets()GCC로 함수 를 사용하는 C 코드를 컴파일하려고 하면 다음 경고가 표시됩니다.

(.text + 0x34) : 경고 :`gets ‘함수는 위험하므로 사용해서는 안됩니다.

나는 이것이 스택 보호 및 보안과 관련이 있음을 기억하지만 정확히 왜 그런지 잘 모르겠습니다.

이 경고를 어떻게 제거 할 수 있으며 사용에 대한 경고가 표시되는 이유는 gets()무엇입니까?

경우 gets()매우 위험하다 왜 우리는 그것을 제거 할 수 없습니다?



답변

gets안전하게 사용하기 위해서는 읽을 문자 수를 정확히 알아야 버퍼를 충분히 크게 만들 수 있습니다. 어떤 데이터를 읽을 것인지 정확히 아는 경우에만 알 수 있습니다.

을 사용하는 대신 서명이있는을 ( gets를) 사용하려고합니다.fgets

char* fgets(char *string, int length, FILE * stream);

( fgets, 전체 행을 읽으면 '\n'문자열을 그대로 두어야합니다 . 그러면 처리해야합니다.)

이 언어는 1999 ISO C 표준까지 언어의 공식적인 부분으로 유지되었지만 2011 표준에 의해 공식적으로 제거되었습니다. 대부분의 C 구현은 여전히 ​​그것을 지원하지만 적어도 gcc는 그것을 사용하는 코드에 대해 경고를 발행합니다.


답변

gets()위험한가

최초의 인터넷 웜 ( 모리스 인터넷 웜 )은 약 30 년 전 (1988-11-02) 탈출 gets()했으며 시스템에서 시스템으로 전파하는 방법 중 하나로 버퍼 오버 플로우를 사용했습니다 . 기본적인 문제는 함수가 버퍼의 크기를 알지 못하기 때문에 줄 바꿈을 찾거나 EOF를 발견 할 때까지 계속 읽으며 주어진 버퍼 범위를 오버플로 할 수 있다는 것입니다.

당신은 gets()존재 한다는 말을 잊어 버려야합니다 .

C11 표준 ISO / IEC 9899 : 2011 gets()은 표준 기능으로 제거 되었으며 A Good Thing ™ (ISO / IEC 9899 : 1999 / Cor.3 : 2007에서 공식적으로 ‘폐기 됨’및 ‘더 이상 사용되지 않음’으로 표시됨-기술 강령) C99의 경우 3, C11에서 제거됨). 안타깝게도 이전 버전과의 호환성으로 인해 수년 동안 라이브러리에 남아있을 것입니다 ( ‘수십 년’). 그것이 나에게 달려 있다면 구현은 gets()다음과 같습니다.

char *gets(char *buffer)
{
    assert(buffer != 0);
    abort();
    return 0;
}

어쨌든 코드가 조만간 충돌 할 수 있으므로 문제를 빨리 해결하는 것이 좋습니다. 오류 메시지를 추가 할 준비가되었습니다.

fputs("obsolete and dangerous function gets() called\n", stderr);

최신 버전의 Linux 컴파일 시스템은 연결하면 gets()보안 문제 ( mktemp(),…) 가있는 다른 기능에 대해서도 경고를 생성 합니다.

대안 gets()

fgets ()

다른 사람들이 말했듯이, 정규 대안은 할 수 gets()있다 fgets()지정 stdin파일 스트림으로.

char buffer[BUFSIZ];

while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
    ...process line of data...
}

아직 아무도 언급 gets()하지 않은 것은 줄 바꿈을 포함하지 않지만 포함합니다 fgets(). 따라서 fgets()줄 바꿈을 삭제 하는 래퍼를 사용해야 할 수도 있습니다 .

char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
    if (fgets(buffer, buflen, fp) != 0)
    {
        size_t len = strlen(buffer);
        if (len > 0 && buffer[len-1] == '\n')
            buffer[len-1] = '\0';
        return buffer;
    }
    return 0;
}

또는 더 나은 :

char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
    if (fgets(buffer, buflen, fp) != 0)
    {
        buffer[strcspn(buffer, "\n")] = '\0';
        return buffer;
    }
    return 0;
}

또한 caf 가 주석에서 지적하고 paxdiablo 가 그의 대답에 표시 fgets()하면 데이터가 한 줄에 남아있을 수 있습니다. 래퍼 코드는 다음에 해당 데이터를 읽습니다. 원하는 경우 나머지 데이터 라인을 고치기 위해 쉽게 수정할 수 있습니다.

        if (len > 0 && buffer[len-1] == '\n')
            buffer[len-1] = '\0';
        else
        {
             int ch;
             while ((ch = getc(fp)) != EOF && ch != '\n')
                 ;
        }

잔차 문제는 EOF 또는 오류, 줄 읽기 및 잘리지 않음 및 부분 줄 읽기이지만 데이터가 잘린 세 가지 결과 상태를보고하는 방법입니다.

이 문제는 gets()버퍼가 끝나는 곳을 모르고 끝을 넘어가는 것을 알지 못하기 때문에 아름답게 돌린 메모리 레이아웃을 혼란스럽게 만들고 버퍼가 할당되면 반환 스택 ( 스택 오버플로 )을 망칠 수 있기 때문에 발생 하지 않습니다 . 버퍼가 동적으로 할당되면 스택 또는 제어 정보를 짓밟거나 버퍼가 정적으로 할당되면 다른 중요한 전역 (또는 모듈) 변수를 통해 데이터를 복사합니다. 이것들 중 어느 것도 좋은 생각이 아닙니다. ‘정의되지 않은 행동’이라는 문구를 나타냅니다.


도있다 TR 24731-1 기능을 포함하여 다양한에 안전한 대안을 제공합니다 (C 표준위원회에서 기술 보고서) gets():

§6.5.4.1 gets_s기능

개요

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);

런타임 제약

s널 포인터가 아니어야합니다. n0과 같거나 RSIZE_MAX보다 크지 않아야합니다. 개행 문자, 파일 끝 또는 읽기 오류는에서 n-1문자를 읽을 때 발생합니다
stdin. 25)

3 런타임 제약 조건 위반이있는 경우 s[0]널 문자로 설정되고 stdin개행 문자를 읽거나 파일 끝 또는 읽기 오류가 발생할 때까지 문자를 읽고 버립니다 .

기술

4이 gets_s함수 n
는에 의해 지정된 스트림에서에 의해 지정된 stdin배열로 지정된 문자 수보다 적은 수를 읽습니다 s. 줄 바꿈 문자 (폐기) 이후 또는 파일 끝 이후에는 추가 문자를 읽지 않습니다. 버려진 개행 문자는 읽은 문자 수에 포함되지 않습니다. 배열에 마지막 문자를 읽은 직후 널 문자가 작성됩니다.

5 파일 끝이 발견되고 배열로 읽은 문자가 없거나 조작 중에 읽기 오류가 발생 s[0]하면 널 문자로 설정되고 다른 요소는 s지정되지 않은 값을 갖습니다.

권장 연습

6이 fgets기능을 사용하면 올바르게 작성된 프로그램이 입력 배열을 너무 길게 처리하여 결과 배열에 저장할 수 없습니다. 일반적으로 이것은 호출자가 fgets결과 배열에 개행 문자의 존재 또는 부재에주의를 기울여야합니다. fgets대신 (개행 문자를 기반으로 필요한 처리와 함께)
사용을 고려하십시오 gets_s.

25)gets_s함수는, 달리 gets입력 행이 버퍼를 오버플로하여 저장하기 위해 런타임 제약 조건 위반을 만듭니다. 달리 fgets, gets_s입력 라인에 성공적으로 호출 사이의 일대일 관계를 유지한다 gets_s. 사용하는 프로그램은 gets그러한 관계를 기대합니다.

Microsoft Visual Studio 컴파일러는 TR 24731-1 표준에 대한 근사치를 구현하지만 Microsoft에서 구현 한 서명과 TR의 서명간에 차이가 있습니다.

C11 표준 ISO / IEC 9899-2011에는 라이브러리의 선택적 부분으로 부록 K의 TR24731이 포함되어 있습니다. 불행히도 유닉스 계열 시스템에서는 거의 구현되지 않습니다.


getline() — POSIX

POSIX 2008 년도에 안전한 대안 제공 gets()이라고를 getline(). 라인을위한 공간을 동적으로 할당하므로 결국이를 해제해야합니다. 따라서 줄 길이에 대한 제한이 제거됩니다. 또한 읽은 데이터의 길이 -1( 또는 EOF!가 아님)를 반환하므로 입력의 널 바이트를 안정적으로 처리 할 수 ​​있습니다. ‘자신의 단일 문자 구분 기호 선택’변형도 있습니다 getdelim(). 예를 들어, find -print0파일 이름 끝이 ASCII NUL '\0'문자 로 표시 되는 위치 의 출력을 처리 할 때 유용 할 수 있습니다 .


답변

때문에 gets로부터 바이트를 가져 오는 동안 검사의 종류를하지 않습니다 stdin을 어딘가에을 넣어. 간단한 예 :

char array1[] = "12345";
char array2[] = "67890";

gets(array1);

이제, 당신은 당신이 원하는 수의 문자를 입력 할 수 있습니다, gets상관하지 않습니다. 두 번째로 배열의 크기 (이 경우 array1)를 초과하는 바이트 는 메모리에서 찾은 모든 것을 덮어 씁니다 gets. 앞의 예에서 이것은 "abcdefghijklmnopqrts"예측할 수 없을 정도로 입력 한 경우 에도 다른 내용을 덮어 씁니다 array2.

이 함수는 일관된 입력을 가정하기 때문에 안전하지 않습니다. 절대 사용하지 마십시오!


답변

gets버퍼 오버 플로우를 중지 할 방법이 없으므로 사용해서는 안됩니다 . 사용자가 버퍼에 넣을 수있는 것보다 많은 데이터를 입력하면 손상이 발생하거나 더 악화 될 수 있습니다.

실제로 ISO는 실제로 C 표준에서 제거 하는 단계를 gets밟았습니다 (C99에서는 더 이상 사용되지 않지만 C99에서는 더 이상 사용되지 않음).

올바른 것은 사용자가 읽은 문자를 제한 할 수 있으므로 파일 핸들에 fgets함수 를 사용하는 것입니다 stdin.

그러나 이것은 또한 다음과 같은 문제가 있습니다.

  • 다음에 사용자가 입력 한 추가 문자가 선택됩니다.
  • 사용자가 너무 많은 데이터를 입력했다는 빠른 알림이 없습니다.

이를 위해 경력의 어느 시점에서든 거의 모든 C 코더가 더 유용한 래퍼 fgets도 작성합니다. 내 꺼야 :

#include <stdio.h>
#include <string.h>

#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    if (buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

몇 가지 테스트 코드 :

// Test program for getLine().

int main (void) {
    int rc;
    char buff[10];

    rc = getLine ("Enter string> ", buff, sizeof(buff));
    if (rc == NO_INPUT) {
        printf ("No input\n");
        return 1;
    }

    if (rc == TOO_LONG) {
        printf ("Input too long\n");
        return 1;
    }

    printf ("OK [%s]\n", buff);

    return 0;
}

fgets버퍼 오버플로를 방지 하는 것과 동일한 보호 기능을 제공 하지만 호출자에게 발생한 상황을 알리고 초과 문자를 지워 다음 입력 작업에 영향을 미치지 않도록합니다.

당신이 원하는대로 자유롭게 사용하십시오, 나는 “당신이 원하는 것을하세요”라이센스하에 배포합니다 🙂


답변

fgets .

stdin에서 읽으려면 :

char string[512];

fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */


답변

API를 중단하지 않으면 API 함수를 제거 할 수 없습니다. 그렇다면 많은 응용 프로그램이 더 이상 컴파일되거나 실행되지 않습니다.

이것이 하나의 참조가 제공 하는 이유입니다 .

에 의해 지정된 배열을 오버플로하는 행을 읽으면 정의되지 않은 동작이 발생합니다. fgets ()를 사용하는 것이 좋습니다.


답변

나는에, 최근에 읽은 에 유즈넷 게시물comp.lang.c , gets()표준에서 제거지고 있습니다. 우 호우

위원회가 드래프트에서 gets ()를 제거하기 위해 방금 투표했다는 사실을 알게되어 기쁩니다.