On 2023-11-10 10:44, Pedro Luis Castedo Cepeda wrote:
El 10/11/2023 a las 11:16, Corinna Vinschen escribió:
On Nov  9 23:17, Brian Inglis wrote:
On 2023-11-09 12:04, Pedro Luis Castedo Cepeda wrote:
- Prevent strftime to parsing format string beyond its end when
    it finish with "%E" or "%O".
---
   newlib/libc/time/strftime.c | 2 ++
   1 file changed, 2 insertions(+)

diff --git a/newlib/libc/time/strftime.c b/newlib/libc/time/strftime.c
index 56f227c5f..c4e9e45a9 100644
--- a/newlib/libc/time/strftime.c
+++ b/newlib/libc/time/strftime.c
@@ -754,6 +754,8 @@ __strftime (CHAR *s, size_t maxsize, const CHAR *format,
         switch (*format)
       {
+    case CQ('\0'):
+      break;
       case CQ('a'):
         _ctloc (wday[tim_p->tm_wday]);
         for (i = 0; i < ctloclen; i++)

These cases appear to already be taken care of by setting and using
(depending on the config parameters) the "alt" variable for those modifiers,
and the default: return 0; for the format *character* (possibly wide) not
matching following any modifiers.

Patches to newlib should go to the newlib mailing list at sourceware dot org.

Also, a simple reproducer would be nice.

My first contribution. Sorry about posting to wrong mail list and, at best, minimalistic patch motivation reasoning. First time with git send-mail, too.

I came across this newlib "feature" trying to update GLib port to 2.78.1. When trying to find out why test_strftime (glib/test/date.c) was failing I discovered that one of the test format strings, "%E" was triggering a loop in g_date_strftime (glib/gdate.c) requiring more and more memory till it was stopped by a fortunate maximum size check in function.

The problem is that __strftime  (newlib/libc/time/strftime.c) doesn't check for '\0' after a terminal "%E" and it continues parsing the format string. Finally (not sure if intentionally), this triggers a direct return 0 from __strftime instead breaking the loop, preventing it from add '\0' to the end of returned string. Same for "%O", I think (not tested).

It seems that this trailing '\0' allows to differentiate returning an empty string from needing more space (at least, in Glib).

So, is it a newlib bug? Not really, I think this format string is bad-formed (%E should modify something, shouldn't it?) So undefined behaviour is OK. I could patch-out these format strings from the port.

But... from Glib tests, it seems that, at least:

- If G_OS_WIN32, terminal "%E" & "%O" are silently discarded.
- If __FreeBSD__ || __OpenBSD__ || __APPLE__ they are transformed to E & O, respectively.
- And if #else the same thing is expected.

So it seems that returning 0-terminated string is a common practice and I also think that this is more deterministic and, potentially, safer. That's why I sent the patch. It tries to be the shortest addition to check for end of string after %E & %O modifiers and takes G_OS_WIN32 approach (only cause it's the simplest).

Not seeing any issue with any format - see attached source and log output, built under Cygwin.
[Derived from a bash script using printf %(...)T to do the same thing.]

--
Take care. Thanks, Brian Inglis              Calgary, Alberta, Canada

La perfection est atteinte                   Perfection is achieved
non pas lorsqu'il n'y a plus rien à ajouter  not when there is no more to add
mais lorsqu'il n'y a plus rien à retirer     but when there is no more to cut
                                -- Antoine de Saint-Exupéry
/*!gcc -o strftime-formats strftime-formats.c
 * strftime-formats.c - enumerate strftime formats and values using bash printf 
%(%...)T
 *
 * %a          locale abbreviated weekday name [Sun..Sat]  Fri
 * %A              locale weekday name [Sunday..Saturday]  Friday
 * %b            locale abbreviated month name [Jan..Dec]  Aug
 * %B               locale month name [January..December]  August
 * %c                                    locale date time  2022 Aug 26 Fri 
12:45:19
 * %C                                    century [-99...]  20
 * %d                  day in month, zero-padded [01..31]  26
 * %D                                  US date [%m/%d/%y]  08/26/22
 * %e                 day in month, blank-padded [ 1..31]  26
 * %E                     locale era extensions [ignored]  
 * %f                          invalid format character ?  
 * %F             ISO 8601 date year-month-day [%Y-%m-%d]  2022-08-26
 * %g        ISO 8601 week based year in century [00..99]  22
 * %G    ISO 8601 week based year with century [-9999...]  2022
 * %h            locale abbreviated month name [Jan..Dec]  Aug
 * %H                        hour, 24-hour clock [00..23]  12
 * %i                            year in century [00..99]  
 * %I                        hour, 12-hour clock [01..12]  12
 * %j                              day in year [001..366]  238
 * %J                          invalid format character ?  
 * %k             hour, 24-hour clock, blank pad [ 0..23]  12
 * %K                          invalid format character ?  
 * %l             hour, 12-hour clock, blank pad [ 0..12]  12
 * %L                          invalid format character ?  
 * %m                                      month [01..12]  08
 * %M                                     minute [00..59]  45
 * %n                                             newline '
 * '
 * %N                           nanoseconds [0-999999999] 123456789 
 * %N                                    Emperor/Era Name  
 * %o                                    Emperor/Era Year  
 * %O         locale alternate digit extensions [ignored]  
 * %p                          locale AM/PM 12-hour clock  PM
 * %P                locale lowercase am/pm 12-hour clock  pm
 * %q                                 quarter year [1..4]  
 * %Q                          invalid format character ?  
 * %r                            time, 12-hour [%I:%M:%S]  12:45:19
 * %R                      ISO 8601 time, 24-hour [%H:%M]  12:45
 * %s                    seconds since the epoch -...0...  1661539519
 * %S                                     second [00..60]  19
 * %t                                                 tab '     '
 * %T                   ISO 8601 time, 24-hour [%H:%M:%S]  12:45:19
 * %u            ISO 8601 day of week [1..7, Monday == 1]  5
 * %U        week in year, starting first Sunday [00..53]  34
 * %v                          VMS/Oracle date [%e-%b-%Y]  26-Aug-2022
 * %V           ISO 8601 week in week based year [01..53]  34
 * %w                     day of week [0..6, Sunday == 0]  5
 * %W        week in year, starting first Monday [00..53]  34
 * %x                          locale date representation  2022-08-26
 * %X               locale time representation [%H:%M:%S]  12:45:19
 * %y                            year in century [00..99]  22
 * %Y                        year with century [-9999...]  2022
 * %z          timezone offset east of GMT [-2300..+2300]  -0600
 * %Z  timezone abbreviation, blank if undetermined [XXX]  MDT
 * %+      default locale date format [%a %b %e %T %Z %Y]  Fri Aug 26 21:34:07 
MDT 2022
 * %                                no format character ?
 */


#include <locale.h>
#include <stdio.h>
#include <time.h>


const char *tfmt[] = {
        "%a          locale abbreviated weekday name [Sun..Sat]",
        "%A              locale weekday name [Sunday..Saturday]",
        "%b            locale abbreviated month name [Jan..Dec]",
        "%B               locale month name [January..December]",
        "%c                                    locale date time",
        "%C                                    century [-99...]",
        "%d                  day in month, zero-padded [01..31]",
        "%D                                  US date [%m/%d/%y]",
        "%e                 day in month, blank-padded [ 1..31]",
        "%E                      locale alternate era [ignored]",
        "%f                          invalid format character ?",
        "%F             ISO 8601 date year-month-day [%Y-%m-%d]",
        "%g        ISO 8601 week based year in century [00..99]",
        "%G    ISO 8601 week based year with century [-9999...]",
        "%h            locale abbreviated month name [Jan..Dec]",
        "%H                        hour, 24-hour clock [00..23]",
        "%i                            year in century [00..99]",
        "%I                        hour, 12-hour clock [01..12]",
        "%j                              day in year [001..366]",
        "%J                          invalid format character ?",
        "%k             hour, 24-hour clock, blank pad [ 0..23]",
        "%K                          invalid format character ?",
        "%l             hour, 12-hour clock, blank pad [ 0..12]",
        "%L                          invalid format character ?",
        "%m                                      month [01..12]",
        "%M                                     minute [00..59]",
        "%n                                             newline",
        "%N                           nanoseconds [0-999999999]",
        "%N                   locale extension Emperor/Era Name",
        "%o                   locale extension Emperor/Era Year",
        "%O                   locale alternate digits [ignored]",
        "%p                          locale 12-hour clock AM/PM",
        "%P                locale 12-hour clock lowercase am/pm",
        "%q                                 quarter year [1..4]",
        "%Q                          invalid format character ?",
        "%r                            time, 12-hour [%I:%M:%S]",
        "%R                      ISO 8601 time, 24-hour [%H:%M]",
        "%s                    seconds since the epoch -...0...",
        "%S                                     second [00..60]",
        "%t                                                 tab",
        "%T                   ISO 8601 time, 24-hour [%H:%M:%S]",
        "%u            ISO 8601 day of week [1..7, Monday == 1]",
        "%U        week in year, starting first Sunday [00..53]",
        "%v             BSD/OSX/Ruby VMS/Oracle date [%e-%b-%Y]",
        "%V           ISO 8601 week in week based year [01..53]",
        "%w                     day of week [0..6, Sunday == 0]",
        "%W        week in year, starting first Monday [00..53]",
        "%x                          locale date representation",
        "%X               locale time representation [%H:%M:%S]",
        "%y                            year in century [00..99]",
        "%Y                        year with century [-9999...]",
        "%z          timezone offset east of GMT [-2300..+2300]",
        "%Z  timezone abbreviation, blank if undetermined [XXX]",
        "%+      default locale date format [%a %b %e %T %Z %Y]",
        "%                                no format character ?",
    };


int
main( void )
{
    char        out[64]         = "";
    char        fmt[4]          = "% ";
    char *      f               = &fmt[1];
    time_t      now             = time( &now );
    struct tm * local           = localtime( &now );
    char *      locale          = setlocale( LC_ALL, "");
    size_t      len;
    int         rc;


    for (size_t tf = 0; tf < sizeof( tfmt )/sizeof( *tfmt ); ++tf)
    {
        *f      = tfmt[tf][1];
        *out    = '\0';
        len     = strftime( out, sizeof( out ), fmt, local);

        if (!(rc = printf( "%s\t'%s'\t%2zi\n", tfmt[tf], out, len)))
        {
            perror( "strftime-formats: printf" );
        }
    }

    locale      = setlocale( LC_ALL, locale);
} /* main() */
%a          locale abbreviated weekday name [Sun..Sat]  'Fri'    3
%A              locale weekday name [Sunday..Saturday]  'Friday'         6
%b            locale abbreviated month name [Jan..Dec]  'Nov'    3
%B               locale month name [January..December]  'November'       8
%c                                    locale date time  '2023 Nov 10 Fri 
22:52:46'      24
%C                                    century [-99...]  '20'     2
%d                  day in month, zero-padded [01..31]  '10'     2
%D                                  US date [%m/%d/%y]  '11/10/23'       8
%e                 day in month, blank-padded [ 1..31]  '10'     2
%E                      locale alternate era [ignored]  ''       0
%f                          invalid format character ?  ''       0
%F             ISO 8601 date year-month-day [%Y-%m-%d]  '2023-11-10'    10
%g        ISO 8601 week based year in century [00..99]  '23'     2
%G    ISO 8601 week based year with century [-9999...]  '2023'   4
%h            locale abbreviated month name [Jan..Dec]  'Nov'    3
%H                        hour, 24-hour clock [00..23]  '22'     2
%i                            year in century [00..99]  ''       0
%I                        hour, 12-hour clock [01..12]  '10'     2
%j                              day in year [001..366]  '314'    3
%J                          invalid format character ?  ''       0
%k             hour, 24-hour clock, blank pad [ 0..23]  '22'     2
%K                          invalid format character ?  ''       0
%l             hour, 12-hour clock, blank pad [ 0..12]  '10'     2
%L                          invalid format character ?  ''       0
%m                                      month [01..12]  '11'     2
%M                                     minute [00..59]  '52'     2
%n                                             newline  '
'        1
%N                           nanoseconds [0-999999999]  ''       0
%N                   locale extension Emperor/Era Name  ''       0
%o                   locale extension Emperor/Era Year  ''       0
%O                   locale alternate digits [ignored]  ''       0
%p                          locale 12-hour clock AM/PM  'PM'     2
%P                locale 12-hour clock lowercase am/pm  'pm'     2
%q                                 quarter year [1..4]  '4'      1
%Q                          invalid format character ?  ''       0
%r                            time, 12-hour [%I:%M:%S]  '10:52:46'       8
%R                      ISO 8601 time, 24-hour [%H:%M]  '22:52'  5
%s                    seconds since the epoch -...0...  '1699681966'    10
%S                                     second [00..60]  '46'     2
%t                                                 tab  '       '        1
%T                   ISO 8601 time, 24-hour [%H:%M:%S]  '22:52:46'       8
%u            ISO 8601 day of week [1..7, Monday == 1]  '5'      1
%U        week in year, starting first Sunday [00..53]  '45'     2
%v             BSD/OSX/Ruby VMS/Oracle date [%e-%b-%Y]  '10-Nov-2023'   11
%V           ISO 8601 week in week based year [01..53]  '45'     2
%w                     day of week [0..6, Sunday == 0]  '5'      1
%W        week in year, starting first Monday [00..53]  '45'     2
%x                          locale date representation  '2023-11-10'    10
%X               locale time representation [%H:%M:%S]  '22:52:46'       8
%y                            year in century [00..99]  '23'     2
%Y                        year with century [-9999...]  '2023'   4
%z          timezone offset east of GMT [-2300..+2300]  '-0700'  5
%Z  timezone abbreviation, blank if undetermined [XXX]  'MST'    3
%+      default locale date format [%a %b %e %T %Z %Y]  ''       0
%                                no format character ?  ''       0

Reply via email to