1

I have a function that is supposed to launch another process:

DWORD WINAPI StartCalc(LPVOID lpParam) {
    STARTUPINFOW  info;
    PROCESS_INFORMATION   processInfo;
    std::wstring cmd = L"C:\\Windows\\System32\\calc.exe";
    BOOL hR = CreateProcessW(NULL, (LPWSTR)cmd.c_str(), NULL, NULL, TRUE, 0, NULL, NULL,
                             &info, &processInfo);
    if (hR == 0) {
       DWORD errorMessageID = ::GetLastError();
        printf("Error creating process\n");
        return 1;
    }
    return 0;
}

I get an exception in ntdll.dll "Access violation reading location 0xFFFFFFFFFFFFFFFF". I know there are a few common mistakes that might cause this:

  • Inconsistent calling conventions. I am using __stdcall
  • Encoding issues. I storing strings as wide chars
  • The problem happens in both x64 and x86 builds
  • The problems happens when I try to create other Windows processes

What am i doing wrong?

EDIT: This actually isn't a problem with casting cmd.c_str() as a (LPWSTR), that part appears to be fine. I needed to initialize the STARTUPINFO struct: STARTUPINFO info = { 0 };

david
  • 131
  • 1
  • 12
  • 2
    not exactly duplicate, because there another error Access violation **writing** location.. here Access violation **reading** location. the `cmd.c_str()` is really writable memory and point not to string literal (it copied to allocated memory). what is really source of bug - not initialized `STARTUPINFOW info;` – RbMm Aug 24 '19 at 10:03
  • also if we have exatly application path - why not pass it as 1-st parameter of `CreateProcessW` ? in this case (explicit path) the 2 parameter can be and read only string, because when we have path - not need parse and modify command line for temporary isolate application name – RbMm Aug 24 '19 at 10:07
  • `STARTUPINFO info = { sizeof(info) };` must be. and if you have exactly application path - much more better pass it as 1 parameter to create process. even if you have name only - better direct call `SearchPath` first and pass result to 1 parameter. – RbMm Aug 24 '19 at 14:48

1 Answers1

2
BOOL hR = CreateProcessW(NULL, (LPWSTR)cmd.c_str(), NULL, NULL, TRUE, 0, NULL, NULL, &info, &processInfo);
                                  ^^^^^

That's a cast. A great rule of thumb is "spot the cast, you spot the error".

It's true here. CreateProcessW must be passed a writable string. That means, no literal, and also no c_str() result.

From the documentation:

The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.

Pass a real non-const pointer, don't play hide-the-const. &cmd[0] should work, that's guaranteed to be a writable string. To be super-safe, increase your wstring capacity beyond just what you needed, because CreateProcessW is going to use it as a working buffer.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • This is nonsense. The "pointer" is just a memory location to memory which is necessarily writable given that it is an std::string. – Roflcopter4 Apr 24 '23 at 12:46
  • @Roflcopter4: https://eel.is/c++draft/string.accessors#3 – Ben Voigt Apr 24 '23 at 13:12
  • The data for a `std::string` is always allocated on the heap. If a string literal is used to initialize one, the data is copied. The standard might make room for the idea of a reference to read-only data in a string but modern standards implicitly acknowledge that this is a fiction by the existence of `std::string_view`. The fact that your recommendation of `&foo[0]` could possibly work also implicitly acknowledges this. Compiler optimizations might bite you if you do try to modify a formerly `const` thing, but they won't in a function call. It's well defined in practice. – Roflcopter4 Apr 28 '23 at 12:21
  • @Roflcopter4: Whether or not you are permitted to overwrite the content has nothing to do with where the data is stored. The standard clearly says that you must not write through a pointer obtained from `c_str()` or `data()`: **The program shall not modify any of the values stored in the character array; otherwise, the behavior is undefined.** – Ben Voigt Apr 28 '23 at 14:18
  • Undefined per the letter of the standard, but by inference to how the standard describes the `std::string` it is, in fact, well defined by logical necessity. The data is required to be in writable memory. Presumably it is declared to be undefined because the length of the string would not be updated, nor would any mechanism for overflow protection be possible. This doesn't change the fact that the act of writing to that memory is well defined even if the behavior overall is, by fiat, not. – Roflcopter4 May 02 '23 at 07:26