VS2005로 건너온 이후 가장 마음에 안드는 것이 바로 "C4996 Warning"이다.
warning C4996: '_tcsncpy': This function or variable may be unsafe. Consider using _tcsncpy_s instead. ...

사실... "strcpy" 같은 "n"이 붙지 않은 함수들이야 쓰면 안된다는 건 삼척동자가 다 아는 사실이다. 그런 함수들 못쓰게 warning 띄우는 거야 아무 불만 없지.. 그런데 "_tcsncpy" 처럼 "n" 을 붙인 함수들은 Secure한 코드이지 않은가? 언제는 "n"붙은 함수를 쓰라고 하더니.. MS는 어느날 갑자기 _s 붙은 함수들을 만들어 놓고 이걸 안쓰면 워닝을 띄운다. (물론  warning disable시키는 방법이야 있다만.. 기분이 나쁘다는 거다.)

모 어쨌든... MS에서 _s 붙은 시리즈들을 쓰라고 하니.. 일단 어떤건지는 알고 있어야 겠는데... MSDN의 설명을 읽어봐도... 이건 통.. 알쏭달쏭이다.
예를 들어, 대표적인 _s 함수인 strncpy_s 에 대한 원형과 설명을 보면 다음과 같이 되어 있다.

errno_t     strncpy_s( char *strDest, size_t sizeInBytes, const char *strSource, size_t count );

다음은 사용법에 대한 설명 부분이다...

These functions try to copy the first D characters of strSource to strDest, where D is the lesser of count and the length of strSource. If those D characters will fit within strDest (whose size is given as sizeInBytes or sizeInWords) and still leave room for a null terminator, then those characters are copied and a terminating null is appended; otherwise, strDest[0] is set to the null character and the invalid parameter handler is invoked, as described in Parameter Validation.

허허... 읽어볼수록 알쏭달쏭이다...
sizeInBytes는 또 모구 count 는 또 모야...
sizeInBytes, sizeInWords :  The size of the destination string, in characters.
count :  Number of characters to be copied, or _TRUNCATE.
젠장... 더 헷갈린다... @.@

이럴 땐 테스트가 최고다.
MSDN도 찾아보고 이모저모 테스트도 해본 결과 내가 확인한 차이점은 다음과 같다. 혹시 지나가는 고수님께서 틀린 부분을 발견하신다면.. 알려주시라.

....................................................................................................................................................................

1. 대부분의 CRT 문자열 함수에 대한 secure버젼이 존재한다.
strcpy -> strcpy_s
strncpy -> strncpy_s
strncat -> strncat_s
_snprintf -> _snprintf_s
(... 물론 유니코드버젼이나 T-TYPE 버젼도 존재한다.)

특이한 점은... "Buffer Size"를 주도록 되어있는 함수에 대해서도 Secure 버젼이 존재한다는 것이다.
예를 들어 _snprintf 와 _snprintf_s 의 원형을 비교해 보면 다음과 같다.
int _snprintf( char *buffer, size_t count, const char *format [, argument] ... );
int _snprintf_s( char *buffer, size_t sizeOfBuffer, size_t count, const char *format [, argument] ... );
즉, "n"버젼의 버퍼사이즈와는 상관 없이 _s 버젼에서는 count라는 아규먼트가 하나 더 붙는다.
이전 버젼 함수들의 "count"가 "Destination 버퍼의 크기"를 나타내는 반면, _s 버젼의 "count"는 "복사될 Source 스트링의 최대 글자수"이다. (이전 버젼의 "count"는 _s 버젼의 "SizeOfBuffer=SizeInBytes=sizeInWords"에 해당한다.)


