[tz] Minor (unimportant really) technical UB bug in strftime() ?

Paul Eggert eggert at cs.ucla.edu
Fri Nov 11 20:53:02 UTC 2022


On 2022-11-10 16:11, Tom Lane wrote:

> simple modifications of the
> strftime() case can provide pretty good examples.
I'm not sure I'm following this argument, but if I understand it 
correctly I disagree. I very much much want implementations to reject 
attempts to use uninitialized data, and I don't want the C standard to 
prohibit such implementations. Use of uninitialized data is a serious 
problem in real applications - several CVEs have arisen from it - and 
the C standard shouldn't require implementations to sweep it under the rug.

Let me try to make this more concrete. Suppose we are on a platform with 
no trap representations (which is the typical case), and suppose we make 
the following patch to tzcode's implementation of strftime with "%s":

	--- a/strftime.c
	+++ b/strftime.c
	@@ -319,16 +319,7 @@
					time_t) + 1];
		time_t		mkt;

	-	tm.tm_sec = t->tm_sec;
	-	tm.tm_min = t->tm_min;
	-	tm.tm_hour = t->tm_hour;
	-	tm.tm_mday = t->tm_mday;
	-	tm.tm_mon = t->tm_mon;
	-	tm.tm_year = t->tm_year;
	-	tm.tm_isdst = t->tm_isdst;
	-#if defined TM_GMTOFF && ! UNINIT_TRAP
	-	tm.TM_GMTOFF = t->TM_GMTOFF;
	-#endif
	+	memcpy(&tm, t, sizeof tm);
		mkt = mktime(&tm);
		/* If mktime fails, %s expands to the
		   value of (time_t) -1 as a failure

and suppose someone calls tzcode strftime this way:

   struct buf[1000];
   char *f(void)
   {
     struct tm uninitialized;
     return strftime(buf, sizeof buf, "%s", &tm);
   }

This call is obviously buggy since it is passing an uninitialized struct 
tm to strftime. However, the C standard says the patched strftime's 
memcpy initializes strftime's local variable tm, and therefore mktime's 
use of tm's contents is defined and the implementation allow the 
program's use of uninitialized data.

Whatever the reason for this peculiarity of the C standard (and nobody 
seems to remember why it's there), it's counterproductive. In general C 
programs shouldn't use uninitialized data. If they do, it's useful to 
have a debugging implementation that reports any such use the instant 
that it happens as this will help catch real bugs. And since common 
debugging implementations like valgrind and UBSan are not following the 
C standard here, this suggests that the C standard is mistaken, not the 
implementations.


> That leads either to strftime() knowing
> much more about mktime()'s innards than it ought to

It's not a problem for strftime, since strftime's has to know what's 
needed in the struct tm; this follows from strftime's API.

It might be a problem for other scenarios, such as the one you vaguely 
suggested though I don't know the details. However, even assuming these 
other scenarios exist, they don't justify this peculiarity of the C 
standard. The standard should not say that the ordinary meaning of 
"don't use uninitialized variables" is wrong and that there's some 
trickier interpretation that hardly any programmer knows and that was 
wrong even in C17 and so needed rewording (and perhaps still needs 
rewording) and that nevertheless must be supported - even though 
important debugging implementations disagree.


More information about the tz mailing list