strcpy도 사용 금지
(daniel.haxx.se)- cURL 프로젝트가 기존에
strncpy()를 제거한 데 이어, 이제strcpy()도 코드베이스에서 완전히 금지함 -
strcpy()는 API가 단순하지만 버퍼 크기 검증이 분리될 위험이 있어, 장기 유지보수 시 안전하지 않음 - 이를 대신해
curlx_strcopy()라는 새 함수가 도입되어, 대상 버퍼 크기와 문자열 길이를 모두 인자로 받아 복사 가능 여부를 검사 후 수행 - 이 함수는 내부적으로
memcpy()를 사용하며, 널 종료 문자 처리까지 보장함 - 이러한 변경으로 보안성과 코드 일관성을 높이고, AI가 잘못된 취약점 보고를 생성하는 문제도 줄일 수 있음
strcpy 제거 배경
- cURL은 과거에
strncpy()호출을 모두 제거했으며, 이 함수의 비직관적 API와 널 종료 보장 실패, 불필요한 0 패딩 문제를 지적함- 부분 문자열 복사가 필요한 경우
memcpy()를 사용하고 널 종료를 직접 처리하도록 변경
- 부분 문자열 복사가 필요한 경우
-
strcpy()는 API가 단순하지만 버퍼 크기를 명시하지 않아 유지보수 중 검증 코드와 복사 호출이 분리될 위험이 있음- 코드가 수십 년간 여러 개발자에 의해 수정될 경우, 버퍼 크기 검증이 무력화될 가능성이 존재
새로운 문자열 복사 함수 도입
- 이러한 위험을 방지하기 위해
curlx_strcopy()라는 대체 함수를 도입- 인자로 대상 버퍼, 버퍼 크기, 원본 버퍼, 원본 문자열 길이를 받음
- 복사와 널 종료가 모두 가능한 경우에만
memcpy()로 수행 - 실패 시 대상 버퍼를 빈 문자열로 초기화
- 이 함수는
strcpy()보다 더 많은 인자와 코드량이 필요하지만, 버퍼 검증을 복사와 밀접하게 결합해 안전성을 확보 - cURL 코드베이스에서
strcpy()사용을 완전히 금지하고,strncpy()와 동일하게 제거
구현 세부
- 함수 정의 예시는 다음과 같음
void curlx_strcopy(char *dest, size_t dsize, const char *src, size_t slen) { DEBUGASSERT(slen < dsize); if(slen < dsize) { memcpy(dest, src, slen); dest[slen] = 0; } else if(dsize) dest[0] = 0; } -
DEBUGASSERT를 통해 개발 중 오류를 조기 탐지하며, 실제 배포 환경에서는 항상 성공하도록 설계 -
strcpy처럼 반환값이 없으며, 테스트 및 퍼징 단계에서 오류를 잡는 방식을 채택
커뮤니티 반응
- 일부 개발자는
strcpy_s()(C11 Annex K) 와 유사하다고 지적했으나, cURL은 여전히 C89 표준을 사용 중 - 다른 의견으로는 반환값 추가 필요성이나 버퍼 실패 시 처리 방식 개선 제안이 있었음
- 이에 대해 cURL 측은 “항상 성공하는 함수로 설계되었기 때문에 반환값은 불필요하다”고 설명
AI 관련 부가 효과
- 이번 변경으로 AI 챗봇이 cURL 코드에서 strcpy 사용을 잘못 탐지해 ‘취약하다’고 주장하는 문제를 방지할 수 있음
- 다만, 작성자는 “AI가 다른 허위 보고를 만들어낼 가능성은 여전하다”며 AI 기반 코드 분석의 한계를 언급
오류 C4996 'strcpy' : This function or variable may be unsafe. Consider using strcpy_s intead. To disable deprecation, us _CRT_SECURE_NO_WARNINGS. See online hele of details.
Hacker News 의견들
-
strcpy()는 보안뿐 아니라 성능 측면에서도 좋지 않음
예전에는 문자열 길이를 모를 때 strcpy()가 효율적이라 생각했지만, 실제로는 한 바이트씩 복사하는 구조라 CPU가 분기 예측을 해야 하고, 이는 비효율적임- 이제는 가능한 한 null-terminated 문자열 자체를 버려야 한다고 생각함
- 최근에는 strcpy가 스칼라 루프를 쓰는 걸 본 적이 없음. 혹시 ARM 아키텍처에서만 그런 것인지 궁금함
-
C의 문자열 루틴들은 하나같이 큰 제약이 있어서 쓸모가 없다고 느꼈음
그래서 문자열 포인터와 함께 할당된 메모리 크기를 기록하는 라이브러리가 꼭 필요하다고 생각함
예시로 bstring 라이브러리를 참고할 만함- strncpy가 생긴 이유는 고정 길이의 파일 이름을 복사하기 위해서였음. 자세한 설명은 이 StackOverflow 답변 참고
- 문자열에 길이 정보를 포함하지 않은 것은 과거 메모리 절약을 위해서였음. 당시에는 바이트 하나도 아까웠기 때문임
- C의 문자열 함수들이 문제를 일으킨 이유는, 설계자들이 그 결과를 충분히 예측하지 못한 채 추가했기 때문임. 배열이 함수 인자에서 포인터로 강제 변환되는 것도 근본적인 설계 실수임
- 이런 부가적인 book-keeping은 예전엔 부담이었지만, 지금은 충분히 감당 가능한 수준임
- strncpy는 원래 고정 폭 문자열 필드를 다루기 위한 함수였음. 예를 들어
char username[20]같은 필드에 NUL로 채워 넣는 용도였음. 관련 문서는 string_copying.7 매뉴얼 참고
-
curlx_strcopy가 성공 여부를 반환하지 않는 게 의아함
dest[0]을 검사할 수도 있지만, 이는 오류 유발 가능성이 높고 비직관적임- 이전 버전은 에러를 반환했는데, 지금은 조용히 실패하고 빈 문자열을 설정함. 이건 이상함
- 아마도
DEBUGASSERT(slen < dsize);가 통과되면 성공으로 간주하는 듯하지만, release 빌드에서는 assert가 제거될 수 있음. 명시적인 에러 코드가 더 낫다고 생각함 - 이런 설계라면 앞으로 CVE가 나올 가능성이 높다고 봄
-
strncpy()는 원래 null-terminated 문자열을 위한 게 아니라 고정 길이 필드를 위한 함수였음
문제는 정적 분석기가 strcpy 대신 strncpy를 쓰라고 권장하면서 시작되었음. 실제 대안은 snprintf나 strlcpy였음- strlcpy는 BSD 계열 함수라 POSIX에는 없음. 공식 권장은 stpecpy지만, 실제 구현은 거의 없음. 관련 문서 참고
- strncpy가 null 이후를 패딩하는 이유는, 디렉터리 엔트리 같은 고정 길이 이름 필드에서 효율적인 비교를 위해서였음. ANSI C 표준의 근거 문서에도 그렇게 명시되어 있음
-
이 API는 마치 Annex-K처럼 느껴짐. 목적지 버퍼 크기에는 NUL 공간이 포함되지만, 소스 크기에는 포함되지 않음
차라리 memcpy를 직접 쓰는 게 낫다고 생각함 -
기사에서 “strcpy는 AI가 잘못된 취약점 리포트를 만들어내는 미끼”라는 말이 인상적이었음
- 실제로는 AI가 단순히 strcpy를 문제로 지적하는 게 아니라, 논리 오류가 있는 복잡한 증명을 만들어내서 유지보수자들이 이를 검증하느라 고생함
- 이런 잘못된 리포트를 제출하는 사람들은 AI가 틀릴 수 있다는 걸 모르거나, 그냥 신경 쓰지 않음. 어차피 잘못된 보고에도 비용이 없기 때문임
- 결국 AI를 적절하지 않은 용도로 쓰는 사람들이 문제임
-
“코드 근처에서 검사하라”는 원칙은 좋지만, 데이터의 수명 주기 초반에 검사해야 할 때는 애매함
Rust의 Result 타입처럼 “검증된 데이터”임을 타입으로 구분할 수 있다면 좋겠다고 생각함- Result는 단순히 성공/실패만 담을 뿐, 검증된 상태를 보장하지 않음. 대신 검증 과정을 거쳐야만 생성 가능한 별도의 타입을 두는 게 좋음. 이것이 “** parse, don’t validate**” 철학임
- 검증은 소비자 코드 근처가 아니라, 시스템 경계에서 최대한 빨리 수행하는 게 이상적임. 다만 이를 위해서는 표현력 있는 타입 시스템이 필요함
- 이런 경우 Java의 String과 CharSequence처럼 타입을 구분해서 쓰는 것도 방법임
-
버퍼 크기와 문자열 길이의 off-by-one 차이는 끔찍한 사용성 문제임. 앞으로도 오류를 유발할 가능성이 큼
-
새로 제안된 문자열 복사 함수는, 복사가 불가능하면 대상 버퍼를 비워버리고 void를 반환함
하지만 이런 경우는 에러로 처리하고 버퍼를 건드리지 않는 게 낫다고 생각함. DEBUGASSERT로만 막는 건 불안함 -
프로젝트 완성을 축하함. C/C++도 노력하면 메모리 안전성을 확보할 수 있음
다만 모바일 환경에서는 그래프 폰트 크기가 너무 작아 가독성이 떨어짐- strcpy를 제거했다고 해서 코드가 메모리 안전해지는 것은 아님
- 그래프 폰트는 인쇄용으로 설계된 듯함. 블로그용으로는 너무 작음