FlawOpen

flawopen.com/메모리 안전성/strcpy vs strncpy

strncpy()가 strcpy()보다 안전한가요?

CWE-787: 경계 밖 쓰기; CWE-170: 부적절한 널 종료참고 페이지
핵심 답변

고정 너비 필드는 칸이 나열된 상자와 같습니다. 이후 코드가 바이트를 C 문자열로 취급한다면 마지막에 종료 표식이 있어야 합니다. strncpy()는 최대 n바이트를 쓰며 실제 버퍼 크기보다 n이 크지 않을 때만 대상 범위를 넘지 않습니다. 원본이 제한을 채우면 널 종료 문자를 추가하지 않습니다. 이후 문자열 함수가 표식을 찾으려고 대상 범위 밖을 읽을 수 있습니다.

둘 다 잘못된 방법
char dst[16];

/* 1. Unbounded — classic overflow */
strcpy(dst, src);

/* 2. Bounded, but may not terminate.
   If strlen(src) >= 16, dst has no
   '\0' and every later strlen/printf
   reads out of bounds.            */
strncpy(dst, src, sizeof(dst));

/* 3. Off-by-one: room for 16 chars
   plus a terminator = 17 bytes,
   but dst only holds 16.          */
strncpy(dst, src, sizeof(dst));
dst[sizeof(dst)] = '\0';   /* OOB! */
올바른 방법
char dst[16];

/* Preferred: snprintf always
   null-terminates (when size > 0),
   and tells you about truncation. */
int n = snprintf(dst, sizeof(dst),
                 "%s", src);
if (n < 0 || (size_t)n >= sizeof(dst)) {
    /* truncated — handle it */
    return ERR_TOO_LONG;
}

/* Or strlcpy where available —
   always terminates, returns the
   length it tried to create.      */
if (strlcpy(dst, src, sizeof(dst))
        >= sizeof(dst)) {
    return ERR_TOO_LONG;
}

strncpy의 원래 용도

이 동작은 결함이 아닙니다. strncpy()는 고정 너비의 널 패딩 필드용이지 일반적인 안전 C 문자열 복사용이 아닙니다. 원본 문자열이 n보다 짧으면 대상에 n - 1바이트까지 널 바이트를 채웁니다. 원본의 처음 n바이트에 널 바이트가 없으면 정확히 n바이트를 복사하고 대상을 종료하지 않습니다. 고정 너비 필드에는 맞지만 널 종료 문자열을 요구하는 API에 전달하면 위험합니다.

원본이 n보다 짧을 때 필요한 패딩은 필드 너비 전체를 씁니다. 큰 버퍼에 짧은 원본을 복사하면 종료 문자에서 멈추는 함수보다 더 많은 작업을 할 수 있습니다.

잘림을 항상 확인하세요

오버플로 방지는 절반에 불과합니다. 조용한 잘림도 보안 문제입니다. 잘린 호스트명, 경로 또는 권한 값은 의미가 달라질 수 있습니다. snprintf는 쓰려고 했던 길이를 반환하므로 버퍼 크기와 비교하세요. 이 값을 실제 기록 바이트 수로 오해한 점은 Citrix Bleed에서도 관련이 있었습니다.

자주 묻는 질문

strcpy_s와 Annex K 함수는 어떤가요?

널 종료와 경계 검사를 제공하지만 이식성은 고르지 않습니다. Windows에서는 지원이 좋지만 다른 환경에서는 구현이 일정하지 않습니다. 여러 플랫폼을 대상으로 하면 snprintf가 더 널리 제공됩니다.

strlcpy는 C 표준에 포함되나요?

아니요. OpenBSD에서 시작했고 BSD와 macOS에서 널리 사용할 수 있으며 최근 glibc에도 추가됐습니다. 대상 플랫폼 지원 여부를 확인하세요.

C++에서는 무엇을 써야 하나요?

길이를 관리하는 std::string과 std::string_view를 우선 사용하세요. raw buffer가 필요한 API 경계에서만 C 함수를 사용하세요.

참고 자료

함께 보기 CWE와 CVE: 무엇이 다른가 Mass Assignment: 위험과 안전한 필드 허용 목록 Serverless 상태 누출: 사용자 데이터를 요청별로 분리 PHP ==와 Magic Hash 비교 Rust의 unsafe 블록은 메모리 안전한가요?