strcpy

Β· 1579 words Β· 4 minute read

κ°œμš” πŸ”—

ν•œλ‹¬ μ „ νšŒμ‚¬μ—μ„œ λ¦¬λˆ…μŠ€ λ””λ°”μ΄μŠ€ λ“œλΌμ΄λ²„ μ½”λ“œμ— MISRA-C, CERT-C 룰셋듀을 μ΄μš©ν•˜μ—¬ 정적뢄석을 ν•˜λŠ” 도쀑, strcpy 에 λŒ€ν•œ warning 을 μ–΄λ–»κ²Œ μ²˜λ¦¬ν• κΉŒ κ³ λ―Όν•˜λ‹€κ°€ LWN μ—μ„œ Ushering out strlcpy() λΌλŠ” 기사문을 읽게 λ˜μ—ˆλ‹€. string copy에 λŒ€ν•œ 글을 읽고 λΈ”λ‘œκ·Έμ— μ •λ¦¬ν•˜μžκ³  ν–ˆλŠ”λ° μ΄μ œμ„œμ•Ό 겨우 정리할 수 있게 λ˜μ—ˆλ‹€.

λ¦¬λˆ…μŠ€ μ»€λ„μ—μ„œ λ¬Έμžμ—΄ 볡사λ₯Ό μœ„ν•΄ λ§Œλ“€μ–΄μ§„ λ§€ν¬λ‘œλ“€μ€ λ‹€μ–‘ν•˜λ‹€. λͺ‡ 개의 μ‹œλ¦¬μ¦ˆ(?)κ°€ μžˆλŠ”λ° 정리해보면 λ‹€μŒκ³Ό κ°™λ‹€.

  1. strcpy
  2. strncpy
  3. strlcpy
  4. strscpy

strcpy πŸ”—

strcpyλ₯Ό λ‚˜νƒ€λ‚΄λ©΄ μ•„λž˜μ™€ 같이 κ°„λ‹¨ν•˜λ‹€.

1
2
3
4
5
6
7
strcpy(s, t)
    char *s, *t;
    {
        while (*s++ = *t++)
	    ;
    }
}

ν•˜μ§€λ§Œ 이 경우 λ°œμƒκ°€λŠ₯ν•œ λ¬Έμ œλŠ” destination 크기가 source보닀 μž‘μ„ 경우 overrun이 λ°œμƒν•œλ‹€λŠ” 점이닀. 이λ₯Ό κ°œμ„ ν•˜κ³ μž λ§Œλ“€μ–΄μ§„ 것이 strncpy 이닀.

strncpy πŸ”—

strncpyλŠ” μ•„λž˜μ™€ 같은 ν”„λ‘œν† νƒ€μž…μ„ κ°–λŠ”λ‹€.

1
char *strncpy(char *dest, char *src, size_t n);

λͺ…μ‹œμ μœΌλ‘œ λ³΅μ‚¬ν•˜κ³ μž ν•˜λŠ” 크기λ₯Ό 인자둜 λ„˜κ²¨μ£ΌκΈ° λ•Œλ¬Έμ— μ•žμ„œ strcpy처럼 overrun이 λ°œμƒν•  일이 거의 μ—†λ‹€. ν•˜μ§€λ§Œ 이처럼 λ¬Έμ œκ°€ μ—†μ–΄λ³΄μ΄λŠ” 데에도 잠재적인 λ¬Έμ œκ°€ μžˆλ‹€. μ•„λž˜μ˜ 두 가지 경우λ₯Ό μ‚΄νŽ΄λ³΄μž.

  1. 인자 n 보닀 source κ°€ 짧은 경우
  2. 인자 n 보닀 source κ°€ κΈΈ 경우

첫 번째 κ²½μš°μ—λŠ” source κ°€ 인자 n보닀 μž‘μ€λ°λ„ λΆˆκ΅¬ν•˜κ³  전체 arrayλ₯Ό λ³΅μ‚¬ν•˜κ²Œ λ˜λŠ” λΆˆν•„μš”ν•œ 연산이 λ°œμƒν•  수 μžˆλ‹€.

