Code in "asctime" such as... { long y; y = timeptr->tm_year + (long) TM_YEAR_BASE; if (y >= -999 && y <= 9999) (void) sprintf(buf, "%.3s %.3s%3d %02d:%02d:%02d %ld\n", wn, mn, timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, y); else (void) sprintf(buf, "%.3s %.3s%3d %ld\n", wn, mn, timeptr->tm_mday, y); } ...never returns NULL and never (when applied to a "struct tm" derived from a 64-bit time_t) overflows a 26-character buffer passed to asctime. It does lose time-of-day information in the distant past and distant future. --ado
"Olson, Arthur David (NIH/NCI)" <olsona@dc37a.nci.nih.gov> writes:
if (y >= -999 && y <= 9999) (void) sprintf(buf, "%.3s %.3s%3d %02d:%02d:%02d %ld\n", wn, mn, timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, y); else (void) sprintf(buf, "%.3s %.3s%3d %ld\n", wn, mn, timeptr->tm_mday, y);
...never returns NULL and never (when applied to a "struct tm" derived from a 64-bit time_t) overflows a 26-character buffer passed to asctime.
True, but it would still overflow the 26-byte buffer in other cases, e.g., if tm_mday is out of range. And even if you had a valid struct tm, it would still overflow in some currently-theoretical environments, e.g., 64-bit int and 128-bit long and time_t. (While we're fixing this, let's fix it permanently. :-) Also, I'd rather have asctime_r return NULL when the result doesn't fit. That's what POSIX says to do when asctime_r fails, and it's what HP-UX asctime_r does. If you want asctime_r to avoid overflowing a 26-byte buffer, the simplest way is to use snprintf instead of sprintf. Something like the following should do the trick. =================================================================== RCS file: RCS/Makefile,v retrieving revision 2004.1 retrieving revision 2004.1.0.1 diff -pu -r2004.1 -r2004.1.0.1 --- Makefile 2004/03/19 19:48:35 2004.1 +++ Makefile 2004/07/22 20:05:59 2004.1.0.1 @@ -96,6 +96,7 @@ LDLIBS= # -DHAVE_SETTIMEOFDAY=1 if settimeofday has just 1 arg (SVR4) # -DHAVE_SETTIMEOFDAY=2 if settimeofday uses 2nd arg (4.3BSD) # -DHAVE_SETTIMEOFDAY=3 if settimeofday ignores 2nd arg (4.4BSD) +# -DHAVE_SNPRINTF=0 if your system lacks the snprintf function # -DHAVE_STRERROR=0 if your system lacks the strerror function # -DHAVE_SYMLINK=0 if your system lacks the symlink function # -DHAVE_SYS_STAT_H=0 if your compiler lacks a "sys/stat.h" =================================================================== RCS file: RCS/private.h,v retrieving revision 2003.5 retrieving revision 2003.5.0.1 diff -pu -r2003.5 -r2003.5.0.1 --- private.h 2003/12/15 14:36:35 2003.5 +++ private.h 2004/07/22 20:06:11 2003.5.0.1 @@ -46,6 +46,10 @@ static char privatehid[] = "@(#)private. #define HAVE_SETTIMEOFDAY 3 #endif /* !defined HAVE_SETTIMEOFDAY */ +#ifndef HAVE_SNPRINTF +#define HAVE_SNPRINTF 1 +#endif /* !defined HAVE_STRERROR */ + #ifndef HAVE_STRERROR #define HAVE_STRERROR 1 #endif /* !defined HAVE_STRERROR */ =================================================================== RCS file: RCS/asctime.c,v retrieving revision 2004.1 retrieving revision 2004.1.0.2 diff -pu -r2004.1 -r2004.1.0.2 --- asctime.c 1998/05/28 13:56:06 2004.1 +++ asctime.c 2004/07/22 20:03:28 2004.1.0.2 @@ -14,14 +14,52 @@ static char elsieid[] = "@(#)asctime.c 7 #include "private.h" #include "tzfile.h" +#ifndef EOVERFLOW +# define EOVERFLOW EINVAL +#endif + /* ** A la ISO/IEC 9945-1, ANSI/IEEE Std 1003.1, Second Edition, 1996-07-12. */ -char * -asctime_r(timeptr, buf) +/* +** Big enough for something such as +** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n +** (two three-character abbreviations, five strings denoting integers, +** three explicit spaces, two explicit colons, a newline, +** and a trailing ASCII nul). +*/ +#define MAX_ASCTIME_SIZE (3 * 2 + 5 * INT_STRLEN_MAXIMUM(int) + 3 + 2 + 1 + 1) + +#if !HAVE_SNPRINTF +/* +** A substitute for snprintf that is good enough for asctime. +*/ +static int +snprintf(buf, size, format, mday, hour, min, sec, year) +char * buf; +size_t size; +const char * format; +int mday, hour, min, sec; +long year; +{ + char tbuf[MAX_ASCTIME_SIZE]; + size_t len; + (void) sprintf(tbuf, buf, size, format, mday, hour, min, sec, year); + len = strlen(tbuf); + if (len < size) { + (void) strcpy(buf, tbuf); + return len; + } else + return -1; +} +#endif + +static char * +asctime_rn(timeptr, buf, size) register const struct tm * timeptr; char * buf; +size_t size; { static const char wday_name[][3] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" @@ -41,17 +79,29 @@ char * buf; else mn = mon_name[timeptr->tm_mon]; /* ** The X3J11-suggested format is - ** "%.3s %.3s%3d %02.2d:%02.2d:%02.2d %d\n" - ** Since the .2 in 02.2d is ignored, we drop it. + ** "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n" + ** Use "%02d", as it is a bit more portable than "%.2d". */ - (void) sprintf(buf, "%.3s %.3s%3d %02d:%02d:%02d %d\n", + if (snprintf(buf, size, "%.3s %.3s%3d %02d:%02d:%02d %ld\n", wn, mn, timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, - TM_YEAR_BASE + timeptr->tm_year); + timeptr->tm_year + (long) TM_YEAR_BASE) + < 0) { + errno = EOVERFLOW; + return NULL; + } return buf; } +char * +asctime_r(timeptr, buf) +register const struct tm * timeptr; +char * buf; +{ + return asctime_rn(timeptr, buf, 26); +} + /* ** A la X3J11, with core dump avoidance. */ @@ -60,15 +110,7 @@ char * asctime(timeptr) register const struct tm * timeptr; { - /* - ** Big enough for something such as - ** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n - ** (two three-character abbreviations, five strings denoting integers, - ** three explicit spaces, two explicit colons, a newline, - ** and a trailing ASCII nul). - */ - static char result[3 * 2 + 5 * INT_STRLEN_MAXIMUM(int) + - 3 + 2 + 1 + 1]; + static char result[MAX_ASCTIME_SIZE]; - return asctime_r(timeptr, result); + return asctime_rn(timeptr, result, sizeof result); }
Date: Thu, 22 Jul 2004 10:30:47 -0400 From: "Olson, Arthur David (NIH/NCI)" <olsona@dc37a.nci.nih.gov> Message-ID: <75DDD376F2B6B546B722398AC161106C740265@nihexchange2.nih.gov> | Code in "asctime" such as... | ...never returns NULL and never (when applied to a "struct tm" derived from | a 64-bit time_t) overflows a 26-character buffer passed to asctime. | It does lose time-of-day information in the distant past and distant future. Please, don't do that, or anything like it. Every byte in that 26 byte buffer has a defined (very well known) meaning, and code that extracts fields from the asctime output by simply knowing the byte offsets of the relevant fields is (and has been for many years) very very common. So is the fact that the buffer is exactly 26 bytes (25 data and the terminating 0). Don't attempt to fix bugs in the interface of this function, or change it any way at all - for years outside -999..9999 do whatever you like (a NULL return is OK, I guess, though lots of code doesn't bother checking, at least the least evil of several possible evils) - but just truncating the year to the last 4 digits would do as well, just as long as those 4 byte locations are filled with the year number (code that just uses %ld is wrong, as it won't properly use a 4 byte wide field - which isn't a problem if we assume the value is never < 1900, of course, which old code did, but will be a problem if you're going to handle years < 1000). Sometime, someone, somewhere, can define a new function if they want (probably they never will, since strftime() is a perfectly good replacement for asctime for any new uses) and alter the output format. But that can't be asctime(). And note here, I don't care in the slightest what the standards say for this - asctime and the format of that 26 character buffer is one of the oldest, and most stable, infertaces in all of unix - since it was invented, it has never altered, in any unix variant. Fortunately the standards makers also seem to understand this - which is why the day & month names are always the English abbreviations, no matter what the locale is, etc. kre
participants (3)
-
Olson, Arthur David (NIH/NCI) -
Paul Eggert -
Robert Elz