Secure Coding in C - string, memory

markyang92·2021년 6월 20일
0

Secure Coding in C

목록 보기
5/7
post-thumbnail

string

  • 문자열char 사용
    • char, signed char, unsigned char를 문자 타입이라고 한다.
    • 컴파일러는 charsigned char, unsigned char와 동일한 범위와 표기를 갖도록 정의한다.
      • 하지만 둘 중 하나와 같다고 해도 char는 이 둘과 분리된 타입이며, 서로 호환도 되지 않는다.
  • 따라서 표준 문자열 처리 함수들과 호환성을 위해 문자 데이터는 char 사용

stdin 입력

{,f,v}scanf 피할 것

  • scanf, fscanf, vscanf 함수는 stdin, 다른 입력 스트림으로 부터 문자열 데이터를 읽는다.
    • 이 함수들은 유효 정수에서는 잘 동작하지만, 유효하지 않은 값에 대해서는 신뢰성 처리 XX

1. 부적절한 코드

  • 정수나 부동소수점 수를 입력 받기 위해 scanf, fscanf 함수를 사용하는 경우, unexpected behaviour 가능성

fgets 사용

  • scanf류를 피하고 **fgets()를 사용하자.
  • NULL을 찾을 때, 문자열에선 \0을 사용하자.
    • NULL은 진짜 주소가 없는 NULL
    • \0NULL 문자

fgets -> strtol 사용

  1. 문자열 입력fgets 함수를 사용하라
  2. 문자열 -> 정수 변환strtol함수를 사용할 것
    • strtol함수는 입력 문자열이 long 에서 유효한지 점검하는 에러 체크 제공

strtol: long int 반환
strtoll: long long int 반환
strtoul: unsigned long int 반환
strtoull: unsigned long long int 반환


  • atoi 류 함수는 문제가 많으므로 사용 지양
    • 에러 발생 시 errno를 설정하지 않는다.
    • 표시할 수 없는 결과 값을 얻은 경우 정의되지 않은 행동 유발
    • 문자열에 정수 값이 없는 경우 0을 반환하기 때문에, 0이 입력되어 정상적으로 해석된 값인지 그렇지 않은지 확인할 수 없음

fgets 반환은 int

  • fgets(), getc(), getchar() 같은 문자열 입출력 함수는 모두 스트림으로부터 문자를 읽어 int를 반환한다.
  • char 로 반환 시, EOF를 구분 할 수 없다.

길이 명시 강제

  • 신뢰할 수 없는 소스로부터 얻어지는 정수 값은 식별할 수 있는 상/하한 값이 있는지를 확인하기 위해 반드시 평가되어야 한다.
  • 아래와 같이 argument에 의해 메모리 할당되는 함수는 MAX_STRING_LEN을 강제한다.

NULL 방지 + '\0' 미종료 방지

  • 버퍼 오버플로 취약성을 완화하기 위해 일반적으로 복사되는 바이트의 수를 제한하는 대체 함수 사용
    • strncpy
    • strncat
    • fgets
    • snprintf
  • 하지만 이러한 함수들은 지정된 제한을 넘는 문자열을 잘라버린다. (주의해서 사용할 것)
    • '\0' 문자 종료 안될 수도 있음
    • 의도하지 않는 잘림은 데이터의 손실과 더불어 SW 취약성 원인

NULL'\0'은 다르다!

  • NULL: 말그대로 주소가 없는 것
    • 문자열 메모리 할당이 안되면 NULL을 받음
  • '\0': 널 문자, 문자 있는데 널 문자인 것임!
    • 문자열 끝에 \0을 찾아야지 NULL을 찾지말 것!

1. 해결 방법

  • 데이터의 손실이나 NULL 문자 종료하지 않는 것에 대한 대비


'\0' 문자로 끝내기 보장하기

  • 데이터를 모두 담을 만큼 충분히 크지 않은 버퍼에 복사하면 버퍼 오버플로우가 발생한다.
  • dst에 추가되는 '\0'문자 종료를 고려해 루프 종료 조건 수정
  • 복사되는 공간의 크기가 적절하게 생성될 수 있도록 동적 메모리 할당사용

  • 문자열을 NULL로 종료되도록 한다.


strtok() 토큰 파싱

  • strtok() 토큰 파싱은 문자열 복사본 만들어 사용
  • strtok()는 호출되면 문자열 내의 구분자가 처음 나타나는 부분까지 파싱 -> 구분자를 NULL로 바꿈 -> 토큰의 처음 주소 반환
    이후, 다시 strtok()함수를 호출하면 가장 최근에 NULL로 바뀐 부분부터 파싱이 시작된다.
  • 때문에, strtok()인자를 수정하므로 원본 문자열은 안전 X, 원본 문자열을 보존하고 싶다면 문자열 복사본 만들어 사용

