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


229

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

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

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

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

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



답변:


179

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

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

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

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

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


79
실제로 경고하는 것은 gcc가 아니며, gets()사용시 컴파일러가 경고를 발생 시키는 pragma 또는 속성을 포함하는 glibc입니다 .
fuz

실제로 @fuz는 경고하는 컴파일러 일뿐 만 아니라 OP에 인용 된 경고가 링커에 의해 인쇄되었습니다!
Ruslan

163

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'문자 로 표시 되는 위치 의 출력을 처리 할 때 유용 할 수 있습니다 .


8
또한 지적 할 가치가 fgets()있으며 fgets_wrapper()버전은 다음 입력 함수에서 읽을 수 있도록 입력 버퍼에 긴 줄의 끝 부분을 남겨 둡니다. 대부분의 경우 이러한 문자를 읽고 버리려고합니다.
caf

5
왜 바보 같은 전화를 걸지 않고도 기능을 사용할 수 있도록 fgets () 대안을 추가하지 않았는지 궁금합니다. 예를 들어, 문자열로 읽은 바이트 수를 반환 한 fgets 변형은 코드에서 마지막 바이트를 읽은 줄이 개행인지 쉽게 확인할 수있게합니다. 버퍼에 대해 널 포인터를 전달하는 동작이 "다음 줄 바꾸기까지 n-1 바이트까지 읽고 삭제"로 정의 된 경우 코드에서 길이가 긴 행의 꼬리를 쉽게 버릴 수 있습니다.
supercat

2
@ supercat : 예, 동의합니다. 동정입니다. 가장 가까운 접근 방식은 아마도 POSIX getline()와 relative getdelim()이며, 명령으로 읽은 '줄'의 길이를 반환하고 전체 줄을 저장하는 데 필요한 공간을 할당합니다. 심지어 기가 바이트 크기의 단일 행 JSON 파일로 끝나는 경우에도 문제가 발생할 수 있습니다. 모든 기억을 감당할 수 있습니까? (그리고 우리가 그것을하는 동안, 우리는 끝에 널 바이트에 대한 포인터를 반환 할 수 strcpy()있고 strcat()변형 할 수 있습니까? 등)
Jonathan Leffler

4
@ supercat : 다른 문제 fgets()는 파일에 null 바이트가 포함되어 있으면 null 바이트 이후 줄 끝까지 (또는 EOF) 얼마나 많은 데이터를 알 수 없다는 것입니다. strlen()데이터에서 널 바이트까지만보고 할 수 있습니다. 그 후, 그것은 추측 일이므로 거의 확실하게 잘못되었습니다.
Jonathan Leffler

7
" gets()존재하는 소리를 잊어 버리세요 ." 이 작업을 수행하면 다시 실행되어 여기로 돌아옵니다. upvotes를 얻기 위해 stackoverflow를 해킹하고 있습니까?
candied_orange

21

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

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

gets(array1);

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

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


3
gets완전히 사용할 수 없게 만드는 것은 배열 길이 / 카운트 매개 변수가 없다는 것입니다. 거기에 있었다면 또 다른 일반적인 C 표준 함수 일 것입니다.
legends2k

@ legends2k : 의도 된 사용법이 무엇인지 궁금 gets하고 개행을 입력의 일부로 원하지 않는 유스 케이스에 편리한 표준 fgets 변형이없는 이유는 무엇입니까?
supercat

1
@supercat gets은 이름에서 알 수 있듯이에서 문자열을 가져 오도록 설계 stdin되었지만 크기 매개 변수가 없다는 이론적 근거 는 C의 정신 에서 비롯된 것일 수 있습니다 . 프로그래머를 신뢰하십시오. 이 함수는 C11 에서 제거되었으며 지정된 대체 gets_s는 입력 버퍼의 크기를 사용합니다. fgets그래도 그 부분 에 대한 실마리는 없습니다 .
legends2k

@ legends2k : 내가 이해할 수있는 유일한 맥락 gets은 하드웨어 라인 버퍼링 된 I / O 시스템을 사용하여 물리적으로 특정 길이 이상의 라인을 제출할 수 없었으며 프로그램의 수명이 길었을 때입니다. 하드웨어 수명보다 짧았습니다. 이 경우 하드웨어가 127 바이트 이상의 행을 제출할 수 없다면 gets128 바이트 버퍼로 정당화 될 수 있지만 작은 입력을 기대할 때 더 짧은 버퍼를 지정할 수 있다는 이점은 비용.
supercat

@ legends2k : 실제로 이상적인 것은 "문자열 포인터"가 몇 가지 다른 문자열 / 버퍼 / 버퍼 정보 형식 중에서 선택할 바이트를 식별하는 것이며, 접두사 바이트 값 하나는 포함 된 구조체를 나타냅니다. 접두사 바이트 [더하기 패딩], 버퍼 크기, 사용 된 크기 및 실제 텍스트의 주소. 이러한 패턴은 코드가 아무것도 복사하지 않고도 다른 문자열의 임의의 문자열 (그냥 꼬리를) 전달하는 것이 가능 할 것, 그리고 같은 방법 수있는 것이다 getsstrcat안전에이만큼 맞습니다으로 받아들입니다.
supercat

