기본 콘텐츠로 건너뛰기

라벨이 Cert C인 게시물 표시

Secure-Coding-C-DCL-5

Secure-Coding-C-DCL-5 C 언어 시큐어코딩 - Declare and Initialization 5 올바른 구문을 사용하여 가변 길이 배열 선언 가변 길이 배열은 하나 이상의 멤버가 있는 구조의 마지막 요소가 불완전한 특별한 유형의 배열이다. 즉, 배열의 크기가 구조 내에서 명시적으로 지정되어 있지 않다. 가변 길이 배열 구조 정의 시 몇 가지 제한 사항이 존재한다. 불완전한 배열 유형은 구조 내의 마지막 요소 여야 한다. 가변 길이 배열 멤버가 포함된 구조체 배열은 있을 수 없다. 가변 길이 배열 멤버가 포함된 구조체는 다른 구조체의 멤버로 사용할 수 없다. 구조체에는 가변 길이 배열 멤버 외에 하나 이상의 명명된 멤버가 있어야 한다. 잘못된 코드 예제 C 표준에서 가변 길이 배열을 도입하기 전에 유사한 요소를 얻기 위해 단일 요소 배열을 최종 멤버로 사용하는 구조가 사용되었었다. 예제는 하나의 요소을 가진 배열을 가변 길이 배열과 같이 최종 멤버를 할당하려고 시도한다. 구조체가 인스턴스화되면 malloc() 에 계산된 크기가 동적 배열의 실제 크기를 고려하여 수정된다. # include <stdlib.h> struct flexArrayStruct { int num ; int data [ 1 ] ; } ; void func ( size_t array_size ) { /* Space is allocated for the struct */ struct flexArrayStruct * structP = ( struct flexArrayStruct * ) malloc ( sizeof ( struct flexArrayStruct ) + sizeof ( int ) * ( array_size - 1 ) ) ; if ( structP == NULL ) { /* Handle malloc failure */ }

Secure-Coding-C-DCL-4

Secure-Coding-C-DCL-4 C 언어 시큐어코딩 - Declare and Initialization 4 예약된 식별자를 선언하거나 정의하지 않는다. 밑줄과 대문자 또는 밑줄로 시작하는 모든 식별자는 항상 그 용도가 예약되어 있다. 예약된 식별자를 매크로 이름으로 정의하거나, 예약된 컨텍스트에서 식별자를 선언하거나 정의하지 않는다. 잘못된 코드 예제 (Include Guard) C 표준 라이브러리에 헤더에 구현되어 정의된 이름이 없는 경우에도 컴파일러에 의해 암시적으로 미리 정의된 이름과 충돌할 수 있다. # ifndef _MY_HEADER_H_ # define _MY_HEADER_H_ /* Contents of <my_header.h> */ # endif /* _MY_HEADER_H_ */ 올바른 코드 예제 (Include Guard) 매크로 이름을 밑줄로 시작하지 않는다. # ifndef MY_HEADER_H # define MY_HEADER_H /* Contents of <my_header.h> */ # endif /* MY_HEADER_H */ 잘못된 코드 예제 (File Scope Objects) _max_limit 은 정적이므로 충돌의 영향을 받지 않을 것으로 생각할 수 있으나, size_t를 정의하기 위해 포함된 이름들과 충돌할 가능성이 있으며 컴파일러는 예약된 이름을 암시적으로 선언할 수 있다. _limit 은 런타임 라이브러리에 정의된 동일한 이름의 심볼과 충돌할 수 있다. # include <stddef.h> static const size_t _max_limit = 1024 ; size_t _limit = 100 ; unsigned int getValue ( unsigned int count ) { return count < _limit ? count : _limit

Secure-Coding-C-DCL-3

Secure-Coding-C-DCL-3 C 언어 시큐어코딩 - Declare and Initialization 3 상충되는 Linkage 식별자를 선언하지 않는다. Linkage는 동일한 범위 내에서 여러 영역으로 선언되거나 여러 번 선언된 식별자를 동일한 개체나 함수를 참조하도록 만들 수 있다. 식별자는 externally linked, Internally linked 또는 not llinked로 분류된다. 이 세 종류의 분류는 다음과 같은 특징을 갖는다. External linkage: 프로그램에 속한 모든 편집 단위 및 라이브러리에서 동일한 객체 또는 기능을 나타낸다. 식별자는 링커에서 사용할 수 있다. External Internal linkage: 주어진 번역 단위 내에서 동일한 객체 또는 함수를 나타낸다. linker는 내부 링크가 있는 식별자에 대한 정보가 없다. 따라서 이러한 식별자는 번역 단위 내부에 있다. No linkage: 식별자에 연결이 없는 경우 식별자를 사용하는 추가 선언은 새로운 변수 또는 새로운 유형과 같은 새로운 것을 선언한다. 한 번역 내에서 내부 및 외부 링크로 분류된 식별자의 사용은 정의되지 않은 동작이다. 번역의 단위는 헤더와 함께 소스 파일, 전처리 지시어를 통해 포함된 모든 소스 파일을 포함한다. 다음 표는 단일 변환 단위에서 두 번 선언된 객체에 할당된 연결을 나타낸다. 열은 첫 번째 선언, 행은 재 선언을 의미한다. static (second) no linkage (second) extern (second) static (first) internal Undefined Internal no linkage (first) Undefined No linkage External extern (first) Undefined Undefined External 잘못된 코드 예제 이 예제에서 i2 와 i5

