{"id":28909,"date":"2025-12-29T22:36:44","date_gmt":"2025-12-29T21:36:44","guid":{"rendered":"https:\/\/daniel.haxx.se\/blog\/?p=28909"},"modified":"2025-12-29T22:36:44","modified_gmt":"2025-12-29T21:36:44","slug":"no-strcpy-either","status":"publish","type":"post","link":"https:\/\/daniel.haxx.se\/blog\/2025\/12\/29\/no-strcpy-either\/","title":{"rendered":"no strcpy either"},"content":{"rendered":"\n<p>Some time ago I mentioned that we went through the curl source code and eventually got rid of all <code>strncpy<\/code>() calls.<\/p>\n\n\n\n<p>strncpy() is a weird function with a crappy API. It might not null terminate the destination and it <em>pads<\/em> the target buffer with zeroes. Quite frankly, most code bases are probably better off completely avoiding it because each use of it is a potential mistake.<\/p>\n\n\n\n<p>In that particular rewrite when we made strncpy calls extinct, we made <em>sure<\/em> we would either copy the full string properly or return error. It is rare that copying a partial string is the right choice, and if it is, we can just as well <code>memcpy<\/code> it and handle the null terminator explicitly. This meant no case for using strlcpy or anything such either.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69d8d7ff37627&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69d8d7ff37627\" class=\"aligncenter size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2668\" height=\"1501\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/daniel.haxx.se\/blog\/wp-content\/uploads\/2025\/12\/Screenshot-2025-12-29-at-17-08-28-curl-Project-status-dashboard.png\" alt=\"\" class=\"wp-image-28920\"\/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Enlarge\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><figcaption class=\"wp-element-caption\">strncpy density in curl over time<\/figcaption><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">But strcpy?<\/h2>\n\n\n\n<p>strcpy however, has its valid uses and it has a less bad and confusing API. The main challenge with strcpy is that when using it we do not specify the length of the target buffer nor of the source string.<\/p>\n\n\n\n<p>This is normally not a problem because in a C program <code>strcpy<\/code> should only be used when we have full control of both.<\/p>\n\n\n\n<p>But <em>normally<\/em> and <em>always<\/em> are not necessarily the same thing. We are but all human and we all do mistakes. Using strcpy implies that there is at least one or maybe two, buffer size checks done prior to the function invocation. In a good situation.<\/p>\n\n\n\n<p>Over time however &#8211; let&#8217;s imagine we have code that lives on for decades &#8211; when code is maintained, patched, improved and polished by many different authors with different mindsets and approaches, those size checks and the function invoke may glide apart. The further away from each other they go, the bigger is the risk that something happens in between that nullifies one of the checks or changes the conditions for the strcpy.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Enforce checks close to code<\/h2>\n\n\n\n<p>To make sure that the size checks cannot be separated from the copy itself we introduced a string copy replacement function the other day that takes the <em>target buffer<\/em>, <em>target size<\/em>, <em>source buffer<\/em> and <em>source string length<\/em> as arguments and only if the copy can be made and the null terminator also fits there, the operation is done.<\/p>\n\n\n\n<p>This made it possible to implement the replacement using memcpy(). Now we can completely ban the use of strcpy in curl source code, like we already did strncpy.<\/p>\n\n\n\n<p>Using this function version is a little more work and more cumbersome than strcpy since it needs more information, but we believe the upsides of this approach will help us have an oversight for the extra pain involved. I suppose we will see how that will fare down the road. Let&#8217;s come back in a decade and see how things developed!<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69d8d7ff379e6&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69d8d7ff379e6\" class=\"aligncenter size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2668\" height=\"1501\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/daniel.haxx.se\/blog\/wp-content\/uploads\/2025\/12\/Screenshot-2025-12-29-at-17-08-50-curl-Project-status-dashboard.png\" alt=\"\" class=\"wp-image-28922\"\/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Enlarge\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><figcaption class=\"wp-element-caption\">strcpy density in curl over time<\/figcaption><\/figure>\n<\/div>\n\n\n<pre class=\"wp-block-preformatted\">void curlx_strcopy(char *dest,<br>                   size_t dsize,<br>                   const char *src,<br>                   size_t slen)<br>{<br>  DEBUGASSERT(slen &lt; dsize);<br>  if(slen &lt; dsize) {<br>    memcpy(dest, src, slen);<br>    dest[slen] = 0;<br>  }<br>  else if(dsize)<br>    dest[0] = 0;<br>}<\/pre>\n\n\n\n<p><a href=\"https:\/\/github.com\/curl\/curl\/blob\/master\/lib\/curlx\/strcopy.c\">the strcopy source<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">AI slop<\/h2>\n\n\n\n<p>An additional minor positive side-effect of this change is of course that this should effectively prevent the AI chatbots to report strcpy uses in curl source code and insist it is insecure if anyone would ask (as people still apparently do). It has been proven numerous times already that strcpy in source code is like a honey pot for generating hallucinated vulnerability claims.<\/p>\n\n\n\n<p>Still, this will just make them find something else to make up a report about, so there is probably no net gain. AI slop is not a game we can win.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Some time ago I mentioned that we went through the curl source code and eventually got rid of all strncpy() calls. strncpy() is a weird function with a crappy API. It might not null terminate the destination and it pads the target buffer with zeroes. Quite frankly, most code bases are probably better off completely &hellip; <a href=\"https:\/\/daniel.haxx.se\/blog\/2025\/12\/29\/no-strcpy-either\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">no strcpy either<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":5,"featured_media":28357,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[33,428],"class_list":["post-28909","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-curl","tag-curl-and-libcurl","tag-security"],"_links":{"self":[{"href":"https:\/\/daniel.haxx.se\/blog\/wp-json\/wp\/v2\/posts\/28909","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/daniel.haxx.se\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/daniel.haxx.se\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/daniel.haxx.se\/blog\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/daniel.haxx.se\/blog\/wp-json\/wp\/v2\/comments?post=28909"}],"version-history":[{"count":21,"href":"https:\/\/daniel.haxx.se\/blog\/wp-json\/wp\/v2\/posts\/28909\/revisions"}],"predecessor-version":[{"id":28932,"href":"https:\/\/daniel.haxx.se\/blog\/wp-json\/wp\/v2\/posts\/28909\/revisions\/28932"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/daniel.haxx.se\/blog\/wp-json\/wp\/v2\/media\/28357"}],"wp:attachment":[{"href":"https:\/\/daniel.haxx.se\/blog\/wp-json\/wp\/v2\/media?parent=28909"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/daniel.haxx.se\/blog\/wp-json\/wp\/v2\/categories?post=28909"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/daniel.haxx.se\/blog\/wp-json\/wp\/v2\/tags?post=28909"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}