두 번째 κ²½μš°μ—λŠ” source κ°€ 인자 n보닀 큰 κ²½μš°μ΄λ‹€. 이 경우 destination 은 NULL 둜 λλ‚˜μ§€ μ•Šκ²Œ 돼 λ¬Έμžμ—΄λ‘œμ¨ μ‚¬μš©ν•  수 μ—†λ‹€. μ΄λŸ¬ν•œ 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μ‚¬μš© 버전이 strlcpy이닀.

strlcpy πŸ”—

BSD κ³„μ—΄μ˜ μ»€λ„μ—μ„œλŠ” strncpyλ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ strlcpyλ₯Ό κ΅¬ν˜„ν•˜μ˜€λ‹€.

1
size_t strlcpy(char *dest, const char *src, size_t n);

ν”„λ‘œν† νƒ€μž…μ€ strncpy와 λΉ„μŠ·ν•˜λ‹€. ν•˜μ§€λ§Œ strncpyμ™€μ˜ ν•œ 가지 차이점은 strlcpyλŠ” 항상 destination λ¬Έμžμ—΄μ΄ NUL-terminated λΌλŠ” 것을 보μž₯ν•œλ‹€λŠ” 점이닀. 그리고 λ°˜ν™˜κ°’μœΌλ‘œ src의 길이λ₯Ό λ°˜ν™˜ν•˜κΈ° λ•Œλ¬Έμ— *dest둜 λ°˜ν™˜λœ λ¬Έμžμ—΄κ³Ό λΉ„κ΅ν•¨μœΌλ‘œμ¨ μ •μƒμ μœΌλ‘œ λ¬Έμžμ—΄ 볡사가 μ΄λ€„μ‘ŒλŠ”μ§€ 비ꡐ할 수 μžˆλ‹€. ν•˜μ§€λ§Œ λ‹Ήμ‹œμ— λΉ„νš¨μœ¨μ μ΄λΌλŠ” 이유둜 glibc λ©”μΈν…Œμ΄λ„ˆμ™€ 컀널 κ°œλ°œμžλ“€μ—κ²Œλ„ strlcpyλŠ” ν™˜μ˜λ°›μ§€ λͺ»ν–ˆλ‹€.

This is horribly inefficient BSD crap. Using these function only leads to other errors. Correct string handling means that you always know how long your strings are and therefore you can you memcpy (instead of strcpy). Beside, those who are using strcat or variants deserved to be punished.

λ§žλŠ” 말이긴 ν•˜λ‹€. source λ¬Έμžμ—΄μ˜ 길이가 μ–Όλ§ˆμΈμ§€ μ•Œκ³  있기 λ•Œλ¬Έμ— λͺ…μ‹œμ μœΌλ‘œ ν•˜μžλ©΄ memcpyλ₯Ό μ΄μš©ν•˜λ©΄ λ˜μ§€ ꡳ이 strlcpyλ₯Ό μ΄μš©ν•΄κ°€λ©΄μ„œ λ°˜ν™˜κ°’μ„ 재차 *dest와 λΉ„κ΅ν•˜λŠ” μ½”λ“œλ₯Ό 지 ν•„μš”λŠ” μ—†λ‹€. ν•˜μ§€λ§Œ 이것보닀 더 μ€‘μš”ν•œ λͺ‡ 가지 결함이 μžˆλ‹€.

  1. μ‹€μ œ 데이터가 볡사될 수 μ—†λŠ” κ²½μš°μ—λ„ source λ¬Έμžμ—΄μ„ 읽어야 ν•œλ‹€.
  2. source λ¬Έμžμ—΄μ„ μ‹ λ’°ν•  수 μ—†λŠ” 경우(non-NUL terminated)λ₯Ό μ²˜λ¦¬ν•˜μ§€ λͺ»ν•œλ‹€.
  3. race condition 이 μ‘΄μž¬ν•œλ‹€.

