[Git][wxwidgets/wxwidgets][master] 4 commits: Fix out-of-bounds read on trailing % in wxDateTime::Format()

1 view
Skip to first unread message

Vadim Zeitlin (@_VZ_)

unread,
Jun 4, 2026, 5:37:32 PM (3 days ago) Jun 4
to wx-commi...@googlegroups.com

Vadim Zeitlin pushed to branch master at wxWidgets / wxWidgets

Commits:

  • 53901b80
    by dxbjavid at 2026-06-04T17:19:27+02:00
    Fix out-of-bounds read on trailing % in wxDateTime::Format()
    
    Add a unit test but restrict it to only the cases when our own function
    is used because MSVC CRT strftime() asserts in this case.
    
    Closes #26543.
    
  • 48561fc6
    by dxbjavid at 2026-06-04T17:22:36+02:00
    Fix off-by-one in hostent/servent pointer list terminator
    
    The h_addr_list/h_aliases/s_aliases copy loops in deepCopyHostent() and
    deepCopyServent() reserve N pointer slots for N entries and then write
    the terminator with *++q, one slot too far, so the array isn't
    terminated right after the last entry and that slot holds copied
    address/alias bytes used as a pointer.
    
    Reserve a slot for the terminator and write it with *q.
    
    Closes #26553.
    
  • 41b7f696
    by Crementif at 2026-06-04T17:23:54+02:00
    Fix text and column overflow in wxMSW wxListCtrl
    
    Compute the items rectangles ourselves as LVM_GETSUBITEMRECT doesn't
    return the correct result in several cases.
    
    Closes #26558.
    
  • ddd2b3fa
    by Vadim Zeitlin at 2026-06-04T17:44:58+02:00
    Capture "this" explicitly in lambda used in wxMSW wxJoystick code
    
    Avoid warning about implicit "this" capture being deprecated in C++20.
    

5 changed files:

