반응형


C언어로 작성된 라우팅 프로토콜에서, 디버그를 목적으로 라우팅 테이블을 문자열(char* )로 출력하도록 했는데, 어느 순간 아래와 같은 buffer overflow 메세지와 함께 프로세스가 중단되었다.


18:58:47.109 AodvUseraLogRtTable: 

        dest            next     dst_seq  ifidx  #nextHops

----------------------------------------------------------------    (여기까지 문자열을 출력하는 중이었음)

*** buffer overflow detected ***: ./aodvd terminated

======= Backtrace: =========

/lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x50)[0x1f5df0]

/lib/i386-linux-gnu/libc.so.6(+0xe4cca)[0x1f4cca]

/lib/i386-linux-gnu/libc.so.6(+0xe43c8)[0x1f43c8]

/lib/i386-linux-gnu/libc.so.6(_IO_default_xsputn+0x95)[0x1797e5]

/lib/i386-linux-gnu/libc.so.6(_IO_vfprintf+0x2b06)[0x14fc66]

/lib/i386-linux-gnu/libc.so.6(__vsprintf_chk+0xad)[0x1f447d]

./aodvd[0x804c395]

./aodvd[0x805228a]

./aodvd[0x80596a1]

./aodvd[0x804dff8]

./aodvd[0x804e3ab]

./aodvd[0x804b179]

/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xe7)[0x126e37]

./aodvd[0x80490d1]

======= Memory map: ========

00110000-0026a000 r-xp 00000000 08:01 1570625    /lib/i386-linux-gnu/libc-2.13.so

0026a000-0026b000 ---p 0015a000 08:01 1570625    /lib/i386-linux-gnu/libc-2.13.so

0026b000-0026d000 r--p 0015a000 08:01 1570625    /lib/i386-linux-gnu/libc-2.13.so

0026d000-0026e000 rw-p 0015c000 08:01 1570625    /lib/i386-linux-gnu/libc-2.13.so

0026e000-00271000 rw-p 00000000 00:00 0 

00271000-0028b000 r-xp 00000000 08:01 1570653    /lib/i386-linux-gnu/libgcc_s.so.1

0028b000-0028c000 r--p 00019000 08:01 1570653    /lib/i386-linux-gnu/libgcc_s.so.1

0028c000-0028d000 rw-p 0001a000 08:01 1570653    /lib/i386-linux-gnu/libgcc_s.so.1

004df000-004fb000 r-xp 00000000 08:01 1570612    /lib/i386-linux-gnu/ld-2.13.so

004fb000-004fc000 r--p 0001b000 08:01 1570612    /lib/i386-linux-gnu/ld-2.13.so

004fc000-004fd000 rw-p 0001c000 08:01 1570612    /lib/i386-linux-gnu/ld-2.13.so

00661000-00662000 r-xp 00000000 00:00 0          [vdso]

08048000-0805e000 r-xp 00000000 08:01 1190565    /home/shan/exp/routing/aodv-uu-usera/aodvd

0805e000-0805f000 r--p 00015000 08:01 1190565    /home/shan/exp/routing/aodv-uu-usera/aodvd

0805f000-08060000 rw-p 00016000 08:01 1190565    /home/shan/exp/routing/aodv-uu-usera/aodvd

08060000-08061000 rw-p 00000000 00:00 0 

09aec000-09b0d000 rw-p 00000000 00:00 0          [heap]

b7846000-b7847000 rw-p 00000000 00:00 0 

b7856000-b7859000 rw-p 00000000 00:00 0 

bfe42000-bfe63000 rw-p 00000000 00:00 0          [stack]

$

--> 내용 수정
  다시 확인한 결과, 위의 출력 결과는 strcat에서 buffer overflow가 발생하기 전에, 다른 debug 함수에서 sprintf 함수에 쓰이는 문자열 버퍼의 크기가 제한되어 있어서 발생한 것이며, strcat으로 인한 출력 결과가 아니었다. 하지만 strcat을 수행할 경우에도 이와 비슷한 형태의 에러가 출력되므로, 단지 형태를 참고하는 정도로만 확인하면 좋겠다.