strlen 을 μ΄μš©ν•΄ 전체 μ†ŒμŠ€ λ¬Έμžμ—΄μ˜ 길이λ₯Ό ν™•μΈν•˜κΈ° μœ„ν•΄ 읽어야 ν•˜λŠ” 문제점이 μ‘΄μž¬ν•˜κ³ , μ•„λž˜μ™€ 같이 κ΅¬ν˜„λ˜μ–΄ μžˆλŠ” strlcpy λŠ” λ§Œμ•½ source λ¬Έμžμ—΄μ΄ NUL둜 λλ‚˜μ§€ μ•ŠλŠ” μƒνƒœμΌ 경우 λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆλ‹€. μ‹€μ œ μ•„λž˜μ˜ μ½”λ“œλ₯Ό 보면 κ·ΈλŸ¬ν•œ κ²½μš°κ°€ λ°œμƒν–ˆμ„ λ•Œ ν΄λΌμ΄μ–ΈνŠΈ μͺ½μ—μ„œ μ•Œ 수 μžˆλŠ” 방법이 μ—†λ‹€.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
size_t strlcpy(char *dest, const char *src, size_t size)
{
	size_t ret = strlen(src);

	if (size) {
	    size_t len = (ret >= size) ? size - 1 : ret;
	    memcpy(dest, src, len);
	    dest[len] = '\0';
	}
	return ret;
}

λ˜ν•œ, race condition이 λ°œμƒν•  수 μžˆλ‹€. 이 뢀뢄은 μ–Έλœ» μƒκ°ν•˜μ§€ λͺ»ν•œ 뢀뢄인데, src 의 길이λ₯Ό κ°€μ Έμ˜€κ³  λ‚œ λ’€ μ€‘κ°„μ—μ„œ srcκ°€ λ°”λ€ŒλŠ” κ²½μš°μ—λŠ” 이λ₯Ό μ²˜λ¦¬ν•˜μ§€ λͺ»ν•œλ‹€.

strscpy πŸ”—

1
ssize_t strscpy(char *dest, const char *src, size_t count);

μ΄λŸ¬ν•œ 결점듀을 ν•΄κ²°ν•œ ν•¨μˆ˜κ°€ λ°”λ‘œ strscpy이닀. ν”„λ‘œν† νƒ€μž…λ§Œ 보면 λ‹€λ₯Έ 점이 μ—†λ‹€. 차이점은 λ°˜ν™˜κ°’μ— μžˆλ‹€. strlcpy와 달리 strscpyλŠ” λ³΅μ‚¬λœ λ¬Έμžλ“€μ˜ 개수λ₯Ό λ°˜ν™˜ν•œλ‹€λŠ” νŠΉμ§•μ΄ 있고 μ‹€μ œ κ΅¬ν˜„(https://elixir.bootlin.com/linux/v5.19.3/source/lib/string.cL151)을 μ‚΄νŽ΄λ³΄μ•˜μ„ λ•Œλ„ μœ„μ˜ κ°„λ‹¨ν•œ λ¬Έμžμ—΄ λ³΅μ‚¬λ°©λ²•κ³ΌλŠ” 사뭇 λ‹€λ₯΄λ‹€.

마치며 πŸ”—

ν˜„μž¬ κ°€μž₯ μ΅œμ‹  λ²„μ „μ˜ strscpy ν•¨μˆ˜μ—μ„œλŠ” kasan도 ν•¨κ»˜ 곡뢀해야 μ™„μ „ν•˜κ²Œ ν•¨μˆ˜λ₯Ό 이해할 수 μžˆμ„ 것 κ°™λ‹€. 이젠 ν•˜λ‹€ν•˜λ‹€ λ¬Έμžμ—΄ ν•˜λ‚˜ λ³΅μ‚¬ν•˜λŠ” ν•¨μˆ˜μ‘°μ°¨ μ‰½κ²Œ μ΄ν•΄ν•˜κΈ° νž˜λ“€μ–΄μ§ˆ μ§€κ²½κΉŒμ§€ 이λ₯΄λ €λ‹€. λ°°μ›Œλ„ κΉŒλ¨Ήμ–΄λ²„λ¦¬λ‹ˆ μ–Έμ  κ°„ λ‹€μ‹œ 이 글도 λ‹€μ‹œ 뒀적거릴 λ•Œκ°€ 올 것이닀.

좜처 πŸ”—