표준 라이브러리의 어떤 기능을 피해야합니까?
나는 스택 오버플로에서 일부 C 함수가 "구식"이거나 "피해야한다"고 읽었습니다. 이런 종류의 기능에 대한 몇 가지 예와 그 이유를 알려 주시겠습니까?
이러한 기능에 대한 대안은 무엇입니까?
안전하게 사용할 수 있습니까? 모범 사례가 있습니까?
더 이상 사용되지 않는 함수
안전하지 않음 이러한 함수
의 완벽한 예는 gets () 입니다. 대상 버퍼의 크기를 알 수있는 방법이 없기 때문입니다. 결과적으로 gets ()를 사용하여 입력을 읽는 모든 프로그램에는 버퍼 오버플로 취약점이 있습니다. 비슷한 이유로 strcpy () 대신 strncpy () 를 사용 하고 strcat () 대신 strncat () 을 사용해야합니다 .
그러나 더 많은 예제에는 임시 파일 덮어 쓰기와 관련된 잠재적 보안 문제 로 인한 tmpfile () 및 mktemp () 함수 가 포함되며 더 안전한 mkstemp () 함수 로 대체됩니다 .
재진입이 아닌
다른 예로는 재진입이 불가능하고 (따라서 스레드 안전이 보장되지 않는 ) gethostbyaddr () 및 gethostbyname () 이 재진입 getaddrinfo () 및 freeaddrinfo () 로 대체되었습니다 .
여기서 패턴을 알아 차릴 수 있습니다. 보안 부족 (안전하게 구현할 수있는 충분한 정보를 서명에 포함하지 않았을 가능성이 있음) 또는 재전송하지 않는 것이 일반적인 사용 중단 원인입니다.
구식, 비
이식성 일부 다른 함수는 기능을 복제하고 다른 변형만큼 이식 가능하지 않기 때문에 단순히 더 이상 사용되지 않습니다. 예를 들어, bzero () 대신 memset ()을 사용 합니다.
스레드 안전성 및 재진입
게시물에서 스레드 안전성 및 재진입에 대해 질문했습니다. 약간의 차이가 있습니다. 공유되고 변경 가능한 상태를 사용하지 않으면 함수가 재진입됩니다. 예를 들어, 필요한 모든 정보가 함수로 전달되고 필요한 버퍼도 함수에 전달되면 (함수에 대한 모든 호출에서 공유하는 대신) 재진입이 가능합니다. 즉, 서로 다른 스레드가 독립적 인 매개 변수를 사용하여 실수로 상태를 공유 할 위험이 없습니다. 재진입은 스레드 안전성보다 더 강력한 보증입니다. 여러 스레드에서 동시에 사용할 수있는 함수는 스레드로부터 안전합니다. 다음과 같은 경우 함수는 스레드로부터 안전합니다.
- 재진입 가능 (즉, 통화간에 상태를 공유하지 않음) 또는 :
- 재진입은 아니지만 공유 상태에 필요한 동기화 / 잠금을 사용합니다.
일반적으로 단일 UNIX 사양 및 IEEE 1003.1 (예 : "POSIX")에서 재진입이 보장되지 않는 함수는 스레드로부터 안전하다고 보장되지 않습니다. 즉, 재진입이 보장되는 기능 만 멀티 스레드 애플리케이션에서 이식 가능하게 사용할 수 있습니다 (외부 잠금 없음). 그러나 이러한 표준의 구현이 재진입이 아닌 함수를 스레드 세이프하도록 선택할 수 없다는 것을 의미하지는 않습니다. 예를 들어, Linux는 스레드 안전성을 보장하기 위해 (단일 UNIX 사양 이상의) 재진입이 아닌 함수에 동기화를 자주 추가합니다.
문자열 (일반적으로 메모리 버퍼)
또한 문자열 / 배열에 근본적인 결함이 있는지 질문했습니다. 어떤 사람들은 이것이 사실이라고 주장 할 수 있지만, 나는 언어에 근본적인 결함이 없다고 주장합니다. C 및 C ++에서는 배열의 길이 / 용량을 별도로 전달해야합니다 (다른 언어에서와 같이 ".length"속성이 아님). 이것은 결함이 아닙니다. 모든 C 및 C ++ 개발자는 필요한 곳에 길이를 매개 변수로 전달하기 만하면 올바른 코드를 작성할 수 있습니다. 문제는이 정보를 필요로하는 여러 API가이를 매개 변수로 지정하지 못했다는 것입니다. 또는 일부 MAX_BUFFER_SIZE 상수가 사용된다고 가정합니다. 이러한 API는 이제 더 이상 사용되지 않으며 배열 / 버퍼 / 문자열 크기를 지정할 수있는 대체 API로 대체되었습니다.
Scanf (마지막 질문에 대한 답변)
개인적으로 C ++ iostreams 라이브러리 (std :: cin, std :: cout, << 및 >> 연산자, std :: getline, std :: istringstream, std :: ostringstream)를 사용합니다. , 등), 그래서 나는 일반적으로 그것을 다루지 않습니다. 그래도 순수 C를 사용해야한다면 개인적으로 fgetc () 또는 getchar () 를 strtol () , strtoul () 등과 함께 사용하고 수동으로 파싱 할 것입니다. varargs 또는 형식 문자열. 즉, 내가 아는 한 [f] scanf () , [f] printf () 에는 문제가 없습니다.등. 형식 문자열을 직접 작성하는 한 임의의 형식 문자열을 전달하거나 사용자 입력이 형식 문자열로 사용되도록 허용하지 않으며 적절한 경우 <inttypes.h>에 정의 된 형식 지정 매크로를 사용합니다 . (참고로 snprintf () 는 sprintf () 대신 사용해야 하지만 형식 문자열의 사용이 아니라 대상 버퍼의 크기를 지정하지 못한 것과 관련이 있습니다.) C ++에서 boost :: format 은 varargs없이 printf와 같은 형식을 제공 한다는 점도 지적해야합니다 .
다시 한 번 사람들은 str 함수의 "n"버전이 안전한 버전이라는 우스꽝스러운 주장을 만트라처럼 반복하고 있습니다.
그것이 그들이 의도 한 것이라면 항상 null로 문자열을 종료합니다.
"n"버전의 함수는 고정 길이 필드 (예 : 초기 파일 시스템의 디렉토리 항목)와 함께 사용하도록 작성되었습니다. 여기서 문자열이 필드를 채우지 않는 경우에만 nul 종결자가 필요합니다. 이것은 또한 함수가 대체로 사용되는 경우 무의미하게 비효율적 인 이상한 부작용이있는 이유이기도합니다. 예를 들어 strncpy ()를 사용하십시오.
s2가 가리키는 배열이 n 바이트보다 짧은 문자열 인 경우, 모두 n 바이트가 기록 될 때까지 s1이 가리키는 배열의 복사본에 null 바이트가 추가됩니다.
파일 이름을 처리하기 위해 할당 된 버퍼는 일반적으로 4KB이기 때문에 성능이 크게 저하 될 수 있습니다.
"가정"안전한 버전을 원하면 항상 문자열을 nul로 종료하고 부작용이없는 strl 루틴 (strlcpy, strlcat 등)을 얻거나 직접 작성하십시오. 이들은 조용히 문자열을자를 수 있기 때문에 실제로 안전하지 않다는 점에 유의하십시오. 이것은 실제 프로그램에서 가장 좋은 행동 방법은 아닙니다. 이것이 괜찮은 경우도 있지만 치명적인 결과를 초래할 수있는 상황도 많이 있습니다 (예 : 의료 처방전 인쇄).
여기에 몇 가지 대답은 strncat()
over 사용 을 제안합니다 strcat()
. strncat()
(및 strncpy()
)도 피해야한다고 제안합니다 . 올바르게 사용하기 어렵고 버그로 이어지는 문제가 있습니다.
- 의 길이 매개 변수
strncat()
는 대상 버퍼의 크기가 아니라 대상에 복사 할 수있는 최대 문자 수와 관련이 있습니다 (정확히 세 번째 항목 참조). 이로 인해strncat()
특히 여러 항목이 대상에 연결되는 경우 사용해야하는 것보다 사용하기 가 더 어려워집니다. - 결과가 잘 렸는지 확인하기 어려울 수 있습니다 (중요 할 수도 있고 아닐 수도 있음).
- 일회성 오류가 발생하기 쉽습니다. C99 표준에 따르면 "따라서로 가리키는 배열에서 끝날 수있는 최대 문자 수
s1
는strlen(s1)+n+1
"strncat( s1, s2, n)
strncpy()
또한 직관적 인 방식으로 사용하려고하면 버그가 발생할 수있는 문제가 있습니다. 대상이 null로 종료된다는 보장은 없습니다. '\0'
(적어도 특정 상황에서) 버퍼의 마지막 위치에 직접 을 드롭하여 해당 코너 케이스를 구체적으로 처리해야합니다 .
OpenBSD strlcat()
와 같은 것을 사용하는 것이 좋을 것 strlcpy()
입니다. (일부 사람들은 그 기능을 싫어한다는 것을 알고 있습니다. strncat()
/ 보다 안전하게 사용하기가 훨씬 쉽다고 생각합니다 strncpy()
).
Todd Miller와 Theo de Raadt가 strncat()
and의 문제에 대해 말한 내용은 다음 과 strncpy()
같습니다.
There are several problems encountered when
strncpy()
andstrncat()
are used as safe versions ofstrcpy()
andstrcat()
. Both functions deal with NUL-termination and the length parameter in different and non-intuitive ways that confuse even experienced programmers. They also provide no easy way to detect when truncation occurs. ... Of all these issues, the confusion caused by the length parameters and the related issue of NUL-termination are most important. When we audited the OpenBSD source tree for potential security holes we found rampant misuse ofstrncpy()
andstrncat()
. While not all of these resulted in exploitable security holes, they made it clear that the rules for usingstrncpy()
andstrncat()
in safe string operations are widely misunderstood.
OpenBSD's security audit found that bugs with these functions were "rampant". Unlike gets()
, these functions can be used safely, but in practice there are a lot of problems because the interface is confusing, unintuitive and difficult to use correctly. I know that Microsoft has also done analysis (though I don't know how much of their data they may have published), and as a result have banned (or at least very strongly discouraged - the 'ban' might not be absolute) the use of strncat()
and strncpy()
(among other functions).
Some links with more information:
- http://www.usenix.org/events/usenix99/full_papers/millert/millert_html/
- http://en.wikipedia.org/wiki/Off-by-one_error#Security_implications
- http://blogs.msdn.com/michael_howard/archive/2004/10/29/249713.aspx
- http://blogs.msdn.com/michael_howard/archive/2004/11/02/251296.aspx
- http://blogs.msdn.com/michael_howard/archive/2004/12/10/279639.aspx
- http://blogs.msdn.com/michael_howard/archive/2006/10/30/something-else-to-look-out-for-when-reviewing-code.aspx
Some people would claim that strcpy
and strcat
should be avoided, in favor of strncpy
and strncat
. This is somewhat subjective, in my opinion.
They should definitely be avoided when dealing with user input - no doubt here.
In code "far" from the user, when you just know the buffers are long enough, strcpy
and strcat
may be a bit more efficient because computing the n
to pass to their cousins may be superfluous.
Standard library functions that should never be used:
setjmp.h
setjmp()
. Together withlongjmp()
, these functions are widely recogniced as incredibly dangerous to use: they lead to spaghetti programming, they come with numerous forms of undefined behavior, they can cause unintended side-effects in the program environment, such as affecting values stored on the stack. References: MISRA-C:2012 rule 21.4, CERT C MSC22-C.longjmp()
. Seesetjmp()
.
stdio.h
gets()
. The function has been removed from the C language (as per C11), as it was unsafe as per design. The function was already flagged as obsolete in C99. Usefgets()
instead. References: ISO 9899:2011 K.3.5.4.1, also see note 404.
stdlib.h
atoi()
family of functions. These have no error handling but invoke undefined behavior whenever errors occur. Completely superfluous functions that can be replaced with thestrtol()
family of functions. References: MISRA-C:2012 rule 21.7.
string.h
strncat()
. Has an awkward interface that are often misused. It is mostly a superfluous function. Also see remarks forstrncpy()
.strncpy()
. The intention of this function was never to be a safer version ofstrcpy()
. Its sole purpose was always to handle an ancient string format on Unix systems, and that it got included in the standard library is a known mistake. This function is dangerous because it may leave the string without null termination and programmers are known to often use it incorrectly. References: Why are strlcpy and strlcat considered insecure?.
Standard library functions that should be used with caution:
assert.h
assert()
. Comes with overhead and should generally not be used in production code. It is better to use an application-specific error handler which displays errors but does not necessarily close down the whole program.
signal.h
signal()
. References: MISRA-C:2012 rule 21.5, CERT C SIG32-C.
stdarg.h
va_arg()
family of functions. The presence of variable-length functions in a C program is almost always an indication of poor program design. Should be avoided unless you have very specific requirements.
stdio.h
Generally, this whole library is not recommended for production code, as it comes with numerous cases of poorly-defined behavior and poor type safety.
fflush()
. Perfectly fine to use for output streams. Invokes undefined behavior if used for input streams.gets_s()
. Safe version ofgets()
included in C11 bounds-checking interface. It is preferred to usefgets()
instead, as per C standard recommendation. References: ISO 9899:2011 K.3.5.4.1.printf()
family of functions. Resource heavy functions that come with lots of undefined behavior and poor type safety.sprintf()
also has vulnerabilities. These functions should be avoided in production code. References: MISRA-C:2012 rule 21.6.scanf()
family of functions. See remarks aboutprintf()
. Also, -scanf()
is vulnerable to buffer overruns if not used correctly.fgets()
is preferred to use when possible. References: CERT C INT05-C, MISRA-C:2012 rule 21.6.tmpfile()
family of functions. Comes with various vulnerability issues. References: CERT C FIO21-C.
stdlib.h
malloc()
family of functions. Perfectly fine to use in hosted systems, though be aware of well-known issues in C90 and therefore don't cast the result. Themalloc()
family of functions should never be used in freestanding applications. References: MISRA-C:2012 rule 21.3.Also note that
realloc()
is dangerous in case you overwrite the old pointer with the result ofrealloc()
. In case the function fails, you create a leak.system()
. Comes with lots of overhead and although portable, it is often better to use system-specific API functions instead. Comes with various poorly-defined behavior. References: CERT C ENV33-C.
string.h
strcat()
. See remarks forstrcpy()
.strcpy()
. Perfectly fine to use, unless the size of the data to be copied is unknown or larger than the destination buffer. If no check of the incoming data size is done, there may be buffer overruns. Which is no fault ofstrcpy()
itself, but of the calling application - thatstrcpy()
is unsafe is mostly a myth created by Microsoft.strtok()
. Alters the caller string and uses internal state variables, which could make it unsafe in a multi-threaded environment.
Avoid
strtok
for multithreaded programs as its not thread-safe.gets
as it could cause buffer overflow
It is probably worth adding again that strncpy()
is not the general-purpose replacement for strcpy()
that it's name might suggest. It is designed for fixed-length fields that don't need a nul-terminator (it was originally designed for use with UNIX directory entries, but can be useful for things like encryption key fields).
It is easy, however, to use strncat()
as a replacement for strcpy()
:
if (dest_size > 0)
{
dest[0] = '\0';
strncat(dest, source, dest_size - 1);
}
(The if
test can obviously be dropped in the common case, where you know that dest_size
is definitely nonzero).
Also check out Microsoft's list of banned APIs. These are APIs (including many already listed here) that are banned from Microsoft code because they are often misused and lead to security problems.
You may not agree with all of them, but they are all worth considering. They add an API to the list when its misuse has led to a number of security bugs.
Almost any function that deals with NUL terminated strings is potentially unsafe. If you are receiving data from the outside world and manipulating it via the str*() functions then you set yourself up for catastrophe
Don't forget about sprintf - it is the cause of many problems. This is true because the alternative, snprintf has sometimes different implementations which can make you code unportable.
In case 1 (linux) the return value is the amount of data needed to store the entire buffer (if it is smaller than the size of the given buffer then the output was truncated)
In case 2 (windows) the return value is a negative number in case the output is truncated.
Generally you should avoid functions that are not:
buffer overflow safe (a lot of functions are already mentioned in here)
thread safe/not reentrant (strtok for example)
In the manual of each functions you should search for keywords like: safe, sync, async, thread, buffer, bugs
It is very hard to use scanf
safely. Good use of scanf
can avoid buffer overflows, but you are still vulnerable to undefined behavior when reading numbers that don't fit in the requested type. In most cases, fgets
followed by self-parsing (using sscanf
, strchr
, etc.) is a better option.
But I wouldn't say "avoid scanf
all the time". scanf
has its uses. As an example, let's say you want to read user input in a char
array that's 10 bytes long. You want to remove the trailing newline, if any. If the user enters more than 9 characters before a newline, you want to store the first 9 characters in the buffer and discard everything until the next newline. You can do:
char buf[10];
scanf("%9[^\n]%*[^\n]", buf));
getchar();
Once you get used to this idiom, it's shorter and in some ways cleaner than:
char buf[10];
if (fgets(buf, sizeof buf, stdin) != NULL) {
char *nl;
if ((nl = strrchr(buf, '\n')) == NULL) {
int c;
while ((c = getchar()) != EOF && c != '\n') {
;
}
} else {
*nl = 0;
}
}
In all the string-copy/move scenarios - strcat(), strncat(), strcpy(), strncpy(), etc. - things go much better (safer) if a couple simple heuristics are enforced:
1. Always NUL-fill your buffer(s) before adding data.
2. Declare character-buffers as [SIZE+1], with a macro-constant.
For example, given:
#define BUFSIZE 10
char Buffer[BUFSIZE+1] = { 0x00 }; /* The compiler NUL-fills the rest */
we can use code like:
memset(Buffer,0x00,sizeof(Buffer));
strncpy(Buffer,BUFSIZE,"12345678901234567890");
relatively safely. The memset() should appear before the strncpy(), even though we initialized Buffer at compile-time, because we don't know what garbage other code placed into it before our function was called. The strncpy() will truncate the copied data to "1234567890", and will not NUL-terminate it. However, since we have already NUL-filled the entire buffer - sizeof(Buffer), rather than BUFSIZE - there is guaranteed to be a final "out-of-scope" terminating NUL anyway, as long as we constrain our writes using the BUFSIZE constant, instead of sizeof(Buffer).
Buffer and BUFSIZE likewise work fine for snprintf():
memset(Buffer,0x00,sizeof(Buffer));
if(snprintf(Buffer,BUFIZE,"Data: %s","Too much data") > BUFSIZE) {
/* Do some error-handling */
} /* If using MFC, you need if(... < 0), instead */
Even though snprintf() specifically writes only BUFIZE-1 characters, followed by NUL, this works safely. So we "waste" an extraneous NUL byte at the end of Buffer...we prevent both buffer-overflow and unterminated string conditions, for a pretty small memory-cost.
My call on strcat() and strncat() is more hard-line: don't use them. It is difficult to use strcat() safely, and the API for strncat() is so counter-intuitive that the effort needed to use it properly negates any benefit. I propose the following drop-in:
#define strncat(target,source,bufsize) snprintf(target,source,"%s%s",target,source)
It is tempting to create a strcat() drop-in, but not a good idea:
#define strcat(target,source) snprintf(target,sizeof(target),"%s%s",target,source)
because target may be a pointer (thus sizeof() does not return the information we need). I don't have a good "universal" solution to instances of strcat() in your code.
A problem I frequently encounter from "strFunc()-aware" programmers is an attempt to protect against buffer-overflows by using strlen(). This is fine if the contents are guaranteed to be NUL-terminated. Otherwise, strlen() itself can cause a buffer-overrun error (usually leading to a segmentation violation or other core-dump situation), before you ever reach the "problematic" code you are trying to protect.
atoi is not thread safe. I use strtol instead, per recommendation from the man page.
'developer tip' 카테고리의 다른 글
Java 8에서 ArrayList의 기본 용량이 이제 0 인 이유는 무엇입니까? (0) | 2020.09.12 |
---|---|
NHibernate의 역 속성 (0) | 2020.09.12 |
@OneToMany List <> 대 Set <> 차이점 (0) | 2020.09.12 |
Java를 사용하여 멀티 파트 / 양식 데이터 POST 요청을하려면 어떻게해야합니까? (0) | 2020.09.11 |
Java는 음수로 모듈러스 계산을 어떻게 수행합니까? (0) | 2020.09.11 |