Link to home
Create AccountLog in
Avatar of TexanLonghorn
TexanLonghornFlag for United States of America

asked on

Bug with return value of strlcat()

I am using strlcat() and strlcpy() in the software we are developing for a project. I adobted the version written by "itsmeandnobodyelse" on Jan. 16 (see enclosed code snippet), since the definition does not come with standard Linux. I had a frustrating day of writing unit tests for this code. I developed the tests in Cygwin as I wanted to validate that the native implementation and our implementation work the same. However, I find that Cygwin doesn't work according to the available documentation. When tried on Mac and Linux I found the following:

Lets start with the return values section of Apple's man page:

RETURN VALUES
The strlcpy() and strlcat() functions return the total length of the string they tried to create.

Sun even documents this in better detail. The return value is:
min(sz, strlen(dest)) + strlen(src)

That seems simple enough, so what are the results of the tests. The dest in my tests is a 5 character array. I will represent the contents of this as a string literal.

0 == strlcat( "", "", 0 ) yes it does

3 == strlcat( "", "bbb", 0 ) no it returns 0

2 == strlcat( "aa", "", 5 ) yes it does

2 == strlcat( "", "bb", 5 ) yes it does

4 == strlcat( "aa", "bb", 5 ) yes it does

5 == strlcat( "", "abcde", 5 ) no this returns 4

9 == strlcat( "aaaa", "abcde", 5 ) no this returns 4

? == strlcat( "aa", "abcd", 1 )

Well what should the last one return. The case of dest not having a NULL within size characters is an error condition. I think that this should return 5 (size + strlen(src) ). But, no matter what I think, it actually returns 0.

So of course this means that the example that they give in the man page does not work.

if ( strlcpy( pname, dir, sizeof( pname ) ) >= sizeof( pname ) )
goto toolong;

As we can see from my example above, strlcat( "aaaa", "abcde", 5) only returns 4. And last I checked 4 is less than 5.

I'll have to try this out on a Sun Solaris to see what it says.

Questions:
1. Where the code came from?
2. Is it the same souce code used in Cygwin, Sun and Apple? and if so, is it a bug that needs to be reported?
3. What is the best approach to fix the above problem?
/* global.h */
#ifndef GLOBAL_H
#define GLOBAL_H
 
#ifdef __cplusplus
#include <cstring>
extern "C"
{
#else
#include <string.h>
#endif
 
inline
size_t strlcat(char *dst, const char *src, size_t siz)
{
        size_t ld = strlen(dst);
        size_t ls = strlen(src);
        if (ld >= siz)
             return 0;
        if (ld + ls >= siz)
            ls = siz - ld -1;
        strncat(dst, src, ls);
        return ld + ls; 
}
 
#ifdef __cplusplus
}
#endif
 
#endif

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of ozo
ozo
Flag of United States of America image

Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
SOLUTION
Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
SOLUTION
Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
Avatar of TexanLonghorn

ASKER

>> The strlcpy() and strlcat() functions return the total length of the string they tried to create. For strlcpy() that means the length of src. For strlcat() that means the initial length of dst plus the length of src.

Yes, this is correct. But this is not how strlcat is working on any of the platforms. As Sun documents it on their man page strlcat is supposed to return min(sz, strlen(dest) ) + strlen(src) where I find that it returns min( (sz - 1), ( strlen(dest) + strlen(src) ) ). That means it never returns a value longer than size - 1, so you never know if the string was truncated. The following are some examples from the unit test function I wrote:
=================================
     memset( dest, 0, 5);
 
     stat = strlcat(dest, "abcde", 5 );
     sprintf(errorStr, "Failed: expected 4 received %d", stat );
     ASSERT( stat == 4, errorStr );
 
     /* FIXME this also does not follow the doc. An attempted buffer
      * overrun did not return a length longer than the buffer.
     sprintf(errorStr, "Failed: expected 5 received %d", stat );
     ASSERT( stat == 5, errorStr );
     */
=================================
    memset( dest, 0, 5);
    memset( dest, 'a', 2);
 
    stat = strlcat(dest, "bcdefgh", 5 );
    sprintf(errorStr, "Failed: expected 4 received %d", stat );
    ASSERT( stat == 4, errorStr );
 
    /* FIXME this also does not follow the doc. An attempted buffer
     * overrun did not return a length longer than the buffer.
    sprintf(errorStr, "Failed: expected 9 received %d", stat );
    ASSERT( stat == 9, errorStr );
    */
=================================
    memset( dest, 0, 5);
    memset( dest, 'a', 2);
 
    stat = strlcat(dest, "abcd", 1 );
    sprintf(errorStr, "Failed: expected 0 received %d", stat );
    ASSERT( stat == 0, errorStr );
 
    /* FIXME this also does not follow the doc. An attempted buffer
     * overrun did not return a length longer than the buffer.
    sprintf(errorStr, "Failed: expected 6 received %d", stat );
    ASSERT( stat == 6, errorStr );
    */

Open in new window

SOLUTION
Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
It seems to me that the implementation of the strlcpy() and strlcat() functions has always been a controversial issue as mentioned by Wikipedia. You probably read what it said about them: "It has been noted that they are non-standard, that there are implementation differences between the BSD and Solaris implementations, and that no study has demonstrated that they lead to safer or more-secure software than using standard C functions. Furthermore, some, including Ulrich Drepper, argue that strlcpy and strlcat make truncation errors easier for a programmer to ignore and thus can introduce more bugs than they remove; consequently, these functions have not been added to the GNU C Library. Others have expressed concern regarding the risks of truncation when using any string function involving static allocation." In which case, if the functions are none-standard, then this leaves the door wide open to different implementations on different platforms.
SOLUTION
Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
May I ask why you gave a B grade ? That usually means that something was missing in the answers and/or that something is still unclear. If so, feel free to ask about that, and we'll be happy to help you further ...