A Tale of strlcpy()

Posted on Apr 5, 2021

Oh… my… what a week! Apart from my Busy VIP Girl Life, in the code department it’s been rough. You might’ve seen some of it on my sourcehut feed… but let’s concentrate on cras today, because I think it’ll be interesting for you all folks.

So, cras had a very nasty bug. I don’t feel like doing a huge analysis of it, because it’s actually trivial: string overflows yielded very, very bad things. These overflows were caused by me using strncpy() to copy things from a string to another.

Yes, I know… strncpy() is blatantly wrong. It is slightly better than its predecessor strcpy() because it explicitly asks for an maximum number of bytes to copy… but: if it hits that maximum, it doesn’t null-terminate the destination string… elsewhere, if it finds a null terminator in the source string, it stops copying and zero-pads the destination string. All of this is because of some historical reason I don’t fully understand related to copying legacy UNIX directory paths, apparently?

In summary, strncpy() does either of these things, regarding null terminators: it fills your string with trailing zeros or it doesn’t null-terminate your string. It does what you think it should only when you’re copying exactly one byte less than the destination string allocated size.

I was aware of these caveats, I thought I was dealing with that as I should… truth is I was doing a bad job trying to “patch” the behavior of a procedure I shouldn’t have been using in the first place.

To safely copy a string from one place to the other there are a couple of alternatives.

  1. strlen() + memcpy()
  2. snprintf()
  3. strlcpy()

The so-called strlen() + memcpy() idiom is essentially the same as strncpy() but without the confusion. You know memcpy() is very dumb and just copies stuff over. It doesn’t null-terminate the destination variable because it’s actually meant to be useful to move any kind of memory: it asks for void * pointers and always copies over the amount of bytes you ask it to no questions asked. It can get cumbersome though.

snprintf() is really nice. All stdio.h procedures guarantee they null-terminate the destination string. However, there’s one general catch that might be inconvenient for you… and there is one specific catch that made it inconvenient for me to use it in cras.

The first catch is that snprintf() is C99. If you care about this sort of stuff and restrict yourself to C89 (there are good reasons to do so), you can’t use it. I’m in the pro-C99 team, though, so this doesn’t affect me.1 Great!

My specific catch was that the interface of snprintf() radically differs from strncpy()’s: the order of arguments is different and it requires a printf() format string (as we’re just copying things over, it’d been %s). To replace all the calls I wanted to replace would’ve been a bit harder than using a simple substitution command.

Enter strlcpy(). This one is, so to speak, what strncpy() should be in the first place: it copies a string from one variable to the other, limited to a certain amount of bytes and is guaranteed to null-terminate the destination string. Moreover, it allows you to check whether more characters have been left uncopied from the source string by looking at its return value. And its parameters list looks identical to strncpy()’s! A simple call to sed 's/strncpy/strlcpy/g' src.c and you’re done! Sweet! Oh, wait… there must be a catch, isn’t there?

Yep, there is one… As many of the best things in C, strlcpy() comes from BSD-land. On BSD systems you get it by default if _BSD_SOURCE is set. On other POSIX like systems like Linux, you need libbsd… and the header file is bsd/string.h, whereas on BSDs it’s just string.h… So, unless you’re willing to deal with quite a bunch of #ifdef’s in your source to fake portability,2 what do you do?

Go suckless, baby! Writing your own strlcpy() is feasible, but given that we’re trying to avoid memory related bugs here… Using a tried and tested implementation is way safer than rolling your own in-house version of it. Luckily, quite a bunch of suckless projects provide a nice strlcpy.c file under a non-obstrusive license, namely the ICS License… So I grabbed the one from sbase, removed the estrlcpy() function I didn’t need, recreated the header to my needs, and voilà safe copying of strings in my code!

This is the beauty of FOSS: being able to improve your own work building upon the work of better coders than you are. And this is also the beauty of good FOSS: easily to integrate into your own code! Awesome! A big thank you to Todd C. Miller, author of the module, and everyone who has helped preserving it… just by using it!

I feel like there should be a conclusion to this post? Not sure. I just wanted to share the good feelings in this sunny Monday!

P.S.: Oh, people in charge of the C Standard… maybe instead of adding weird stuff to C, adding strlcpy() would make more sense? Just asking!

  1. I know some people call C99 an abomination and call for exclusively using C89. There’s a lot to get into there. ↩︎

  2. Using conditional compiling is not writing portable code! It’s just using a compiler gimmick to smash together different codebases into one so they compile… only one on those cases you’ve considered. Any system you leave out might show compatibility issues! Portable code is guaranteed to work on any system, even those you don’t know about but abide to the standards. ↩︎