본문 바로가기

C/C++

[펌]C/C++ Secure Function 에 대해서

Visual Studio 2005 Secure Function 에 좀더 쓰겠습니다.

사실 strcpy 류의 함수를 _s 의 postfix를 붙여주는 것만으로는 충분하지 않습니다.

char szBuf[10];
strcpy(szBuf, “test”)

와 같은 코드를 그냥 컴파일 하면 분명히 deprecated warning이 나옵니다.
그리고 _CRT_SECURE_NO_DEPRECATE 를 정의하거나, #pragma warning 을 이용해서 warning control을 할 수 있습니다.
이 방법은 예전의 방법입니다. Secure check를 전혀 하지 않는 방법이지요. 그런데 C++의 특성상 string manipulation 과정에서
buffer overrun이나 stack을 깨먹는 경우가 허다해서 _s postfix가 붙은 함수들이 나왔습니다.

하나만 살펴보면

errno_t strcpy_s(
char *strDestination,
size_t sizeInBytes,
const char *strSource
);

와 같은 signature를 띄고 있습니다. 기존의 strcpy 가 2개의 parameter를 받는 반면 이 함수는 3개를 받습니다.
그래서 C/C++에서는 아래와 같이 수정해야 합니다.

char szBuf[10];
strcpy_s(szBuf, 10, “test”)

그런데 사용자가 C가 아닌 C++을 사용하고 있고, _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES 값이 1로 define 되어 있다면(default로 1로 정의되어 있습니다.)
있다면, template function의 도움을 받아서 좀더 쉽게 수정을 할 수가 있는데요, strcpy_s 의 template function signature는 아래와 같습니다.

template <size_t size>
errno_t strcpy_s(
char (&strDestination)[size],
const char *strSource
); // C++ only

첫번째 parameter가 char * 가 아니라 char 배열에 대한 reference를 받는 형태로 변경되었습니다. 또한 type argument로 size_t 형태의 size를 받습니다.
이 함수를 사용하기 위해서는, 코드를 다음과 같이 바꾸어야 합니다.

char szBuf[10]
strcpy_s<10>(szBuf, “test”);

하지만 template function은 type inferencing 을 수행할 수 있으므로,

char szBuf[10]
strcpy_s(szBuf, “test”);

라고도 쓸 수 있습니다.
그래서 그냥 _s 만 쓰면 만사가 다 해결 될 것 같은데, 또 다른 문제가 있습니다. 위의 template function의 첫번째 parameter가 반드시 배열이여야 한다는 것이지요.

아래 코드를 보면 알 수 있는데요.

int _tmain(int argc, _TCHAR* argv[])
{
char arrayChar[10];
strcpy_s(arrayChar, "hello");

char *pDynamicChar = new char[10];
strcpy_s(pDynamicChar, "hello");
delete [] pDynamicChar;
return 0;
}

첫번째 strcpy_s 는 정확히 char array를 parameter로 전달됩니다. 하지만 두번째 strcpy_s 는 char *를 parameter로 전달합니다.
이 경우 template function의 type inferencing 이 일어나지 않으므로, “error C2660: 'strcpy_s' : function does not take 2 arguments” 와 같은 오류가 발생하게 됩니다.
두번째 pattern이 매우 널리 사용되는 구조임에 불구하고 말이죠. 이 경우 위의 코드는 아래와 같이 변경되어야 합니다.

int _tmain(int argc, _TCHAR* argv[])
{
char arrayChar[10];
strcpy_s(arrayChar, "hello");

char *pDynamicChar = new char[10];
strcpy_s(pDynamicChar, 10, "hello");
delete [] pDynamicChar;
return 0;
}

원 래의 strcpy_s() 함수를 쓴거죠. 즉 template function 은 static array에 대해서만 동작을 합니다. 이것은 자칫 큰 혼란을 가져 올 수 있는데, 어떨때는 strcpy_s가 2개의 parameter를 가지는 것처럼 보였다가, 또 어떨때는 strcpy_s 가 반드시 3개의 parameter를 가져야 하는 것 처럼 보이기 때문입니다.

이 러한 혼돈을 피하기 위해서는, 제 개인적인 견해로는 위에서 예를 들어 드린 template function은 사용하지 않는 것이 더 좋을 것 같습니다. 즉 처음부터 사용자가 _s postfix가 붙은 함수들은 모두 parameter가 한 개씩 늘었다고 이해하는 편이, 혼돈일 덜하기 때문입니다. 따라서

int _tmain(int argc, _TCHAR* argv[])
{
char arrayChar[10];
strcpy_s(arrayChar, 10, "hello");

char *pDynamicChar = new char[10];
strcpy_s(pDynamicChar, 10, "hello");
delete [] pDynamicChar;
return 0;
}

와 같이 쓰는 것이 좀더 일관되어 보입니다. 만일 이렇게 쓰기로 결정했다면 이제 template overload function 를 원천적으로 쓰지 못하도록 막는 것이 필요합니다.
아까 말씀드린데로 _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES 가 1로 정의되어 있을 때만 template overload function이 정의 되므로, 이녀석을 0로 define 해주는 것이 필요합니다.
그런데
#define _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES 0
구문을 어디에 둘것인가도 좀 까다로운데, 가장 좋은 방법은 stdafx.h 파일에 header file include 이전에 위치시키는 것이 가장 좋겠습니다.
즉,

#pragma once

#define _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES 0

#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include
#include

와 같이 쓰면 됩니다.

이런 secure Template overloads와 관련해서 3가지의 constant가 있는데요

_CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES
위에서 알아본 바와 같이 _s postfix가 붙은 함수 이름으로 template function을 정의합니다.
즉,

errno_t strcpy_s(
char (&strDestination)[size],
const char *strSource
); // C++ only

를 정의하게 됩니다.

_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES
_s postfix가 붙지 않음 함수 이름으로 template function을 정의합니다.
즉,
errno_t strcpy(
char (&strDestination)[size],
const char *strSource
); // C++ only

를 정의하게 됩니다.

_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT 를 1로 정의하게 되면
기존에 counter를 가지던 함수들 즉 strncpy 등의 함수에 대해서도 template overload function을 정의하게 됩니다.

Default 값은 각각

_CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES 1
_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 0
_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT 0

입니다.

여기서 끝이 아닙니다. 또다른 문제는 return 형에 있습니다.
기존에 strcpy 류의 함수를 보면 dest string의 char *를 return 하도록 구성되어 있는데, secure function 들은 error_t 형태를 반환하게 됩니다.
다행이도 이 부분은 compiler 가 conversion 오류를 발생하기 때문에 찾기가 쉽습니다.

마지막으로 제나름의 Guideline을 알려드리면,

1. Secure function을 쓸건지 안쓸건지를 결정하자
2. Secure function을 안 쓸거라면 과감하게 _CRT_SECURE_NO_DEPRECATE 를 정의하고 프로그램이 죽어 나자빠 질때를 기다리자.
3. Secure function을 쓸거라면 _s postfix를 가진 함수로 모두 대체하고, parameter는 length가 포함된 함수를 쓰자.
4. template overload function 은 쓰지 않도록 CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES 를 stdafx.h 파일에서 0 으로 정의하자.
5. 반환값을 사용하지 말자, 반환값의 형변환 오류가 발생하면 모두 변경한다.

그러나 이러한 Guideline이 모든 경우에 다 적용이 가능한 것은 아닙니다.
이미 개발되어 있는 code의 일부를 가져와서 쓰는 경우나, 어쩔 수 없이 예전의 방식을 써야 하는 경우가 아직은 상당히 존재하리라 예측됩니다.

출처 :
http://himskim.egloos.com/1352834