Skip to content

Commit

Permalink
utils: provide safer strncpy function
Browse files Browse the repository at this point in the history
The function checks for source and destination lengths provided.
  • Loading branch information
LukasWoodtli committed Jan 4, 2025
1 parent 36b8ce5 commit 13e8a3d
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 0 deletions.
22 changes: 22 additions & 0 deletions core/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -1167,3 +1167,25 @@ size_t utils_strnlen(const char *str, size_t max_size) {
return pos - str;
}
}

void utils_strncpy(char *dest, const size_t dest_size, const char *src, const size_t src_size) {
if (src == NULL || dest == NULL) {
return;
}

size_t bytes_to_write = dest_size;
size_t actual_src_len = utils_strnlen(src, src_size);

if (actual_src_len < bytes_to_write) {
bytes_to_write = actual_src_len;
}
memmove(dest, src, bytes_to_write);
if (bytes_to_write < dest_size) {
dest[bytes_to_write] = '\0';
}

// Do this always. Just to be sure!
if (dest_size > 0) {
dest[dest_size - 1] = '\0';
}
}
11 changes: 11 additions & 0 deletions core/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,15 @@ lwm2m_client_t *utils_findClient(lwm2m_context_t *contextP, void *fromSessionH);
*/
size_t utils_strnlen(const char *str, size_t max_size);

/** A safer version of `strncpy`. Copies at most src_size bytes to dest, but checks for available space.
*
* If dest is not NULL and dest_size is not 0 the destination buffer is always terminated with a '\0'.
*
* @param dest The char buffer where to copy the string.
* @param dest_size The size of the destination buffer.
* @param src The source string.
* @param src_size The size of the source string buffer. The effective source string can be shorter.
*/
void utils_strncpy(char *dest, const size_t dest_size, const char *src, const size_t src_size);

#endif /* WAKAAMA_UTILS_H */
91 changes: 91 additions & 0 deletions tests/core_utils_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,90 @@ void test_strnlen_max_len_5(void) {
CU_ASSERT_EQUAL(len, 3);
}

void test_strncpy_null(void) {
char dest[] = {'a', 'b', 'c'};
utils_strncpy(dest, sizeof(dest), NULL, 1);
CU_ASSERT_NSTRING_EQUAL(dest, "abc", sizeof(dest));

// The following tests are not asserting anything.
// Sanitizers are used to check for undefined behaviour and leaks.
utils_strncpy(NULL, 99, "xyz", 1);
utils_strncpy(NULL, 5, NULL, 3);
}

void test_strncpy_dest_0_src_0(void) {
char dst[] = {'a', 'b', 'c'};
const char *src = "xyz";
utils_strncpy(dst, 0, src, 0);
CU_ASSERT_NSTRING_EQUAL(dst, "abc", sizeof(dst));
}

void test_strncpy_dest_1_src_0(void) {
char dst[] = {'a', 'b', 'c'};
const char *src = "xyz";
utils_strncpy(dst, 1, src, 0);
CU_ASSERT_EQUAL(dst[0], '\0');
CU_ASSERT_EQUAL(strlen(dst), 0); // NOSONAR
CU_ASSERT_EQUAL(utils_strnlen(dst, sizeof(dst)), 0);
}

void test_strncpy_dest_sizeof_src_0(void) {
char dst[] = {'a', 'b', 'c'};
const char *src = "xyz";
utils_strncpy(dst, sizeof(dst), src, 0);
CU_ASSERT_EQUAL(dst[0], '\0');
CU_ASSERT_EQUAL(strlen(dst), 0); // NOSONAR
CU_ASSERT_EQUAL(utils_strnlen(dst, sizeof(dst)), 0);
}

void test_strncpy_dest_sizeof_src_1(void) {
char dst[] = {'a', 'b', 'c'};
const char *src = "xyz";
utils_strncpy(dst, sizeof(dst), src, 1);
CU_ASSERT_NSTRING_EQUAL(dst, "x", 1);
CU_ASSERT_EQUAL(dst[1], '\0');
CU_ASSERT_EQUAL(strlen(dst), 1); // NOSONAR
CU_ASSERT_EQUAL(utils_strnlen(dst, sizeof(dst)), 1);
}

void test_strncpy_dest_sizeof_src_2(void) {
char dst[] = {'a', 'b', 'c'};
const char *src = "xyz";
utils_strncpy(dst, sizeof(dst), src, 2);
CU_ASSERT_NSTRING_EQUAL(dst, "xy", 2);
CU_ASSERT_EQUAL(dst[2], '\0');
CU_ASSERT_EQUAL(strlen(dst), 2); // NOSONAR
CU_ASSERT_EQUAL(utils_strnlen(dst, sizeof(dst)), 2);
}

void test_strncpy_dest_sizeof_src_3(void) {
char dst[] = {'a', 'b', 'c'};
const char *src = "xyz";
utils_strncpy(dst, sizeof(dst), src, 3);
// only 2 characters and NULL has space in dst
CU_ASSERT_NSTRING_EQUAL(dst, "xy", 2);
CU_ASSERT_EQUAL(dst[2], '\0');
CU_ASSERT_EQUAL(strlen(dst), 2); // NOSONAR
CU_ASSERT_EQUAL(utils_strnlen(dst, sizeof(dst)), 2);
}

void test_strncpy_dest_sizeof_src_4(void) {
char dst[] = {'a', 'b', 'c'};
char src[10];
memset(src, '\0', sizeof(src));
src[0] = 'u';
src[1] = 'v';
src[2] = 'w';
const size_t src_len = sizeof(src);
CU_ASSERT_EQUAL(src_len, 10);
utils_strncpy(dst, sizeof(dst), src, src_len);
// only 2 characters and NULL has space in dst
CU_ASSERT_NSTRING_EQUAL(dst, "uv", 2);
CU_ASSERT_EQUAL(dst[2], '\0');
CU_ASSERT_EQUAL(strlen(dst), 2); // NOSONAR
CU_ASSERT_EQUAL(utils_strnlen(dst, sizeof(dst)), 2);
}

static struct TestTable table[] = {
{"test_strnlen_null()", test_strnlen_null},
{"test_strnlen_0()", test_strnlen_0},
Expand All @@ -89,6 +173,13 @@ static struct TestTable table[] = {
{"test_strnlen_max_len_3()", test_strnlen_max_len_3},
{"test_strnlen_max_len_4()", test_strnlen_max_len_4},
{"test_strnlen_max_len_5()", test_strnlen_max_len_5},
{"test_strncpy_null()", test_strncpy_null},
{"test_strncpy_dest_0_src_0()", test_strncpy_dest_0_src_0},
{"test_strncpy_dest_1_src_0()", test_strncpy_dest_1_src_0},
{"test_strncpy_dest_sizeof_src_0()", test_strncpy_dest_sizeof_src_0},
{"test_strncpy_dest_sizeof_src_1()", test_strncpy_dest_sizeof_src_1},
{"test_strncpy_dest_sizeof_src_3()", test_strncpy_dest_sizeof_src_3},
{"test_strncpy_dest_sizeof_src_4()", test_strncpy_dest_sizeof_src_4},
{NULL, NULL},
};

Expand Down

0 comments on commit 13e8a3d

Please sign in to comment.