Changes:

  • src/common/datetimefmt.cpp
    ... ... @@ -405,8 +405,17 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
    405 405
                 continue;
    
    406 406
             }
    
    407 407
     
    
    408
    +        // advance to the format specifier following the '%': a trailing '%'
    
    409
    +        // is not a valid format, so output it verbatim and stop here instead
    
    410
    +        // of letting the loop's own ++p move the iterator past the end
    
    411
    +        if ( ++p == format.end() )
    
    412
    +        {
    
    413
    +            res += wxT('%');
    
    414
    +            break;
    
    415
    +        }
    
    416
    +
    
    408 417
             // set the default format
    
    409
    -        switch ( (*++p).GetValue() )
    
    418
    +        switch ( (*p).GetValue() )
    
    410 419
             {
    
    411 420
                 case wxT('Y'):               // year has 4 digits
    
    412 421
                 case wxT('G'):               // (and ISO week year too)
    

  • src/common/sckaddr.cpp
    ... ... @@ -165,6 +165,7 @@ hostent *deepCopyHostent(hostent *h,
    165 165
         char **h_addr_list = (char **)(buffer + pos);
    
    166 166
         while(*(p++) != nullptr)
    
    167 167
             pos += sizeof(char *);
    
    168
    +    pos += sizeof(char *); /* and one slot for the null terminator */
    
    168 169
     
    
    169 170
         /* copy addresses and fill new pointer list */
    
    170 171
         for (p = h->h_addr_list, q = h_addr_list; *p != nullptr; p++, q++)
    
    ... ... @@ -178,7 +179,7 @@ hostent *deepCopyHostent(hostent *h,
    178 179
             *q = buffer + pos; /* set copied pointer to copied content */
    
    179 180
             pos += len;
    
    180 181
         }
    
    181
    -    *++q = nullptr; /* null terminate the pointer list */
    
    182
    +    *q = nullptr; /* null terminate the pointer list */
    
    182 183
         h->h_addr_list = h_addr_list; /* copy pointer to pointers */
    
    183 184
     
    
    184 185
         /* ensure word alignment of pointers */
    
    ... ... @@ -191,6 +192,7 @@ hostent *deepCopyHostent(hostent *h,
    191 192
         char **h_aliases = (char **)(buffer + pos);
    
    192 193
         while(*(p++) != nullptr)
    
    193 194
             pos += sizeof(char *);
    
    195
    +    pos += sizeof(char *); /* and one slot for the null terminator */
    
    194 196
     
    
    195 197
         /* copy aliases and fill new pointer list */
    
    196 198
         for (p = h->h_aliases, q = h_aliases; *p != nullptr; p++, q++)
    
    ... ... @@ -206,7 +208,7 @@ hostent *deepCopyHostent(hostent *h,
    206 208
             *q = buffer + pos; /* set copied pointer to copied content */
    
    207 209
             pos += len + 1;
    
    208 210
         }
    
    209
    -    *++q = nullptr; /* null terminate the pointer list */
    
    211
    +    *q = nullptr; /* null terminate the pointer list */
    
    210 212
         h->h_aliases = h_aliases; /* copy pointer to pointers */
    
    211 213
     
    
    212 214
         return h;
    
    ... ... @@ -310,6 +312,7 @@ servent *deepCopyServent(servent *s,
    310 312
         char **s_aliases = (char **)(buffer + pos);
    
    311 313
         while(*(p++) != nullptr)
    
    312 314
             pos += sizeof(char *);
    
    315
    +    pos += sizeof(char *); /* and one slot for the null terminator */
    
    313 316
     
    
    314 317
         /* copy addresses and fill new pointer list */
    
    315 318
         for (p = s->s_aliases, q = s_aliases; *p != nullptr; p++, q++){
    
    ... ... @@ -323,7 +326,7 @@ servent *deepCopyServent(servent *s,
    323 326
             *q = buffer + pos; /* set copied pointer to copied content */
    
    324 327
             pos += len + 1;
    
    325 328
         }
    
    326
    -    *++q = nullptr; /* null terminate the pointer list */
    
    329
    +    *q = nullptr; /* null terminate the pointer list */
    
    327 330
         s->s_aliases = s_aliases; /* copy pointer to pointers */
    
    328 331
         return s;
    
    329 332
     }
    

  • src/msw/joystick.cpp
    ... ... @@ -435,7 +435,7 @@ wxString wxJoystick::GetProductName() const
    435 435
         if (joyGetDevCaps(m_joystick, &joyCaps, sizeof(joyCaps)) != JOYERR_NOERROR)
    
    436 436
             return wxEmptyString;
    
    437 437
     
    
    438
    -    auto GetNameFromReg = [=](wxRegKey::StdKey root) -> wxString
    
    438
    +    auto GetNameFromReg = [this, joyCaps](wxRegKey::StdKey root) -> wxString
    
    439 439
         {
    
    440 440
             wxString result;
    
    441 441
             wxString subKey1 = wxString::Format(wxT("%s\\%s\\%s"), REGSTR_PATH_JOYCONFIG, joyCaps.szRegKey, REGSTR_KEY_JOYCURR);
    

  • src/msw/listctrl.cpp
    ... ... @@ -1403,20 +1403,29 @@ bool wxListCtrl::GetSubItemRect(long item, long subItem, wxRect& rect, int code)
    1403 1403
     
    
    1404 1404
         wxCopyRECTToRect(rectWin, rect);
    
    1405 1405
     
    
    1406
    -    // We can't use wxGetListCtrlSubItemRect() for the 0th subitem as 0 means
    
    1407
    -    // the entire row for this function, so we need to calculate it ourselves.
    
    1408
    -    if ( subItem == 0 && code == wxLIST_RECT_BOUNDS )
    
    1406
    +    // We can't trust the native LVM_GETSUBITEMRECT for LVIR_BOUNDS because:
    
    1407
    +    //  - subitem 0 returns the entire row bounds, not just column 0
    
    1408
    +    //  - when column 0 is narrower than the icon, the native control clamps
    
    1409
    +    //    all subitems' left edge to at least the icon width, misaligning them
    
    1410
    +    //
    
    1411
    +    // So for all subitems we calculate x and width from GetColumnWidth() in
    
    1412
    +    // visual (column order) order, which always reflects the real column sizes.
    
    1413
    +    if ( subItem != wxLIST_GETSUBITEMRECT_WHOLEITEM && code == wxLIST_RECT_BOUNDS )
    
    1409 1414
         {
    
    1410
    -        // Because the columns can be reordered, we need to sum the widths of
    
    1411
    -        // all preceding columns to get the correct x position.
    
    1415
    +        // Start from the row's left edge (which accounts for scrolling).
    
    1416
    +        RECT rowRect;
    
    1417
    +        wxGetListCtrlItemRect(GetHwnd(), item, LVIR_BOUNDS, rowRect);
    
    1418
    +        int x = rowRect.left;
    
    1419
    +
    
    1412 1420
             for ( auto col : GetColumnsOrder() )
    
    1413 1421
             {
    
    1414 1422
                 if ( col == subItem )
    
    1415 1423
                     break;
    
    1416 1424
     
    
    1417
    -            rect.x += GetColumnWidth(col);
    
    1425
    +            x += GetColumnWidth(col);
    
    1418 1426
             }
    
    1419 1427
     
    
    1428
    +        rect.x = x;
    
    1420 1429
             rect.width = GetColumnWidth(subItem);
    
    1421 1430
         }
    
    1422 1431
     
    

  • tests/datetime/datetimetest.cpp
    ... ... @@ -800,6 +800,17 @@ TEST_CASE("wxDateTime::Format", "[datetime]")
    800 800
     
    
    801 801
         wxDateTime dt(29, wxDateTime::May, 1976, 18, 30, 15, 678);
    
    802 802
         CHECK( dt.Format("%F %T.%l") == "1976-05-29 18:30:15.678" );
    
    803
    +
    
    804
    +    // A format ending in a lone '%' must not read past the end of the format
    
    805
    +    // string; the trailing percent is output verbatim. The literal is long
    
    806
    +    // enough to be heap-allocated so the over-read trips ASAN without the fix.
    
    807
    +    //
    
    808
    +    // The MSVC CRT considers a trailing '%' an invalid format string and
    
    809
    +    // aborts, so skip this check there. This CRT is also used by MinGW.
    
    810
    +#if !defined(_MSC_VER) && !defined(__MINGW32__)
    
    811
    +    CHECK( dt.Format("a long enough format ending in a percent sign %") ==
    
    812
    +                     "a long enough format ending in a percent sign %" );
    
    813
    +#endif
    
    803 814
     }
    
    804 815
     
    
    805 816
     TEST_CASE("wxDateTime::ParseFormat", "[datetime]")
    

Reply all
Reply to author
Forward
0 new messages