diff options
| author | jvoisin | 2026-05-01 00:36:32 +0200 |
|---|---|---|
| committer | jvoisin | 2026-05-01 00:44:53 +0200 |
| commit | ddd22b2f533db9c0da0bb262fbafa51f67c8587e (patch) | |
| tree | d319dab03de20929f95ccf7f9bec8c428ab6a66b /tests | |
| parent | d6105aba5fd791e8d3f069e771517cdb947b5604 (diff) | |
Fix strncat/wcsncat
Previously, no checks were done when __n <= __b, but strncat _appends_ after
existing content, making this a overly broad check check. For example, with an
8-byte buffer containing "12345\0", strncat(buf, "ABCD", 4) would have the
check skipped, but the result "12345ABCD\0" is 10 bytes, resulting in an
overflow.
This commit fixes this oversight, and adds a bunch of tests.
Diffstat (limited to '')
| -rw-r--r-- | tests/Makefile | 10 | ||||
| -rw-r--r-- | tests/test_mbsnrtowcs_dynamic.c | 4 | ||||
| -rw-r--r-- | tests/test_strncat_dynamic_write.c | 2 | ||||
| -rw-r--r-- | tests/test_strncat_n_eq_buf.c | 18 | ||||
| -rw-r--r-- | tests/test_strncat_n_gt_buf.c | 17 | ||||
| -rw-r--r-- | tests/test_strncat_n_lt_buf.c | 18 | ||||
| -rw-r--r-- | tests/test_strncat_n_one.c | 18 | ||||
| -rw-r--r-- | tests/test_strncat_safe.c | 34 | ||||
| -rw-r--r-- | tests/test_strncat_static_write.c | 10 | ||||
| -rw-r--r-- | tests/test_wcsncat_n_eq_buf.c | 18 | ||||
| -rw-r--r-- | tests/test_wcsncat_n_gt_buf.c | 17 | ||||
| -rw-r--r-- | tests/test_wcsncat_n_lt_buf.c | 18 | ||||
| -rw-r--r-- | tests/test_wcsncat_n_one.c | 18 | ||||
| -rw-r--r-- | tests/test_wcsncat_safe.c | 34 | ||||
| -rw-r--r-- | tests/test_wcsnrtombs_dynamic.c | 4 |
15 files changed, 227 insertions, 13 deletions
diff --git a/tests/Makefile b/tests/Makefile index 6904b2d..9bedd16 100644 --- a/tests/Makefile +++ b/tests/Makefile | |||
| @@ -84,6 +84,11 @@ RUNTIME_TARGETS= \ | |||
| 84 | test_strlcpy_dynamic_write \ | 84 | test_strlcpy_dynamic_write \ |
| 85 | test_strlcpy_static_write \ | 85 | test_strlcpy_static_write \ |
| 86 | test_strncat_dynamic_write \ | 86 | test_strncat_dynamic_write \ |
| 87 | test_strncat_n_eq_buf \ | ||
| 88 | test_strncat_n_gt_buf \ | ||
| 89 | test_strncat_n_lt_buf \ | ||
| 90 | test_strncat_n_one \ | ||
| 91 | test_strncat_safe \ | ||
| 87 | test_strncat_static_write \ | 92 | test_strncat_static_write \ |
| 88 | test_strncpy_dynamic_write \ | 93 | test_strncpy_dynamic_write \ |
| 89 | test_strncpy_static_write \ | 94 | test_strncpy_static_write \ |
| @@ -101,6 +106,11 @@ RUNTIME_TARGETS= \ | |||
| 101 | test_wcsnrtombs_static \ | 106 | test_wcsnrtombs_static \ |
| 102 | test_wcscat_static_write \ | 107 | test_wcscat_static_write \ |
| 103 | test_wcscpy_static_write \ | 108 | test_wcscpy_static_write \ |
| 109 | test_wcsncat_n_eq_buf \ | ||
| 110 | test_wcsncat_n_gt_buf \ | ||
| 111 | test_wcsncat_n_lt_buf \ | ||
| 112 | test_wcsncat_n_one \ | ||
| 113 | test_wcsncat_safe \ | ||
| 104 | test_wcsncat_static_write \ | 114 | test_wcsncat_static_write \ |
| 105 | test_wcsncpy_static_write \ | 115 | test_wcsncpy_static_write \ |
| 106 | test_wmemcpy_dynamic_read \ | 116 | test_wmemcpy_dynamic_read \ |
diff --git a/tests/test_mbsnrtowcs_dynamic.c b/tests/test_mbsnrtowcs_dynamic.c index 77b9082..58575d3 100644 --- a/tests/test_mbsnrtowcs_dynamic.c +++ b/tests/test_mbsnrtowcs_dynamic.c | |||
| @@ -14,9 +14,7 @@ int main(int argc, char** argv) { | |||
| 14 | srcp = src; | 14 | srcp = src; |
| 15 | mbsnrtowcs(buffer, &srcp, 2, 2, &st); | 15 | mbsnrtowcs(buffer, &srcp, 2, 2, &st); |
| 16 | 16 | ||
| 17 | /* Unsafe: ask to write argc (10) wide chars into 4-element buffer. | 17 | /* Unsafe: ask to write argc (10) wide chars into 4-element buffer. */ |
| 18 | * Before the fix, the else branch clamped source bytes instead of | ||
| 19 | * the output wide-char count, allowing destination overflow. */ | ||
| 20 | CHK_FAIL_START | 18 | CHK_FAIL_START |
| 21 | srcp = src; | 19 | srcp = src; |
| 22 | memset(&st, 0, sizeof(st)); | 20 | memset(&st, 0, sizeof(st)); |
diff --git a/tests/test_strncat_dynamic_write.c b/tests/test_strncat_dynamic_write.c index d5c5a94..c538339 100644 --- a/tests/test_strncat_dynamic_write.c +++ b/tests/test_strncat_dynamic_write.c | |||
| @@ -7,11 +7,9 @@ int main(int argc, char** argv) { | |||
| 7 | strncat(buffer, "1234567", 5); | 7 | strncat(buffer, "1234567", 5); |
| 8 | puts(buffer); | 8 | puts(buffer); |
| 9 | 9 | ||
| 10 | #if 0 | ||
| 11 | CHK_FAIL_START | 10 | CHK_FAIL_START |
| 12 | strncat(buffer, argv[1], argc); | 11 | strncat(buffer, argv[1], argc); |
| 13 | CHK_FAIL_END | 12 | CHK_FAIL_END |
| 14 | #endif | ||
| 15 | 13 | ||
| 16 | puts(buffer); | 14 | puts(buffer); |
| 17 | return ret; | 15 | return ret; |
diff --git a/tests/test_strncat_n_eq_buf.c b/tests/test_strncat_n_eq_buf.c new file mode 100644 index 0000000..bbc39d2 --- /dev/null +++ b/tests/test_strncat_n_eq_buf.c | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | #include "common.h" | ||
| 2 | |||
| 3 | #include <string.h> | ||
| 4 | |||
| 5 | int main(int argc, char** argv) { | ||
| 6 | char buffer[8] = {0}; | ||
| 7 | strncat(buffer, "12345", 5); | ||
| 8 | puts(buffer); | ||
| 9 | |||
| 10 | /* n == buffer size but overflow due to existing content. | ||
| 11 | * buffer has 5 chars, src "ABC" (len 3): 5+3+1 = 9 > 8 → overflow. */ | ||
| 12 | CHK_FAIL_START | ||
| 13 | strncat(buffer, "ABC", 8); | ||
| 14 | CHK_FAIL_END | ||
| 15 | |||
| 16 | puts(buffer); | ||
| 17 | return ret; | ||
| 18 | } | ||
diff --git a/tests/test_strncat_n_gt_buf.c b/tests/test_strncat_n_gt_buf.c new file mode 100644 index 0000000..cca226b --- /dev/null +++ b/tests/test_strncat_n_gt_buf.c | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | #include "common.h" | ||
| 2 | |||
| 3 | #include <string.h> | ||
| 4 | |||
| 5 | int main(int argc, char** argv) { | ||
| 6 | char buffer[8] = {0}; | ||
| 7 | strncat(buffer, "12345", 5); | ||
| 8 | puts(buffer); | ||
| 9 | |||
| 10 | /* n > buffer size and overflow: n=10, buffer has 5 chars. */ | ||
| 11 | CHK_FAIL_START | ||
| 12 | strncat(buffer, "1234567890", 10); | ||
| 13 | CHK_FAIL_END | ||
| 14 | |||
| 15 | puts(buffer); | ||
| 16 | return ret; | ||
| 17 | } | ||
diff --git a/tests/test_strncat_n_lt_buf.c b/tests/test_strncat_n_lt_buf.c new file mode 100644 index 0000000..35a0ba1 --- /dev/null +++ b/tests/test_strncat_n_lt_buf.c | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | #include "common.h" | ||
| 2 | |||
| 3 | #include <string.h> | ||
| 4 | |||
| 5 | int main(int argc, char** argv) { | ||
| 6 | char buffer[8] = {0}; | ||
| 7 | strncat(buffer, "12345", 5); | ||
| 8 | puts(buffer); | ||
| 9 | |||
| 10 | /* n < buffer size but overflow due to existing content. | ||
| 11 | * buffer has 5 chars, src "ABCD" (len 4), min(4,3)=3: 5+3+1 = 9 > 8. */ | ||
| 12 | CHK_FAIL_START | ||
| 13 | strncat(buffer, "ABCD", 3); | ||
| 14 | CHK_FAIL_END | ||
| 15 | |||
| 16 | puts(buffer); | ||
| 17 | return ret; | ||
| 18 | } | ||
diff --git a/tests/test_strncat_n_one.c b/tests/test_strncat_n_one.c new file mode 100644 index 0000000..fce46bd --- /dev/null +++ b/tests/test_strncat_n_one.c | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | #include "common.h" | ||
| 2 | |||
| 3 | #include <string.h> | ||
| 4 | |||
| 5 | int main(int argc, char** argv) { | ||
| 6 | char buffer[8] = {0}; | ||
| 7 | strncat(buffer, "1234567", 7); | ||
| 8 | puts(buffer); | ||
| 9 | |||
| 10 | /* n=1, buffer has 7 chars ("1234567"). | ||
| 11 | * 7+1+1 = 9 > 8 → overflow. Even n=1 can overflow a nearly-full buffer. */ | ||
| 12 | CHK_FAIL_START | ||
| 13 | strncat(buffer, "X", 1); | ||
| 14 | CHK_FAIL_END | ||
| 15 | |||
| 16 | puts(buffer); | ||
| 17 | return ret; | ||
| 18 | } | ||
diff --git a/tests/test_strncat_safe.c b/tests/test_strncat_safe.c new file mode 100644 index 0000000..ff8cadd --- /dev/null +++ b/tests/test_strncat_safe.c | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | #include "common.h" | ||
| 2 | |||
| 3 | #include <string.h> | ||
| 4 | |||
| 5 | int main(int argc, char** argv) { | ||
| 6 | char buffer[8] = {0}; | ||
| 7 | |||
| 8 | /* Safe: empty buffer, append 7 chars with n=7 → "1234567\0" = exactly 8 bytes */ | ||
| 9 | strncat(buffer, "1234567", 7); | ||
| 10 | puts(buffer); | ||
| 11 | |||
| 12 | /* Safe: reset and append with n larger than source. | ||
| 13 | * src is "AB" (len 2), n=100 → only 2 chars copied + NUL = 3 bytes */ | ||
| 14 | buffer[0] = '\0'; | ||
| 15 | strncat(buffer, "AB", 100); | ||
| 16 | puts(buffer); | ||
| 17 | |||
| 18 | /* Safe: partially filled, append fits exactly. | ||
| 19 | * buffer = "ABCD" (4 chars), append "EFG" with n=3 → 4+3+1 = 8 = exact fit */ | ||
| 20 | buffer[0] = '\0'; | ||
| 21 | strncat(buffer, "ABCD", 4); | ||
| 22 | strncat(buffer, "EFG", 3); | ||
| 23 | puts(buffer); | ||
| 24 | |||
| 25 | /* Safe: n limits copy to fit. | ||
| 26 | * buffer = "ABCDE" (5 chars), src = "FGHIJKLM" (8 chars), n=2 → 5+2+1 = 8 */ | ||
| 27 | buffer[0] = '\0'; | ||
| 28 | strncat(buffer, "ABCDE", 5); | ||
| 29 | strncat(buffer, "FGHIJKLM", 2); | ||
| 30 | puts(buffer); | ||
| 31 | |||
| 32 | /* All safe operations passed without trapping */ | ||
| 33 | return 0; | ||
| 34 | } | ||
diff --git a/tests/test_strncat_static_write.c b/tests/test_strncat_static_write.c index 7fe89ff..53d1532 100644 --- a/tests/test_strncat_static_write.c +++ b/tests/test_strncat_static_write.c | |||
| @@ -4,15 +4,15 @@ | |||
| 4 | 4 | ||
| 5 | int main(int argc, char** argv) { | 5 | int main(int argc, char** argv) { |
| 6 | char buffer[8] = {0}; | 6 | char buffer[8] = {0}; |
| 7 | char src[] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; | 7 | strncat(buffer, "12345", 5); |
| 8 | strncat(buffer, src, 5); | ||
| 9 | puts(buffer); | 8 | puts(buffer); |
| 10 | 9 | ||
| 11 | #if 0 | 10 | /* n=4 is less than buffer size (8), but buffer already has 5 chars, |
| 11 | * so appending 4 more + NUL = 10 bytes total, overflowing the buffer. | ||
| 12 | */ | ||
| 12 | CHK_FAIL_START | 13 | CHK_FAIL_START |
| 13 | strncat(buffer, src, 10); | 14 | strncat(buffer, "ABCD", 4); |
| 14 | CHK_FAIL_END | 15 | CHK_FAIL_END |
| 15 | #endif | ||
| 16 | 16 | ||
| 17 | puts(buffer); | 17 | puts(buffer); |
| 18 | return ret; | 18 | return ret; |
diff --git a/tests/test_wcsncat_n_eq_buf.c b/tests/test_wcsncat_n_eq_buf.c new file mode 100644 index 0000000..e516842 --- /dev/null +++ b/tests/test_wcsncat_n_eq_buf.c | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | #include "common.h" | ||
| 2 | |||
| 3 | #include <wchar.h> | ||
| 4 | |||
| 5 | int main(int argc, char** argv) { | ||
| 6 | wchar_t buffer[8] = {0}; | ||
| 7 | wcsncat(buffer, L"12345", 5); | ||
| 8 | printf("%ls\n", buffer); | ||
| 9 | |||
| 10 | /* n == buffer capacity but overflow due to existing content. | ||
| 11 | * buffer has 5 wchars, src L"ABC" (len 3): 5+3+1 = 9 > 8 → overflow. */ | ||
| 12 | CHK_FAIL_START | ||
| 13 | wcsncat(buffer, L"ABC", 8); | ||
| 14 | CHK_FAIL_END | ||
| 15 | |||
| 16 | printf("%ls\n", buffer); | ||
| 17 | return ret; | ||
| 18 | } | ||
diff --git a/tests/test_wcsncat_n_gt_buf.c b/tests/test_wcsncat_n_gt_buf.c new file mode 100644 index 0000000..186b8c0 --- /dev/null +++ b/tests/test_wcsncat_n_gt_buf.c | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | #include "common.h" | ||
| 2 | |||
| 3 | #include <wchar.h> | ||
| 4 | |||
| 5 | int main(int argc, char** argv) { | ||
| 6 | wchar_t buffer[8] = {0}; | ||
| 7 | wcsncat(buffer, L"12345", 5); | ||
| 8 | printf("%ls\n", buffer); | ||
| 9 | |||
| 10 | /* n > buffer capacity and overflow: n=10, buffer has 5 wchars. */ | ||
| 11 | CHK_FAIL_START | ||
| 12 | wcsncat(buffer, L"1234567890", 10); | ||
| 13 | CHK_FAIL_END | ||
| 14 | |||
| 15 | printf("%ls\n", buffer); | ||
| 16 | return ret; | ||
| 17 | } | ||
diff --git a/tests/test_wcsncat_n_lt_buf.c b/tests/test_wcsncat_n_lt_buf.c new file mode 100644 index 0000000..99a42de --- /dev/null +++ b/tests/test_wcsncat_n_lt_buf.c | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | #include "common.h" | ||
| 2 | |||
| 3 | #include <wchar.h> | ||
| 4 | |||
| 5 | int main(int argc, char** argv) { | ||
| 6 | wchar_t buffer[8] = {0}; | ||
| 7 | wcsncat(buffer, L"12345", 5); | ||
| 8 | printf("%ls\n", buffer); | ||
| 9 | |||
| 10 | /* n < buffer capacity but overflow due to existing content. | ||
| 11 | * buffer has 5 wchars, src L"ABCD" (len 4), min(4,3)=3: 5+3+1 = 9 > 8. */ | ||
| 12 | CHK_FAIL_START | ||
| 13 | wcsncat(buffer, L"ABCD", 3); | ||
| 14 | CHK_FAIL_END | ||
| 15 | |||
| 16 | printf("%ls\n", buffer); | ||
| 17 | return ret; | ||
| 18 | } | ||
diff --git a/tests/test_wcsncat_n_one.c b/tests/test_wcsncat_n_one.c new file mode 100644 index 0000000..23de28e --- /dev/null +++ b/tests/test_wcsncat_n_one.c | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | #include "common.h" | ||
| 2 | |||
| 3 | #include <wchar.h> | ||
| 4 | |||
| 5 | int main(int argc, char** argv) { | ||
| 6 | wchar_t buffer[8] = {0}; | ||
| 7 | wcsncat(buffer, L"1234567", 7); | ||
| 8 | printf("%ls\n", buffer); | ||
| 9 | |||
| 10 | /* n=1, buffer has 7 wchars. | ||
| 11 | * 7+1+1 = 9 > 8 → overflow. Even n=1 can overflow a nearly-full buffer. */ | ||
| 12 | CHK_FAIL_START | ||
| 13 | wcsncat(buffer, L"X", 1); | ||
| 14 | CHK_FAIL_END | ||
| 15 | |||
| 16 | printf("%ls\n", buffer); | ||
| 17 | return ret; | ||
| 18 | } | ||
diff --git a/tests/test_wcsncat_safe.c b/tests/test_wcsncat_safe.c new file mode 100644 index 0000000..00fc8e6 --- /dev/null +++ b/tests/test_wcsncat_safe.c | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | #include "common.h" | ||
| 2 | |||
| 3 | #include <wchar.h> | ||
| 4 | |||
| 5 | int main(int argc, char** argv) { | ||
| 6 | wchar_t buffer[8] = {0}; | ||
| 7 | |||
| 8 | /* Safe: empty buffer, append 7 wide chars → exactly fills buffer */ | ||
| 9 | wcsncat(buffer, L"ABCDEFG", 7); | ||
| 10 | printf("%ls\n", buffer); | ||
| 11 | |||
| 12 | /* Safe: reset, append with n larger than source. | ||
| 13 | * src is L"AB" (len 2), n=100 → only 2 wchars copied */ | ||
| 14 | buffer[0] = L'\0'; | ||
| 15 | wcsncat(buffer, L"AB", 100); | ||
| 16 | printf("%ls\n", buffer); | ||
| 17 | |||
| 18 | /* Safe: partially filled, append fits exactly. | ||
| 19 | * buffer = L"ABCD" (4 wchars), append L"EFG" n=3 → 4+3+1 = 8 = exact fit */ | ||
| 20 | buffer[0] = L'\0'; | ||
| 21 | wcsncat(buffer, L"ABCD", 4); | ||
| 22 | wcsncat(buffer, L"EFG", 3); | ||
| 23 | printf("%ls\n", buffer); | ||
| 24 | |||
| 25 | /* Safe: n limits copy to fit. | ||
| 26 | * buffer = L"ABCDE" (5 wchars), src long, n=2 → 5+2+1 = 8 = exact fit */ | ||
| 27 | buffer[0] = L'\0'; | ||
| 28 | wcsncat(buffer, L"ABCDE", 5); | ||
| 29 | wcsncat(buffer, L"FGHIJKLM", 2); | ||
| 30 | printf("%ls\n", buffer); | ||
| 31 | |||
| 32 | /* All safe operations passed without trapping */ | ||
| 33 | return 0; | ||
| 34 | } | ||
diff --git a/tests/test_wcsnrtombs_dynamic.c b/tests/test_wcsnrtombs_dynamic.c index 808c9c8..28b03bf 100644 --- a/tests/test_wcsnrtombs_dynamic.c +++ b/tests/test_wcsnrtombs_dynamic.c | |||
| @@ -14,9 +14,7 @@ int main(int argc, char** argv) { | |||
| 14 | srcp = src; | 14 | srcp = src; |
| 15 | wcsnrtombs(buffer, &srcp, 4, 4, &st); | 15 | wcsnrtombs(buffer, &srcp, 4, 4, &st); |
| 16 | 16 | ||
| 17 | /* Unsafe: ask to write argc (10) bytes into 8-byte buffer. | 17 | /* Unsafe: ask to write argc (10) bytes into 8-byte buffer. */ |
| 18 | * Before the fix, the first branch incorrectly divided the byte-sized | ||
| 19 | * buffer capacity by sizeof(wchar_t), making the check too permissive. */ | ||
| 20 | CHK_FAIL_START | 18 | CHK_FAIL_START |
| 21 | srcp = src; | 19 | srcp = src; |
| 22 | memset(&st, 0, sizeof(st)); | 20 | memset(&st, 0, sizeof(st)); |
