Skip to content

Use after free in std::system_error constructor #5275

@fredemmott

Description

@fredemmott

Describe the bug

std::system_error (in _System_error) constructs a temporary std::string with _Makestr, then stores a .c_str() to it without retaining it, which is invalid.

In my real application, I am seeing an stdext::bad_alloc from within _Throw_fs_error, even in release builds. In debug builds, I see the use-after-free bit pattern, with a trace like this:

Image

The debugger shows that at the time of the _Doraise() call, the string has been overwritten with the 0xdd - use-after-free bit pattern.

I'm able to reproduce this with /fsanitize-address-use-after-return.

Command-line test case

Either of these repros works:

#include <filesystem>
#include <print>

int main(int argc, char** argv) {
    // REPRO 1:
    auto ex = []() {
        std::string what{"abc"};
        std::error_code ec{2, std::system_category()};
        return std::system_error{ec, what};
    }();
    std::println("{}", ex.what());
    // REPRO 2:
    try {
        const std::filesystem::path p { "does-not-exist" };
        std::println("Empty? {}", std::filesystem::is_empty(p));
    } catch (const std::filesystem::filesystem_error& e) {
        std::println("Ex: {}", e.what());
    }
  return 0;
}

The std::filesystem::is_empty() case has a user-after free both for _What and for the path.

PS C:\Users\fred\code\filesystem-test> cl /std:c++latest /EHsc /MDd /Zi /W4 /fsanitize=address /fsanitize-address-use-after-return test.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.42.34435 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

/std:c++latest is provided as a preview of language features from the latest C++
working draft, and we're eager to hear about bugs and suggestions for improvements.
However, note that these features are provided as-is without support, and subject
to changes or removal as the working draft evolves. See
https://go.microsoft.com/fwlink/?linkid=2045807 for details.

test.cpp
test.cpp(4): warning C4100: 'argv': unreferenced formal parameter
test.cpp(4): warning C4100: 'argc': unreferenced formal parameter
Microsoft (R) Incremental Linker Version 14.42.34435.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
/debug
/InferAsanLibs
test.obj
PS C:\Users\fred\code\filesystem-test> ./test.exe
=================================================================
==29036==ERROR: AddressSanitizer: stack-buffer-underflow on address 0x00161e0ff210 at pc 0x7ff67786271f bp 0x00161e0ff0d0 sp 0x00161e0ff0d8
WRITE of size 8 at 0x00161e0ff210 thread T0
    #0 0x7ff67786271e in std::_Container_base12::_Container_base12(void) C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\xmemory:1199
    #1 0x7ff67785fbbe in std::_String_val<struct std::_Simple_types<char>>::_String_val<struct std::_Simple_types<char>>(void) C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\xstring:402
    #2 0x7ff6778119b3 in std::_Compressed_pair<class std::allocator<char>, class std::_String_val<struct std::_Simple_types<char>>, 1>::_Compressed_pair<class std::allocator<char>, class std::_String_val<struct std::_Simple_types<char>>, 1><>(struct std::_Zero_then_variadic_args_t) C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\xmemory:1495
    #3 0x7ff677861bf6 in std::basic_string<char, struct std::char_traits<char>, class std::allocator<char>>::basic_string<char, struct std::char_traits<char>, class std::allocator<char>>(char const *const) C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\xstring:749
    #4 0x7ff6778116eb in `main'::`2'::<lambda_1>::operator() C:\Users\fred\code\filesystem-test\test.cpp:7
    #5 0x7ff677811292 in main C:\Users\fred\code\filesystem-test\test.cpp:6
    #6 0x7ff6778cb238 in invoke_main D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
    #7 0x7ff6778cb181 in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #8 0x7ff6778cb03d in __scrt_common_main D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:330
    #9 0x7ff6778cb2ad in mainCRTStartup D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp:16
    #10 0x7ff94bd0259c  (C:\WINDOWS\System32\KERNEL32.DLL+0x18001259c)
    #11 0x7ff94d58af37  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18005af37)

Address 0x00161e0ff210 is located in stack of thread T0 at offset 0 in frame
    #0 0x7ff67781155f in `main'::`2'::<lambda_1>::operator() C:\Users\fred\code\filesystem-test\test.cpp:6

  This frame has 2 object(s):
    [32, 72) 'what'
    [48, 64) 'ec'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp, SEH and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-underflow C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\xmemory:1199 in std::_Container_base12::_Container_base12(void)
Shadow bytes around the buggy address:
  0x00161e0fef80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x00161e0ff000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x00161e0ff080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x00161e0ff100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x00161e0ff180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x00161e0ff200: 00 00[f1]f1 f1 f1 00 00 00 00 00 f2 f2 f2 f2 00
  0x00161e0ff280: 00 f3 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00
  0x00161e0ff300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x00161e0ff380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x00161e0ff400: 00 00 00 00 00 00 00 00 00 f2 f2 f2 f2 00 00 00
  0x00161e0ff480: 00 00 f2 f2 f2 f2 01 f2 f8 f2 f8 f2 f8 f3 f3 f3
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==29036==ABORTING

Expected behavior

  • std::system_error should manage lifetime of temporary strings it creates
  • std::filesystem::is_empty() should throw an exception on error, not internally crash

STL version

Microsoft Visual Studio Community 2022
Version 17.12.3

Additional context

None.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions