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 기반 코드 분석의 한계를 언급
이건 25년전 게임 회사 재직시절에 디버그 코드로 작업 했던 방식이었는데, 비단 strcpy 하나 뿐이겠어요. 릴리즈에서는 속도 향상으로 다시 풀어놓은거로 서비스되었죠. 사실 게임 쪽이 메모리 충돌은 제일 민감해서 작업도 매우 민감하게 정신차리고 작업을 하니까, 메모리 디버거도 자체적으로 만들어 썼죠. 그런데 오늘날에 보니까 그게 가비지 컬렉션을 만들고 있었던거야. 아련한 추억이로다.
오류 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를 제거했다고 해서 코드가 메모리 안전해지는 것은 아님
- 그래프 폰트는 인쇄용으로 설계된 듯함. 블로그용으로는 너무 작음