Secure-Coding-C-DCL-2

Secure-Coding-C-DCL-2 C 언어 시큐어코딩 - Declare and Initialization 2 식별자를 사용하기 전에 선언한다. C90 표준은 변수와 함수의 암시적 타이핑을 허용하였으나, C11 표준에는 형식 지정자가 필요하며 암시적 함수 선언을 금지하고 있다. 따라서 기존 레거시 코드 중 일부는 암시적 타이핑을 사용하고 있을 확률이 높다. 일부 컴파일러는 계속해서 지원하고 있지만 이는 호환성을 유지하지 위함이므로 새 코드에서는 사용해서는 안된다. 잘못된 코드 예제 C는 더 이상 선언문에 타입 지정자가 없다는 것을 허용하지 않는다. 이 예제는 형식 지정자를 생략한다. extern foo ; 올바른 코드 예제 이 예제는 형식 지정자가 명시적으로 포함되어 있다. extern int foo ; 잘못된 코드 예제 (암시적 함수 선언) 모든 함수는 호출되기 전에 명시적으로 선언되어야 한다. C90에서 명시적 프로토 타입 없이 함수를 호출하면 컴파일러에서 암시적 선언을 제공했다. 함수 호추이 이루어진 지점에서 표시되지 않으면 C90 호환 플랫폼은 extern int identifier(); 를 암시적으로 선언한다. 이 선언은 함수가 int를 반환할 수 있음을 의미한다. 그러나 현재 C 표준을 따르기 위해서는 프로그래머가 모든 함수를 호출하기전에 명시저긍로 프로토 타입을 작성해야 한다. # include <stddef.h> /* #include <stdlib.h> is missing */ int main ( void ) { for ( size_t i = 0 ; i < 100 ; ++ i ) { /* int malloc() assumed */ char * ptr = ( char * ) malloc ( 0x10000000 ) ; * ptr = 'a' ; } return 0 ;

Secure-Coding-C-DCL-1

Secure-Coding-C-DCL-1 C 언어 시큐어코딩 - Declare and Initialization 1 적절한 저장 기간을 가진 객체 선언 모든 객체는 수명을 결정하는 저장 기간이 있다. : static, thread, automatic, or allocated. 객체 수명은 실행 중 저장 장치에 예약된 기간 동안 보장된다. 객체가 존재하고, 상수 주소를 가지며, 수명 기간 동안 저장된 값을 유지한다. 하지만 수명 기간이 지나고 발생하는 참조의 동작에 대한 정의는 없으며 포인터가 가리키는 객체의 수명이 끝나면 포인터의 값은 불확실해진다. 객체의 수명 기간 지난 후 접근을 시도해서는 안된다. 그러한 시도는 정의되지 않은 동작으로 악용될 수 있는 취약점으로 이어질 수 있다. 잘못된 코드 예제 (다른 저장 기간) 이 코드 예제는 자동 저장 기간을 갖는 변수 c_str 의 주소를 정적 저장 기간을 갖는 변수 p 에 할당한다. 할당 자체는 유효하지만 dont_do_this() 의 끝에서와 같이 p 가 주소를 보유하고 있는 동안에 c_str 이 범위를 벗어나는 것은 유효하지 않다. # include <stdio.h> const char * p ; void dont_do_this ( void ) { const char c_str [ ] = "This will change" ; p = c_str ; /* Dangerous */ } void innocuous ( void ) { printf ( "%s\n" , p ) ; } int main ( void ) { dont_do_this ( ) ; innocuous ( ) ; return 0 ; } 올바른 코드 예제 (동일한 저장 기간) 이 예제에서 p 는 c_str 과 동일한 저장 기간으로 선언되므로 p 는 this_is_OK()

Secure-Coding-C-preprocessor-3

