본문 바로가기

CERT C/표현식

[CERT C/표현식] (3) 포인터 연산 수행의 중요성

의미

포인터 연산은 주소에 대해서 연산을 하는 것을 의미하는데, 포인터에 더해지는 값은 자동적으로 포인터가 가리키는 데이터형으로 조정된다.

동작 방식

포인터 연산의 특징

 

(1) 주소상수 + 정수형 상수(n)

 

-> 주소 상수 + (n * 주소상수에 해당하는 기억공간의 크기)

 

(2) 주소상수 - 주소상수

 

-> 주소에 해당하는 기억공간 간의 첨자차이가 된다.

문제 코드 1. 단축 평가 방식을 신경써서 AND, OR 연산자를 사용하자.

출력을 예상해보기

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

#define BUFFER_SIZE 5
int getNumber(int *);

int main()
{
    int p;
    int buffer[BUFFER_SIZE];
    int *bufptr = buffer;

    while(bufptr <  (buffer + sizeof(buffer)))
    {
        if(getNumber(&p))
        {
            *bufptr++ =  p;
        }
    }

    for(int i = 0; i < BUFFER_SIZE; i++)
    {
        printf("buffer[%d] : %d\n", i, *(buffer + i));
    }
    return 0;
}

int getNumber(int *ptr)
{
    char tmp[10];
    char *nr, *br;

    *ptr = 0;
    memset(tmp, 0, sizeof(tmp));
    printf("정수 입력 : ");
    while(1)
    {
        if(fgets(tmp, sizeof(tmp), stdin) == NULL)
        {
            return 0;
        }

        /* 예외 처리 */

        // fgets로 받은 문자에 반드시 개행문자가 있다고 가정하지 않기
        nr = strchr(tmp, '\n');
        if(nr != NULL) *nr = '\0';
        else fflush(stdin);

        if(strlen(tmp) == 0 || strlen(tmp) > 3)
        {
            return 0;
        }

        // 문자가 없거나 중간에 숫자가 아닌 문자가 존재하는지 확인하기
        *ptr = (int)strtol(tmp, &br, 10);
        if(br == tmp || *br != '\0')
        {
            return 0;
        }
        break;
    }
    return 1;
}

출력 : 

정수 입력을 5번이 아닌 20번을 받아야 프로그램이 종료되게 된다. 그 이유가 무엇일까?

취약점 분석 및 해결 코드 1

분석 

 

(1) 포인터 연산의 특징 1번이 그 이유이다. 주소상수 + 정수형 상수(n) = 주소상수 + (주소상수에 해당하는 기억공간의 크기 * n)이 된다.

-> 즉, 위 코드에서 while(bufptr <  (buffer + sizeof(buffer))) 이 부분에서 buffer + sizeof(buffer)은 buffer + (20)이 되고 buffer + (sizeof(int) * 20)이 되면서 원하는 버퍼사이즈인 5번이 아닌 20번을 반복하게 된다.

 

아래 간단하게 수정된 코드를 보고 확실히 짚고 넘어가도록 하자.

 

해결 방법 : 포인터 연산을 실행할 때 포인터 연산 특징을 염두해 두자. ( 굉장히 많이 사용되기 때문에 특히 더 신경쓰자 )

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

#define BUFFER_SIZE 5
int getNumber(int *);

int main()
{
    int p;
    int buffer[BUFFER_SIZE];
    int *bufptr = buffer;

    while(bufptr <  (buffer + BUFFER_SIZE))
    {
        if(getNumber(&p))
        {
            *bufptr++ =  p;
        }
    }

    for(int i = 0; i < BUFFER_SIZE; i++)
    {
        printf("buffer[%d] : %d\n", i, *(buffer + i));
    }
    return 0;
}

int getNumber(int *ptr)
{
    char tmp[10];
    char *nr, *br;

    *ptr = 0;
    memset(tmp, 0, sizeof(tmp));
    printf("정수 입력 : ");
    while(1)
    {
        if(fgets(tmp, sizeof(tmp), stdin) == NULL)
        {
            return 0;
        }

        /* 예외 처리 */

        // fgets로 받은 문자에 반드시 개행문자가 있다고 가정하지 않기
        nr = strchr(tmp, '\n');
        if(nr != NULL) *nr = '\0';
        else fflush(stdin);

        if(strlen(tmp) == 0 || strlen(tmp) > 3)
        {
            return 0;
        }

        // 문자가 없거나 중간에 숫자가 아닌 문자가 존재하는지 확인하기
        *ptr = (int)strtol(tmp, &br, 10);
        if(br == tmp || *br != '\0')
        {
            return 0;
        }
        break;
    }
    return 1;
}

POINT

  • 주소상수 + 정수형 상수(n)  :  주소 상수 + (n * 주소상수에 해당하는 기억공간의 크기)
  • 주소상수 - 주소상수  :  주소에 해당하는 기억공간 간의 첨자차이가 된다.

참조

 


 

EXP08-C. Ensure pointer arithmetic is used correctly

wiki.sei.cmu.edu/confluence/display/c/EXP08-C.+Ensure+pointer+arithmetic+is+used+correctly