C언어 gets함수를 사용해서는 안되는 이유

visual studio c/c++, gcc,c++ C/C++언어

꽤 오래된 과거에 Linux 운영 체제에서 c언어로 작성된 프로그램을 대상으로 유지/보수를 위해 수정 및 컴파일을 하는 과정에서 다음과 같은 경고가 발생할 수 있습니다. 대부분은 경고라는 이유로 별로 대수롭지 않게 생각할 수 있습니다 만 외부에서 접근하여 실행할 수 있는 프로그램일 경우 gets함수는 시스템에 매우 위험한 영향을 끼칠 수 있습니다. 이 글에서는 gets함수 사용의 위험성을 이해하고 수정 방법에 대해 소개하겠습니다.

경고: the ‘gets‘ function is dangerous and should not be used.

컴파일 및 실행 환경

이 글에서 작성한 샘플 소스 , 컴파일 및 실행 환경은 다음과 같습니다.

$ cat /etc/redhat-release
CentOS Linux release 7.2.1511 (Core)
$ uname -r
3.10.0-693.5.2.el7.x86_64

gets함수 샘플 소스 작성하기

다음과 같이 c소스를 작성하여 test.c 이름으로 저장합니다.

#include  <stdio.h>

int main()
{
  char strName[20];
  printf("YourName>");
  gets(strName);
  printf("Answer: %s\n",strName);
  return 0;
}

다음과 같이 gcc명령으로 컴파일해서 실행 파일을 만듭니다.

$ gcc -g -w -o test test.c
/tmp/cc42My4v.o: In function `main':
test.c:(.text+0x1f): warning: the `gets' function is dangerous and should not be used.

$ chmod 775 test
$ ls test*
-rwxr-xr-x. 1 centos wheel 8562 Jun 19 12:31 test
-rw-r--r--. 1 centos wheel  149 Jun 19 12:31 test.c

gets함수 샘플 소스 실행하기

test는 test.c를 컴파일 해서 만든 실행 파일입니다. 다음과 같이 “./test“명령으로 실행합니다.

$ ./test
YourName>abcdefghijk
Answer: abcdefghijk

실행 결과의 소스 각 행에 대한 설명은 다음과 같습니다.

  1. char strName[20];은 char형 메모리를 배열로 20개 준비한 메모리를 가리킵니다.
  2. printf(“YourName>”);가 실행되면 고정 문자열 “YourName>”을 화면에 출력합니다.
  3. gets(strName);가 실행되면 화면 입력으로 키 입력을 기다립니다. 키 입력은 <Enter> 키를 누를 때 까지 무한 반복됩니다.
  4. 여기에서는 11개 문자로 구성된 문자열 “abcdefghijk”을 입력 후 <Enter> 키를 눌렀습니다.
  5. char strName[20];가 선언 되었을 때 char영역을 배열로 20개 준비 했습니다 만 gets함수로 11개 문자가 설정 되었습니다.
  6. printf(“Answer: %s\n”,strName);가 실행되면 고정 문자열 “Answer:”와 함께 strName의 내용을 출력합니다. printf함수는 지정된 strNam 메모리의 처음부터 순서대로 ‘\0’문자가 발견될 때까지 모든 내용을 화면에 출력합니다.

gets함수 에러 발생 시키기

다음과 같이 strName[20]를 넘는 문자를 입력하면 다음과 같이 “Segmentation fault (core dumped)“가 발생합니다.

$ ./test
YourName>jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
Answer: jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
Segmentation fault (core dumped)

이 문제는 gets함수가 strName[20]의 크기를 모르면서 개행을 찾거나 EOF를 만날 때까지 계속 읽기 때문에 주어진 버퍼의 크기를 넘을 수 있습니다. 다르게 설명하자면 C언어는 경계 검사를 수행하지 않아 gets함수가 접근 권한이 없는 주소에 도달 할 때까지 읽기를 계속합니다. 접근 권한이 없는 주소에 도달하는 이런 행위가 아마도 Linux에서는 시스템을 이상 종료 시킬 수 있는 오류 일 것입니다. 그래서 많은 바이러스가 이러한 문제점을 이용합니다. 이러한 이유로 오래전에 gets함수를 표준 함수에서 제거되었으나 이전 버전과의 호환성을 이유로 라이브러리에 오랫동안 현재까지 남아 있으며 개인적인 추측이지만 아래의 대체 함수 대응으로 모든 잔재가 없어질 때까지 남아 있을 것 같습니다.

대체 함수 fgets에 대해서…

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

fgets함수는 개행 문자가 나올 때까지 또는 length-1 크기의 문자를 읽을 때까지 읽고 문자 끝에 ‘\ 0’을 넣습니다. str에 최소한 length크기의 문자 만을 할당 하므로 fgets ()는 안전하게 작동합니다.

fgets함수 샘플 소스 작성하기

다음과 같이 c소스를 작성하여 test2.c 이름으로 저장합니다.

#include  <stdio.h>

int main()
{
  char strName[20];
  printf("YourName>");
  fgets(strName, sizeof(strName), stdin);
  printf("Answer: %s\n",strName);
  return 0;
}

다음과 같이 gcc명령으로 컴파일해서 실행 파일을 만듭니다.

$ gcc -g -w -o test2 test2.c
$ chmod 775 test2
$ ls test2*
-rwxr-xr-x. 1 centos wheel 8562 Jun 19 13:31 test2
-rw-r--r--. 1 centos wheel  149 Jun 19 13:31 test2.c

fgets함수 샘플 소스 실행하기

test2는 test2.c를 컴파일 해서 만든 실행 파일입니다. 다음과 같이 “./test2“명령으로 실행합니다.

$ ./test2
YourName>jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
Answer: jjjjjjjjjjjjjjjjjjj

strName[20]의 크기보다 더 만은 문자열을 입력했지만 fgets(strName, sizeof(strName), stdin);의 실행에서 sizeof(strName)결과가 20이므로 19개의 문자를 strName[20]에 설정하고 나머지에 ‘\0’문자를 할당했습니다.

gets함수는 입력 문자열을 저장할 메모리 영역은 지정받지만 메모리 크기에 대해서는 지정받지 않습니다. fgets함수는 저장할 수 있는 메모리 영역 과 그 크기도 지정받기 때문에 메모리 크기를 넘는 키보드 입력을 무시하여 메모리 참조 오류는 발생하지 않습니다.

제목과 URL을 복사했습니다