Secure-Coding-C-preprocessor-3 C 언어 시큐어 코딩 - Preprocessor-3 함수 형태 매크로 호출 시 전처리 지시문을 사용하지 않는다. 매크로 인자 값에 #define , #ifdef , #include 와 같은 전처리 지시문을 포함하지 않는다. 이 규칙은 매크로를 사용하여 함수가 구현되었는지 여부를 알 수 없는 함수에 대한 인수에서 전처리 지시문을 사용하는 경우에도 적용된다. 예를 들어, memcpy() , printf() , assert() 같은 표준 라이브러리 함수는 매크로로 구현 될 수 있다. 잘못된 코드 예제 예제(GCC Bug)에서 전처리 지시문을 사용하여 memcpy()에 대한 플랫폼 별 인수를 지정한다. 그러나 memcpy() 함수가 매크로를 사용하여 구현된 경우 코드는 정의되지 않은 동작을 유발한다. # include <string.h> void func ( const char * src ) { /* Validate the source string; calculate size */ char * dest ; /* malloc() destination string */ memcpy ( dest , src , # ifdef PLATFORM1 12 # else 24 # endif ) ; /* ... */ } 올바른 코드 예제 예제에서 memcpy() 함수 호출 방법은 호출 외부에서 결정된다. # include <string.h> void func ( const char * src ) { /* Validate the source string; calculate size */ char * dest ; /* malloc() destination string */ # ifdef PLATFORM1

Secure-Coding-C-preprocessor-2

Secure-Coding-C-preprocessor-2 C 언어 시큐어 코딩 - Preprocessor-2 안전하지 않은 매크로 인자로 인한 부작용 발생을 피한다. 인자 중 하나를 두 번 이상 평가하거나 전혀 평가하지 않는 함수 형태 매크로는 안전하지 않다. 또한 할당, 증가, 감소, 휘발성 액세스, 입출력 등의 부작용을 유발할 수 있는 함수 호출을 포함하는 매크로는 호출하지 않는다. 함수 형태 매크로 사용 시 전달 인자로 인한 부작용은 사용 방법에 대한 위험 요소이므로 그 책임은 매크로를 사용한 프로그래머에게 있다. 하지만 이를 해결하기 위한 좋은 방법은 안전하지 않은 함수 형태의 매크로 작성을 하지 않는 것이다. 잘못된 코드 예제 - 1 매크로 인자로 인한 부작용을 보여주는 잘못된 코드 예제: # define ABS(x) (((x) < 0) ? -(x) : (x)) void func ( int n ) { /* Validate that n is within the desired range */ int m = ABS ( ++ n ) ; /* ... */ } 이 예제에서 ABS() 매크로를 호출하면 다음과 같이 확장된다. m = ( ( ( ++ n ) < 0 ) ? - ( ++ n ) : ( ++ n ) ) ; n을 두 번 증가시키는 것을 확인할 수 있다. 올바른 코드 예제 - 1.1 n의 증가 연산을 매크로 호출 전 수행하는 올바른 코드 예제: # define ABS(x) (((x) < 0) ? -(x) : (x)) /* UNSAFE */ void func ( int n ) { /* Validate that n is within the desired range */ ++ n ; int m = ABS ( n ) ; /* ... */ } 추가로 ABS_UNSAFE()로 매크로 이름을 변경

Secure-Coding-C-preprocessor-1

C 언어 시큐어코딩 - Preprocessor 1 Universal character name을 연결을 통해서 생성하지 않는다. C 에서는 식별자, 문자 상수 및 문자열 리터럴에서 기본 문자 집합에 없는 universal character names의 사용을 지원한다. Universal character name \Unnnnnnnn 은 8 자리의 문자 nnnnnnnn 로 나타낸다. 유사하게 \Unnnn universal character name은 nnnn ( 0000nnnn )으로 나타낸다. 리터럴 값 그 자체로, 고정된 값을 의미한다. 예를 들어 const int b = 8; 에서 b는 상수이고 8은 리터럴이다. The C Standard, 5.1.1.2, paragraph 4 에 따르면 토큰 연결을 통해 생성한 universal character name의 동작은 정의되지 않는 것 으로 서술되어 있다. 이에 반드시 필요한 경우를 제외하고는 universal character names를 통한 식별자 정의를 피한다. 잘못된 코드 예제 #define assign(uc1, uc2, val) uc1##uc2 = val void func( void ) { int \u0401; /*...*/ assign(\u04, 01 , 4 ); /*...*/ } 상세 정보 이 코드는 Microsoft Visual Studio 2013을 사용하여 컴파일 및 실행되며 예상대로 변수에 4가 할당된다. Linux에서 GCC 4.8.1은 이 코드를 컴파일하는 것을 거부한다. universal character를 참조하는 assign 매크로에서 ‘프로그램 이탈’을 반환한다. 올바른 코드 예제 Universal character name를 사용하지만 토큰 연결을 사용하여 universal charater name를 생성하지 않는다. #define assign(ucn,