A Tale Of strlcpy()
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
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
related to copying legacy UNIX directory paths,
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.
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
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
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.
strlcpy(). This one is, so to speak, what
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
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
under a non-obstrusive license, namely the ICS License… So I grabbed 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,
strlcpy() would make more sense? Just asking!
I know some people call C99 an abomination and call for exclusively using C89. There’s a lot to get into there. ↩︎
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. ↩︎