strncpy
tags: memmove, memset, strcpy, string.h, strlen, strncpy
strncpy
provides control over the same type of copying provided by
strcpy. Rather than being forced to copy an entire
string, you can choose to copy a portion of a string.
char *
strncpy(char *s1, const char *s2, size_t n);
s1
and s2
are the destination and source strings while n
is the maximum
number of characters to copy. The catch is that if no NUL is found in the first
n
characters then no NUL is added to the destination string. If the string is
less than n
characters long then null characters are appended so a total of
n
characters are written.
Local Variables
Similar to strcpy
, we'll store the length of the source string so we can use
it to perform error checking before attempting to copy. It will also be used to
add NULs when the source string is shorter than n
.
size_t len = strlen(s2);
Parameter Validation
The same validation can be used from strcpy
.
if ( !s1 ||
!s2 ||
/* Check for wrapping while copying */
((((unsigned long) -1) - ((unsigned long) s1)) < len) ||
((((unsigned long) -1) - ((unsigned long) s2)) < len))
{
return s1;
}
Implementation
When parameter validation succeeds there are two cases to handle: n
is less
than or equal to the source string length, or it is greater.
If n <= len
then we can use memmove
or memcpy
to copy n
characters from
s2
to s1
. We can't use strcpy
generically in this case because it will add
a NUL if n == len
, meaning we will have written n + 1
characters.
When n > len
we need to copy the string and then add null characters to pad
out the total number of written characters to n
. We can use strcpy
to copy
the string and then memset
to add the padding for us. Remember that strcpy
will set s1[len]
to NUL, so starting the memset
ats1 + len
would mean we
unnecessarily overwrite an existing NUL with NUL. Thus, we should start the
padding at s1 + len + 1
. Since n > len
then we guarantee that the result of
n - len - 1
is greater than or equal to zero and that the result will never
overflow.
Finally, we return s1
just like strcpy
.
if (n <= len)
{
memmove(s1, s2, n);
}
else
{
strcpy(s1, s2);
memset(s1 + len + 1, '\0', n - len - 1);
}
return s1;
Testing
Normal tests for strncpy
would include passing bad pointers and passing an n
which is less than strlen(s2)
. We also need to test copying from an empty
string, from a string which is shorter than n
(to verify the NUL padding), and
finally setting n
to zero.
int
strncpyTest(void)
{
int ret = 1;
char *str1 = NULL;
char *str2 = "hello, world";
char str3[15] = { 0 };
char *str4 = "";
char zeros[15] = { 0 };
do
{
/* Copy into NULL, may crash */
strncpy(str1, str2, strlen(str2) + 1);
/* Copy from NULL, may crash */
strncpy(str3, str1, sizeof(str3));
/* Should work the same as strcpy */
strncpy(str3, str2, strlen(str2) + 1);
if (0 != memcmp(str3, str2, strlen(str2) + 1))
{
break;
}
memset(str3, '\0', sizeof(str3));
/* Taint str3 and verify the taint is not touched */
str3[strlen(str2) - 4] = 'a';
strncpy(str3, str2, strlen(str2) - 4);
if ((0 != memcmp(str3, str2, strlen(str2) - 4)) ||
(str3[strlen(str2) - 4] != 'a'))
{
break;
}
/* Taint str3 completely and verify strncpy places NULs afterwards */
memset(str3, -1, sizeof(str3));
strncpy(str3, str2, sizeof(str3));
if ((0 != memcmp(str3, str2, strlen(str2))) ||
(0 != memcmp(str3 + strlen(str2),
zeros,
sizeof(str3) - strlen(str2))))
{
break;
}
/* Verify no copy is made from an empty string */
memset(str3, 0, sizeof(str3));
strncpy(str3, str4, strlen(str4));
if (0 != memcmp(str3, zeros, sizeof(str3)))
{
break;
}
ret = 0;
} while (0);
return ret;
}
Conclusion
This implementation makes use of memmove
, strcpy
, and memset
to carry out
the tasks dictated be the relation of n
to strlen(s2)
. Our tests then verify
this behavior through various taint methods.
char *
strncpy(char *s1, const char *s2, size_t n)
{
size_t len = strlen(s2);
if ( !s1 ||
!s2 ||
/* Check for wrapping while copying */
((((unsigned long) -1) - ((unsigned long) s1)) < len) ||
((((unsigned long) -1) - ((unsigned long) s2)) < len))
{
return s1;
}
if (n <= len)
{
memmove(s1, s2, n);
}
else
{
strcpy(s1, s2);
memset(s1 + len + 1, '\0', n - len - 1);
}
return s1;
}