2. 이전 버젼에서는 버퍼사이즈를 초과하는 문자열 복사가 시도될 경우, 버퍼사이즈만큼만 복사되고 나머지는 Truncate되었으나, _s 버젼에서는 "다음과 같은 경우"에는 한글자도 복사되지 않으며, 대신 Exception이 발생한다. (이부분에 대해 MSDN에서는 "Parameter Validation"이라고 하여 대단히 자랑스럽게 설명하고 있다.)
이 Exception이 발생했을 경우, _set_invalid_parameter_handler() 함수에 의해 지정된 핸들러가 호출되며, 여기서 계속 실행시킬 수도 있고 프로세스가 Abort될 수도 있다. (테스트해본 결과, 이 Exception은 일반적인 try ~ catch 로는 핸들링되지 않는 듯 하다.)
"다음과 같은 경우"의 예는 다음과 같다.
- Source Str 및 Destination Str 중 하나라도 NULL인 경우
- 주어진 문자열에 비해 버퍼 사이즈가 너무 작은 경우... ("너무 작다"의 기준에 대해서는 3번을 참조)

3. 복사될 수 있는 최대 글자수는 다음과 같이 "복잡하게" 정해진다.
- Src String에서 Dst Buffer 로 "count" 만큼의 글자(MBCS일 경우 바이트)가 복사된다.  Src String이 count보다 짧다면 그냥 Src String 전체가 복사되며 버퍼의 남는 공간엔 0xFD가 채워진다. 만약 Src String이 count보다 길 경우 count 만큼의  글자가 복사되며 NULL문자가 자동으로 append된다. MBCS를 기준으로 말하자면.. 총 count + 1 바이트가 복사되는 셈이다.
- 이때 append된 NULL문자를 포함한 복사되는 바이트수(유니코드인 경우 글자수)가 Dst Buffer Size(sizeInBytes, sizeInWords)를 넘어선다면 Exception이 발생하고, 한 바이트도 복사되지 않는다.
- 위의 두가지 조건을 모두 만족시키기 위해 "count" 의 최대값은 "SizeOfBuffer - 1" 이하로 지정되어야 한다. (NULL 문자 들어갈 자리 1바이트 때문)
- Src 길이와와 Dst 버퍼사이즈를 모두 따지는 것이 복잡하다면 Src 길이는 "_TRUNCATE" 매크로로 지정하면 된다. 이 경우, 버퍼를 오버하는 문자열은 (버퍼사이즈 - 1바이트) 만큼만 복사된 후 NULL문자가 append된다. 말하자면... lstrcpyn 과 동일하게 동작한다.

예를 들어, 아래의 코드는 Exception이 발생한다.

위의 코드는 이렇게 써야 한다.

위의 코드가 복잡하면 이렇게 써도 된다. (아래와 같이 사용해도 szBuf는 NULL-Terminate 된다.)

_snprintf_s 등 다른 함수들도 마찬가지이다.

물론... Exception Handler를 등록한 후 이렇게 써도 된다.
TCHAR szBuf[4] = {0,};
_tcsncpy_s(szBuf, _countof(szBuf), 
           _T("AAAAAAAAA"), _tcslen(_T("AAAAAAAAA")));

MS에서는 이렇게 쓰라는 생각이었는지 모르겠지만... 저렇게 쓰면 결국 Exception이 발생하고 한바이트도 복사되지 않는다. (그게 맞다는 건가?? @.@)

.............................................................................................................................................................

버퍼를 넘어선 스트링을 복사하려고 시도할 때 어떻게 처리되는 게 맞는 처리일까?

1. 버퍼사이즈가 허락하는 만큼만 복사하고 나머지는 Truncate함.
2. Exception으로 간주하고 관둔다. (1바이트도 복사되지 않음)

만약 정답이 2번이라면 MS의 _s 버젼 함수들이 좋은 선택일 수 있겠지만... 과연??

MS에서 보안문제를 해결하고자 야심차게 내놓은 "_s" 시리즈... 내가 보기엔 MS의 "뻘짓 리스트"에 아이템 하나를 추가했을 뿐이다.
문자열을 다룰 땐 두가지만 확실하면 된다.

1. 버퍼 오버런 방지
2. NULL-Terminating 보장

옛 말대로 "Simple is Best" 이다.

Posted by kuaaan
,


사랑합니다. 편안히 잠드소서