strncpy and strncat for copying strings in C

Recently, I’ve read an interesting article 1, explaining why the strncpy function in C is not safer than strcpy. The post was very interesting, but what’s more, it suggested an alternative idiom for copying strings in C, that might probably be the way to go.

Lat­er, in an­oth­er ar­ti­cle 2 that com­pared some func­tion­al­i­ty in C, Python and Go, one of the com­ments point­ed out that very same id­iom. That grabbed my at­ten­tion, so I de­cid­ed to try it in an ex­am­ple.

The problem with strncpy seems to be the way it manages the source string to be copied. Based on the sample code provided in the documentation 3 (that should be just a reference), the break condition is up to n characters (the third parameter) or until the source string is exhausted, whatever happens first. This should not be a problem, unless n < strlen(source_string). That parameter would make strncpy to finish before it can put a \0 character at the end of the target string, leaving an invalid array of characters 5.

This is an ex­am­ple.

#in­clude <st­dio.h>  /* std­out, fprintf */
#in­clude <string.h>  /* strncpy, strlen */
#de­fine TAR­GET 10
int main(int argc, char* argv[]) {
    char *src = "Castle";    /* 6 chars long */
    char dst[TAR­GET] = "__________";
    dst[TAR­GET - 1] = '\0';
    fprintf(std­out, "%s\n", dst);     /* must be: '_________' */
    /* What hap­pens if I pass a wrong length (low­er than the ac­tu­al
    * str­len) */
    strncpy(dst, src, 3);
    fprintf(std­out, "%s\n", dst);     /* must be: 'Cas______' */
    /* If I copy the string cor­rect­ly, by pass­ing the right
    * length, then str­c­n­py be­haves as ex­pect­ed */
    strncpy(dst, src, strlen(src) + 1);
    fprintf(std­out, "%s\n", dst);     /* must be: 'Castle' */
    re­turn 0;
}

On this example, the target array is represented by the variable dst, and I used a fixed-length string, on purpose for the demonstration, simulating what would actually happen. I null-terminated it so the program can finish successfully, because otherwise the operations on it would not end until the delimiter is reached, and we cannot know when that will happen, considering what’s in memory at that time. In addition, the unpredictable behaviour will lead to errors, and probably to memory corruption. The underscore, should be interpreted as slots: regions or reserved memory that are there, but empty.

The proposed idiom uses strncat (see 4), tricking the function by passing it an empty string as the first parameter, and then the actual string we need to copy. This call will render the same result, but without the previous side effect. Let’s see an example:

#in­clude <st­dio.h>  /* std­out, fprintf */
#in­clude <string.h>  /* strn­cat, strlen */
#de­fine TAR­GET 10
int main(int argc, char* argv[]) {
    char *src = "Castle";    /* 6 chars long */
    char dst[TAR­GET] = "__________";
    dst[TAR­GET - 1] = '\0';
    fprintf(std­out, "%s\n", dst);     /* must be: '_________' */
    /* Pre­pare des­ti­na­tion string */
    dst[0] = '\0';
    /* Copy with strn­cat */
    strn­cat(dst, src, 3);
    fprintf(std­out, "%s\n", dst);     /* must be: 'Cas' */
    /* If I copy the string cor­rect­ly, by pass­ing the right
    * length, then str­c­n­py be­haves as ex­pect­ed */
    dst[0] = '\0';
    strn­cat(dst, src, strlen(src) + 1);
    fprintf(std­out, "%s\n", dst);     /* must be: 'Castle' */
    /* If I try to over­run the buf­fer */
    dst[0] = '\0';
    strn­cat(dst, src, strlen(src) + 10);
    fprintf(std­out, "%s\n", dst);     /* must be: 'Castle' */
    re­turn 0;
}

Here we see, the er­ror is no longer pre­sen­t, prob­a­bly be­cause of the dif­fer­ence on the im­ple­men­ta­tion (the snip­pet on the doc­u­men­ta­tion 4 gives us a hint on what it does, so we can spot the change).

This might seem as a little issue, but it raised some concerns on the Linux kernel development, at the point that a new function was developed. The strscpy function is being included in the Kernel development for Linux 4.3-rc4 6 because it is a better interface. Some of the problems mentioned in the commit message, that inspired this new version, are the ones described on the previous paragraphs.

This makes me wonder, if this should be the “correct” way for performing this operation “safely” in C. In all cases, the error is the same (not checking the boundaries, and trusting the input), and should be avoided. What I mean by this, is that we cannot simply rely on those functions being secure, the security must be in our code, so the proper way to handle these situations is to code defensively: do not trust user input, always check the boundaries, error codes, memory allocation, status of the pointer (a free for every malloc but not for a NULL pointer, etc.).

1

http­s://the-flat-­tran­tor-­so­ci­ety.blogspot.­com.ar/2012/03/no-strncpy-is-not-safer­-str­cpy.html.

2

http­s://blog.­surgut.­co.uk/2015/08/­go-en­joy-python3.html.

3

strncpy doc­u­men­ta­tion.

4(1,2)

strn­cat man­u­al page.

5

An ar­ray of char­ac­ters that is not nul­l-ter­mi­nat­ed, is in­valid.

6

http­s://git.k­er­nel.org/cgit/lin­ux/k­er­nel/git/­tor­vald­s/lin­ux.git/­com­mit/?id=30c44659f4a3e7e1f9f47e895591b4b40bf62671.