문제가 발생한 해당 함수(AodvUseraLogRtTable이라는 개인적으로 만든 함수)에서는 strcat을 쓰고 있었고, 정해진 크기의 문자열에 for문으로 임의의 갯수만큼 다른 문자열을 한 줄씩 추가하는 형태였다.

char buf[2000] = {0};


for ( i = 0 ; i < RT_TABLESIZE ; i++ )

{

    char* temp = ~~~~~~~~~~; // temp에 자동으로 입력되는 문자열

    strcat ( buf, temp );

}


라우팅 테이블에 4줄까지는 괜찮다가 5줄짜리를 출력할 때 문제가 생긴 것으로 보면 한 줄에 대략 200바이트 내외의 길이가 추가되면서 buf의 크기를 넘어서면서 문제가 생겼을 것이다. 이런 접근을 허용하게 되면, heap 영역에서 프로세스마다(또는 변수마다) 활용하는 메모리 영역을 넘어서는 부분을 수정하게 되므로, 다른 프로세스(또는 다른 변수)에서 예상치 못한 결과를 얻게 되는 문제가 있다.




C언어에서 다양한 방법이 가능하겠지만, 가장 간단하게는 두 가지의 해결 방향이 있을 것 같다.


(1) temp의 사이즈를 미리 지정하고, if문을 이용해서 붙이는 대상(buf)의 크기를 넘어가지 않도록 제어하기

  위의 경우에는 temp* 문자열의 크기가 어떻게 되는지 알 수 없다. 문자열을 생성하는 코드에 따라서 아주 길 수도 이쏙 짧을 수도 있다. 아예 처음부터 temp 문자열을 일정한 크기로 만들고, 형태(format)를 잘 정의해서 규모 있게 운용하면 예상 외의 buffer overflow는 줄일 수 있을 것이다.

char buf[2000] = {0};

char temp[100] = {0}; // temp의 크기를 강제 지정

int buf_pos = 0;


for ( i = 0 ; i < RT_TABLESIZE ; i++ )

{

    memset( temp, 0, 100);

    sprintf( temp, "~~~~~~", ... ); // temp에 입력시 format을 미리 정해서 크기를 일정하게

    if( buf_pos + 100 < 2000 )

    {

        // buf에 여유 공간이 있을 때만 strcat 수행

        strcat ( buf, temp ); 

        buf_pos += 100;

    }

}



(2) strncat을 이용해서 temp의 맨 처음으로부터 일정 크기의 문자열만 붙이기 (+ if문 활용해서 buf 크기 넘어가지 않도록 제어)

  temp 문자열의 크기를 미리 정할 수 없지만 앞의 일부분만 가져다 쓰면 되는 경우에는, strncat 함수를 이용해서 붙여넣을 temp 문자열 중에서 정해진 크기만큼만 buf에 붙여지도록 지정할 수 있다.

만약 100바이트씩만 붙인다면, " strncat ( buf, temp, 100); "이 될 것이다.

char buf[2000] = {0};

int buf_pos = 0;


for ( i = 0 ; i < RT_TABLESIZE ; i++ )

{

    char* temp = ~~~~~~~~~~; // temp에 자동으로 입력되는 문자열

    if( buf_pos + 100 < 2000 )

    {

        strncat ( buf, temp, 100 ); // temp의 맨 앞에서부터 100바이트 길이만 buf에 붙이기

        buf_pos += 100;

    }

}



반응형
블로그 이미지

Bryan_

,
반응형

*테스트 환경: 리눅스 (Ubuntu 11.04 이상),  gcc version 4.5.2 이하


