39

I'm porting some code to Windows, and the Microsoft compiler (Visual C++ 8) is telling me that strerror() is unsafe.

Putting aside the annoyance factor in all the safe string stuff from Microsoft, I can actually see that some of the deprecated functions are dangerous. But I can't understand what could be wrong with strerror(). It takes a code (int), and returns the corresponding string, or the empty string if that code is not known.

Where is the danger?

Is there a good alternative in C?

Is there a good alternative in C++?

[edit]

Having had some good answers, and now understanding that some implementations may be crazy enough to actually write to a common shared buffer - unsafe to reentrancy within a single-thread, never mind between threads! - my question stops being "Why can't I use it, and what are the alternatives?" to "Are there any decent, succinct alternatives in C and/or C++?"

Thanks in advance

JamieH
  • 1,257
  • 3
  • 12
  • 19
  • 38
    You can't use it because Microsoft says "the ISO C standard be damned - we won't let you use it, unless you override the warning or error with a pragma". They've also banned memcpy() - that is ridiculous because you tell it how many bytes to copy and if you can't think about it and know there is enough room in the target space for the number of bytes before calling memcpy(), you don't belong in a team writing code in C or C++. – Jonathan Leffler May 23 '09 at 00:02
  • @JonathanLeffler The ISO C standard has the same problem, and solves it in the same way. They solved the unsafe function by adding `strerror_r`, while Microsoft solved the unsafe function by adding `strerror_s`. Either way: if you're calling `strerror` you're doing it wrong; and please stop. – Ian Boyd Jun 01 '20 at 17:55
  • @IanBoyd: ISO C did not solve it with [`strerror_r()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror_r.html); that is a POSIX function, not a standard C function. Standard C has [`strerror_s()`](http://port70.net/~nsz/c/c11/n1570.html#K.3.7.4.2) defined as an optional feature in Annex K of C11 and C18. – Jonathan Leffler Jun 01 '20 at 18:03
  • @JonathanLeffler We have a cross-compiler standard function? Excellent! *(be sure to deprecate any use of the old one)* – Ian Boyd Jun 01 '20 at 18:56
  • 2
    @IanBoyd — No; we still don't have a cross-compiler standard function. No compiler (library) implements Annex K fully accurately, not even the MS compilers. MS doesn't implement POSIX-specific functions (such as `strerror_r()`) in general; non-Windows compilers don't implement Windows-specific functions (such as `str_error_s()`) in general. The only cross-platform standard function is `strerror()`, but apparently some implementations go out of their way to make it thread-unsafe, which seems more than a tad silly but the industry isn't renowned for its adherence to common sensee. – Jonathan Leffler Jun 01 '20 at 18:58
  • As long as we all agree: stop using `strerror`, which MVSC tells you. They even try to warn you to not shoot your own foot. But in the end: you're the one with the gun and the pragmas. – Ian Boyd Jun 01 '20 at 19:05

8 Answers8

35

strerror is deprecated because it's not thread-safe. strerror works on an internal static buffer, which may be overwritten by other, concurrent threads. You should use a secure variant called strerror_s.

The secure variant requires that the buffer size be passed to the function in order to validate that the buffer is large enough before writing to it, helping to avoid buffer overruns that could allow malicious code to execute.

Rachid K.
  • 4,490
  • 3
  • 11
  • 30
dfa
  • 114,442
  • 31
  • 189
  • 228
  • 4
    Er, the question was: _Why_ it is considered unsafe? You've told me precisely nothing. – JamieH May 22 '09 at 23:06
  • 26
    He _did_ tell you why. It works on an internal static buffer - that is, one that is shared across threads. That's unsafe. – nsayer May 22 '09 at 23:09
  • 5
    Ah, yes. My apologies. Now, the next question - to the world at large - is: why in the heck would anyone implement it like that!? – JamieH May 22 '09 at 23:16
  • 3
    JamieH, the function could be efficiently implemented as char *msg[] = { "file not found", "you are ill", "you are't root", "time is over" }; char *strerror(int n) { return msg[n]; } – Johannes Schaub - litb May 22 '09 at 23:28
  • litb - I figured it would be like that (though with a bit more sophistication to match code with string) Andrew C - LOL! – JamieH May 22 '09 at 23:40
  • 1
    @litb and @JamieH: the simplest implementation is only marginally more sophisticated than the outline. One problem comes with locales - if the messages need to be localized, etc. – Jonathan Leffler May 22 '09 at 23:58
  • 8
    It's all about locales - the string returned from strerror needs to remain valid until strerror is called again, even if you change locale (which might unload the old locale). So returning a pointer into the locale data means the implementation needs to keep the old locale hanging around. I assume it's doable, but not as simple as you'd think. – Steve Jessop May 23 '09 at 00:58
  • indeed, i was posting only the very rough plan. obviously it's more complicated in RealLife :) – Johannes Schaub - litb May 23 '09 at 01:53
  • 1
    I thought it was simply that strerror() goes back to before Unix had threads – Edward Falk Dec 10 '17 at 03:58
  • it's actually pretty hard to make it threadsafe 1. without mutexes 2 .without duplicating memory excessively and 3. locale aware. pick any 2 and it's easy. – Erik Aronesty Mar 25 '19 at 22:14
  • @Erik, it seems to me like leaving the strerror table loaded would be a small price to pay for a thread-safe strerror(). I can understand the decision to fully unload the tables in 1995 but I’m not sure why 2019 Windows, which requires a baseline of gigabytes of memory, cannot afford to keep at most some kilobytes of locale strings resident, likely backed by memory shared by all programs. How often does a program unload a locale anyway? The “lost” memory potential seems quite small. – Conrad Meyer Apr 30 '19 at 14:46
23

strerror by itself is not unsafe. In the olden days before threading it simply wasn't a problem. With threads, two or more threads could call strerror leaving the returned buffer in an undefined state. For single-threaded programs, it shouldn't hurt to use strerror unless they're playing some weird games in libc, like common memory for all apps in a DLL.

To address this there's a new interface to the same functionality:

int strerror_r(int errnum, char *buf, size_t buflen);

Note that the caller provides the buffer space and the buffer size. This solves the issue. Even for single-threaded applications, you might as well use it. It won't hurt a bit, and you might as well get used to doing it the safer way.

NOTE: the above prototype is from the POSIX spec for strerror_r(). It may vary per platform or with compiler options or #define symbols. GNU, for instance, makes that or their own version available depending on a #define.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
dwc
  • 24,196
  • 7
  • 44
  • 55
  • "In the olden days before threading it simply wasn't a problem". It's not re-entrant either, but then it never claimed to be callable from signal handlers. – Steve Jessop May 23 '09 at 01:01
  • No problem, just `longjmp` to it from the signal handler ;) – M.M Apr 01 '14 at 21:22
  • 1
    Note that there is no safe size available to preallocate the `strerror_r` output buffer. Technically correct programs must use a malloc loop until a sufficiently large buffer is not rejected by the function. This is a criticism of the function supposedly addressed in the optional annex K function `strerror_s`. – Conrad Meyer Apr 30 '19 at 14:42
  • @ConradMeyer Theoretically yes, but for example the Linux man page states that `The GNU C Library uses a buffer of 1024 characters for strerror(). This buffer size therefore should be sufficient to avoid an ERANGE error when calling strerror_r() and strerror_l().` – Claudiu Mar 31 '21 at 15:08
  • @Claudiu The OP's question originated from porting software to Windows, so I think portability beyond Linux is probably desirable in this context. – Conrad Meyer Apr 01 '21 at 18:08
18

Having had some good answers, and now understanding that some implementations may be crazy enough to actually write to a common shared buffer - unsafe to reentrancy within a single-thread, never mind between threads! - my question stops being "Why can't I use it, and what are the alternatives?" to "Are there any decent, succinct alternatives in C and/or C++?"

Posix specifies strerror_r(), and on Windows you can use strerror_s(), which is a bit different but has the same goal. I do this:

#define BAS_PERROR(msg, err_code)\
  bas_perror(msg, err_code, __FILE__, __LINE__)

void bas_perror (const char* msg, int err_code, const char* filename,
                 unsigned long line_number);


void
bas_perror (const char* usr_msg, int err_code, const char* filename,
            unsigned long line_number)
{
  char sys_msg[64];

#ifdef _WIN32
  if ( strerror_s(sys_msg, sizeof sys_msg, err_code) != 0 )
  {
    strncpy(sys_msg, "Unknown error", taille);
    sys_msg[sizeof sys_msg - 1] = '\0';
  }
#else
  if ( strerror_r(err_code, sys_msg, sizeof sys_msg) != 0 )
  {
    strncpy(sys_msg, "Unknown error", sizeof sys_msg);
    sys_msg[sizeof sys_msg - 1] = '\0';
  }
#endif

  fprintf(stderr, "%s: %s (debug information: file %s, at line %lu)\n",
          usr_msg, sys_msg, filename, line_number);
}

I wrote this function because Posix threads functions don't modify errno, they return an error code instead. So this function is basically the same as perror(), except that it allows you to provide an error code other than errno, and also displays some debugging information. You can adapt it to your need.

Bastien Léonard
  • 60,478
  • 20
  • 78
  • 95
  • Consider using the `errx` or `warnx` functions from the `err.h` family of functions instead, if it is sufficiently portable for your programs. It comes from BSD in the 90s and I believe Linux has it. The ability to use printf format strings is often useful. You could wrap it with a macro to inject FILE:LINE, if that’s useful for you. – Conrad Meyer Apr 30 '19 at 14:34
5

You can not rely on the string that is returned by strerror() because it may change with the next call to the function. The previously returned values may become obsolete then. Especially in multi-threaded environments, you can not ensure that the string is valid when you access it.

Imagine this:

Thread #1:
char * error = strerror(1);
                                    Thread #2
                                    char * error = strerror(2);
printf(error);

Depending on the implementation of strerror(), this code prints out the error code for error code 2, not for error code 1.

beef2k
  • 2,233
  • 2
  • 19
  • 20
  • 7
    By what mechanism would it change? Presumably the strings are defined - statically - in the internals of the implementation of the C runtime library, and thus will never change? Or is that wrong, and they may indeed change on a dynamic basis? – JamieH May 22 '09 at 23:08
  • 1
    Why would you assume this? Yes, it COULD be done like this, but it COULD be done completely different - we don't know ;) .. – beef2k May 22 '09 at 23:11
  • 3
    The main problem is that Posix doesn't require strerror() to be thread-safe. You have to use strerror_r() instead. On Windows use strerror_s(). – Bastien Léonard May 22 '09 at 23:24
  • 1
    See dfa's response above. The problem is that another thread could change the locale, causing the strings to be unloaded and replaced, causing the previous return from strerror() to become an invalid pointer. Or as Bastian points out: because the standard says it's not thread safe. – Edward Falk Jan 12 '18 at 16:29
1

I understand other answer, but I think showing with code is more clear.

check glibc's implementation (we should get similar code in MS lib)

/* Return a string describing the errno code in ERRNUM.
   The storage is good only until the next call to strerror.
   Writing to the storage causes undefined behavior.  */
libc_freeres_ptr (static char *buf);

When the errnum is not kind of known error, it have to generate string like "Unknown error 41". This string is NOT constant, but generate to an allocated buffer. And the buf is global var. so its content may change when call strerror again with lock. That's why it's thread-unsafe.

On the other hand, strerror_r(int errnum, char *buf, size_t buflen), it generate the error string to the argument buf. so there is NO global resource now. That's why it's thread-safe.

ref: https://github.com/liuyang1/glibc/blob/master/string/strerror.c#L23-L26

liuyang1
  • 1,575
  • 1
  • 15
  • 23
  • This is the only answer that mentions the actual reason: that the string in general has to be dynamically constructed, because it includes the error code when it is unknown. It isn't desirable to have a library with a string for every possible code. Without that difficulty, you could just return a pointer to a static string. (The issue of loading another locale would be completely solvable without excess memory usage on platforms that support lazy loading of dynamic libraries: just put each locale's strings in a dynamic library, so that it will only be loaded if needed.) – Daira Hopwood Mar 31 '23 at 12:16
1

For a succinct wrapper, you can use STLSoft's stlsoft::error_desc, as in:

std::string errstr = stlsoft::error_desc(errno);

Looking at the code, it seems that it's implemented in terms of strerror(), which means it'll be safe for reentrancy within a thread (i.e. if used multiple times within a given statement), but it does not address the multithreading problem.

They seem to operate pretty rapid release cycles for defects, so you could try requesting a mod?

dcw
  • 3,481
  • 2
  • 22
  • 30
0

The specification of this function is somehow underspecified in field of thread-safety. Eg. man strerror(3):

This string must not be modified by the application, but may be modified by a subsequent call to strerror().

However, Windows API and GLIBC versions of strerror use thread local storage, so are perfectly safe to use.

-1

Although I do not know Microsoft's reasons, I note that strerror returns a non-const char *, which means that there is a risk that some Merry Prankster has called strerror before you did and modified the message.

Thomas L Holaday
  • 13,614
  • 6
  • 40
  • 51
  • 2
    If if it's "const char *" the same Merry Prankster might try to cast to (char *) and change the code after that ;) .. Declaring something as "const" does not mean that it cannot be changed. It just gives an optimization hint to the compiler. – beef2k May 22 '09 at 23:08
  • 4
    I agree that, from a pure const-ness perspective, this is true, but I strongly suspect that the lack of const is just historical, and users are obliged not to alter the contents of the string in the same way as they are with many such non-const-but-should-be parts of the standard library. _If_ that is the case, then I can still see no reason for strerror()'s deprecation. – JamieH May 22 '09 at 23:09
  • 1
    making it const char* and saying the thing must not be changed would be enough to make it thread safe *if* you make sure you have synchronized the calls so that uses of it don't interleave. In any case, you can't forbid the programmer to make his program crash. He could still create an array and write out of array bounds. and could kill the other threads using Terminate or something :) Making the return a const char* is an effective protection against accidental changes. – Johannes Schaub - litb May 22 '09 at 23:23
  • 2
    Real Programmers put static consts in ROM. – Thomas L Holaday May 23 '09 at 00:07