1. 부적절한 코드

  • strtok 함수를 잘못 사용하는 경우, 데이터 잘립으로 인해 의도치 않은 결과 나타날 수 있다.



1. 해결 방법

  • 문자열을 복사해 사용하라



strchr()\n 개행 없애자!

  • strchr()함수는 아래와 같다.
    • 문자열내 일치하는 문자가 있는지 검사하는 함수
argdescription
char* str검색할 문자열
int c존재하는지 확인할 문자(아스키 값)

returndescription
!NULL ptr문자가 존재하는 포인터
NULL해당 문자가 존재하지 않음
char str[]="BlockDMask";
char* ptr=strchr(str, 'M'); // search 'M'
if(ptr != NULL){
    printf("%c %d", *ptr, ptr);
}

fgets 함수 실패 시 문자열 리셋

  • fgets()함수가 실패하면 전달된 배열의 내용은 정의되지 않는 상태가 되므로 주의할 것
  • fgets() 함수 실패 후, 배열을 사용하면 코드가 정상적으로 동작하지 않을 수 있다.
    • fgets() 함수 Error Handling Code 필수
    • dst buffer에 '\0'을 그냥 박는다.

memory

메모리 해제 후, 즉시 포인터에 새로운 값 저장하라

  • 댕글링 포인터(danggling pointer): free된 메모리 참조
  • 댕글링 포인터(dangling pointer)는 중복 해제나 해제된 메모리를 액세스하는 취약성 있다.
    • 댕글링 포인터를 비롯한 메모리 관련 취약점을 제거하는 간단하면서도 효과적인 방법
    1. 포인터 해제
    2. 다른 유효 객체 참조 || NULL 값 할당


재활용 시, memset

다음의 메모리는 재활용 시 민감한 정보가 있을 수 있기 때문에, memset 후 재활용한다.

  1. dynamic allocated memory
  2. static allocated memory
  3. 자동 할당된 스택메모리
  4. 메모리 캐시
  5. 디스크 또는 디스크 캐시

할당 시, 크기 0으로 할당하지 말것!

  • C99
    • 메모리 할당 크기 0 요청 시, NULL이 반환되거나 크기가 0이 아닌 경우와 같게 동작할 수 있다.
    • 이 경우 반환된 포인터를 객체에 접근하기 위해 사용해선 안된다.

동적 할당 전 후, 메모리 크기 검사할 것

  • 크기 검사 코드 넣을 것

큰 스택 할당 피하라!

  • 보안에 취약하다.

VLA(가변 길이 배열) 피하라

  • 가변 길이 배열(VLA)는 아주 큰 스택할당을 할수 있기 때문에 피할 것!
  • 동적메모리할당으로 해결한다.

재귀 피하라!

  • 피보나치 수열도 아래와 같이 재귀로 많이 작성하는데..
  • secure code로는 for 문으로 더하는 것이 안전하다.

메모리 누수를 피하라

realloc 주의

1. 부적절한 코드

  • 다음 코드는 메모리 누수가 있다.


restrict로 지정된 src, dst 동일한 객체 참조 금지

  • restrict오직 포인터에서 적용되는 키워드
    • 그 포인터가 데이터 객체에 접근할 수 있는 유일, 최초가 되는 수단
    • 즉, 포인터restrict 로 한정되면 그 포인터가 가리키는 데이터 블록은 그 포인터만 접근 가능
    • 컴파일러가 포인터도 최적화
    • 포인터들이 동일한 객체를 참조하지 말 것

memcpyrestrict 지정자를 사용 금지

  • memcpyrestrict 지정자를 사용한 함수로 동일한 객체 주소 인자 전달 시, unexpected behaviour, 결과 알 수 없음

memmove 사용할 것


캐싱 데이터 volatile 금지

  • 일반적으로 CPU는 데이터메모리에서 직접 읽어오기를 꺼려한다.
    • 캐시 사용
  • 프로그램이 아닌, HW에 의해 변경되는 데이터는 캐시에 반영되지 않음
    • 메모리에서 직접 읽어올 것
  • volatile은 데이터가 프로그램이 아닌 외부적 요인에 의해 변경될 수 있다고 컴파일러에게 캐싱 제한할 때 사용(최적화 금지)

1. 부적절한 코드

  • 최적화에 의해 flag가 캐시되었다면 SIGINT를 받아도 종료되지 않는다.

1. 해결 방법

  • volatile로 변수 지정
profile
pllpokko@alumni.kaist.ac.kr

0개의 댓글