C 프로그래밍을 하면서 유난히 혼동하기 쉬운 데이터 타입이 있다면 character array (char [])와 character pointer (char*)일 것 같다. 특히 함수에서 call by reference를 쓰면서 초기화를 해야 하는지, 메모리 할당을 해야 하는지에 대한 확신 없이 어림짐작으로 구현하다 보면 잘못된 메모리 접근으로 인해 Segmentation Fault를 심심찮게 보기도 한다.


사실 교과서에서 배운 바에 의하면 char array는 이미 메모리에 일정 크기가 할당된 변수이고, 해당 char array의 첫번째 인덱스가 char pointer이다. 코드상에서 char var[100]; 이라고 쓰면 100 바이트의 문자열이 생성되는 반면, char* var; 라고만 쓰면 데이터를 기록할 메모리 영역은 없고 포인터만 존재하게 된다.


본 글에서는 call by reference로 넘기는 문자열이 메모리가 할당된 char array여야 하는 경우와, 메모리를 할당할 필요가 없는 char pointer여도 되는 경우의 차이를 기록하였다. 여기에 쓰인 형태 외에도 문자열을 call by reference로 처리하는 다양한 방법이 존재할 수 있으며, 단지 여기서는 경험을 기반으로 한 기록임을 명시해 둔다.


먼저 파라미터로 메모리를 할당할 필요 없이 그냥 char pointer를 써도 되는 경우는, 함수 내부에서 새로운 문자열을 생성하고 해당 문자열이 local variable(지역변수)이 아닌 경우이다. 예를 들어, 아래와 같이 strtok 함수가 쓰이는 경우에는 char pointer만 있으면 된다.

void funcA(char* input, char** output){

    *output = strtok(input, " ");

}


int main(){

    char* split;

    funcA("11111 22222", &split);

    printf("%s\n", split);


    return 0;

}

(출력)

11111


strtok의 경우에는 별도로 메모리를 할당할 필요가 없어서 위와 같이 포인터만 지정해 주면 call by reference로 값을 얻을 수 있다. 이외에도 더 있겠지만 따로 조사하지 않았기에 추후에 내용을 보완하고자 한다.


위와 반대로, 함수 외부에서 char array를 생성/초기화하고 나서 해당 변수의 포인터를 함수에 넘겨줘야 하는 경우는 아래와 같다. 여기서 sprintf 함수는 자체적으로 새로운 문자열을 생성하지 않기 때문에 미리 정의된 문자열(char array)을 써야 한다. 이외에 비슷한 예로 strcpy, strcat 등이 있다.

void funcB(char* output){

    char* a = "11111";

    char* b = "22222";

    sprintf(output, "%s, %s", a, b);

}


int main(){

    char merged[10];

    memset(merged, 0, 10); // 배열값 초기화


    funcB(merged);

    printf("%s\n", merged);


    return 0;

}

(출력)

11111, 22222



반응형
블로그 이미지

Bryan_

,
반응형
#pragma는 컴파일할 때 지정해 주는 사항들에 대한 표현이다.
라이브러리를 첨부하거나 컴파일 하는 동안 보여주는 경고 일부를 무시하는 등의 설정을 할 수 있다.

#pragma comment(lib, "ws2_32.lib")

이것은 ws2_32.lib 파일을 컴파일 시에 추가하겠다는 의미이다.
VS .NET이나 Visual C++ .NET을 쓸 때 프로젝트 속성 창을 통해 설정해줄 수도 있는데,
솔루션이름에서 마우스 오른쪽 단추 - 속성 -> 구성 속성 - 링커 - 입력 에 온 뒤
오른쪽 창의 '추가 종속성' 부분에 ws2_32.lib파일을 적어 주는 것과 같다.

.NET에서 이런 설정을 해 주지 않으면 include가 제대로 되어 있어도
'symbol' 외부 기호('function' 함수에서 참조)를 확인하지 못했습니다.
와 같은 링크 에러(LNK2019)를 발생시킨다.
반응형
블로그 이미지

Bryan_

,