16

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

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


실제로, 원래의 C99 표준은 표준 gets()이 정의 된 7.19.7.7 섹션 또는 섹션 7.26.9 향후 라이브러리 지시 사항 및에 대한 하위 섹션에서 명시 적으로 폐기되지 않았습니다 <stdio.h>. 위험에 대한 각주조차 없습니다. ( Yu Hao답변 에 "ISO / IEC 9899 : 1999 / Cor.3 : 2007 (E)에서 더 이상 사용되지 않음"이라고 표시되어 있습니다.) 그러나 C11은 표준에서 제거했습니다.
Jonathan Leffler

int getLine (char *prmpt, char *buff, size_t sz) { ... if (fgets (buff, sz, stdin) == NULL)는 숨 깁니다 size_t으로 int의 전환 sz. sz > INT_MAX || sz < 2의 이상한 값을 잡을 것입니다 sz.
chux-복원 Monica Monica

if (buff[strlen(buff)-1] != '\n') {악의적 인 사용자의 첫 번째 문자 입력은 내장 된 널 문자 렌더링 buff[strlen(buff)-1]UB 일 수 있으므로 해커 악용 입니다. while (((ch = getchar())...사용자가 널 문자를 입력해야하는 경우 문제가 있습니다.
chux-복원 Monica Monica


6

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

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

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


4

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

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


3
표준에서 제거되는 것이 우수합니다. 그러나 대부분의 구현은 이전 버전과의 호환성으로 인해 향후 20 년 동안 '현재 비표준 확장'으로 제공 할 것입니다.
Jonathan Leffler

1
그렇습니다. 그러나 gcc -std=c2012 -pedantic ...gets ()로 컴파일하면 얻을 수 없습니다. (방금 -std매개 변수를 만들었습니다 )
pmg

4

C11 (ISO / IEC 9899 : 201x)에서 gets()제거되었습니다. (ISO / IEC 9899 : 1999 / Cor.3 : 2007 (E)에서 사용되지 않습니다.)

이외에도 fgets()C11은 새로운 안전한 대안을 소개합니다 gets_s().

C11 K.3.5.4.1 gets_s기능

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

그러나 권장 연습 섹션에서 fgets()여전히 선호됩니다.

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


3

gets()프롬프트에 너무 많이 입력하여 사용자가 프로그램을 중단시킬 수 있기 때문에 위험합니다. 사용 가능한 메모리의 끝을 감지 할 수 없으므로 목적에 비해 너무 적은 양의 메모리를 할당하면 세그먼트 오류 및 충돌이 발생할 수 있습니다. 때때로 사용자가 사람의 이름을 나타내는 프롬프트에 1000자를 입력하지는 않을 것 같지만 프로그래머는 프로그램을 방탄으로 만들어야합니다. (사용자가 너무 많은 데이터를 보내 시스템 프로그램을 중단시킬 수있는 경우 보안 위험이있을 수 있습니다).

fgets() 표준 입력 버퍼에서 몇 개의 문자를 가져 와서 변수를 오버런하지 않도록 지정할 수 있습니다.


실제 위험은 프로그램 을 중단 시킬 수는 없지만 임의의 코드를 실행할 수 있다는 것 입니다. (일반적으로 정의되지 않은 동작 활용 )
Tanz87

2

gets" 아직도 라이브러리에 의존하고있는 경우를 위해"라이브러리에 여전히 포함 되어 있는 C 라이브러리 관리자에게 진지한 초대를하고 싶습니다 .

char *gets(char *str)
{
    strcpy(str, "Never use gets!");
    return str;
}

이것은 아무도 그것에 의존하지 않는지 확인하는 데 도움이됩니다. 감사합니다.


2

C 함수는 위험하며 매우 비용이 많이 드는 실수입니다. Tony Hoare는 그의 연설 "Null References : Billion Dollar Mistake"에서 구체적으로 언급하기 위해 그것을 인용합니다.

http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

한 시간 내내 볼 가치가 있지만 그의 의견에 대해서는 30 분부터 특정 39 분에 대한 비평을 얻습니다.

잘하면 이것이 전체 대화에 대한 당신의 식욕을 자극 할 것입니다. 이것은 우리가 언어로보다 공식적인 정확성 증명이 필요한 방법과 언어 디자이너가 프로그래머가 아닌 언어로 인한 실수에 대해 비난받는 방법에주의를 기울입니다. 이것은 나쁜 언어의 설계자들이 프로그래머의 책임을 '프로그래머의 자유'라는 시각으로 프로그래머에게 촉구해야하는 전체적인 모호한 이유 인 것 같습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.