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;
}
comments powered by Disqus