Why is calling ReadConsole in a loop corrupting the stack?

1.6k views Asked by At

I've disabled line input with the following code:

DWORD dwConsoleMode;
GetConsoleMode(hStdIn, &dwConsoleMode);
dwConsoleMode ^= ENABLE_LINE_INPUT;
SetConsoleMode(hStdIn, dwConsoleMode);

Then I am calling ReadConsole in a loop...in a loop:

wchar_t cBuf;

while (1) {
    /* Display Options */

    do {
        ReadConsole(hStdIn, &cBuf, 1, &dwNumRead, NULL);
    } while (!iswdigit(cBuf));

    putwchar(cBuf);

    if (cBuf == L'0') break;
}

If I run the program and press 0 right away, it exists cleanly.
But if I press a bunch of keys, then press 0, when the program exists it crashes with:

Run-Time Check Failure #2 - Stack around the variable 'cBuf' was corrupted.

Why is this causing the stack to become corrupt? The code is simple, so I can't figure out what is wrong.

Little program that I can reproduce the problem with:

#include <windows.h>
#include <stdio.h>

int wmain(int argc, wchar_t *argv[])
{
    DWORD dwNumRead;
    wchar_t cBuf;

    HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);

    DWORD dwConsoleMode;
    GetConsoleMode(hStdIn, &dwConsoleMode);
    dwConsoleMode ^= ENABLE_LINE_INPUT;
    SetConsoleMode(hStdIn, dwConsoleMode);

    while (true)
    {
        wprintf(L"\nEnter option: ");

        do {
            ReadConsoleW(hStdIn, &cBuf, 1, &dwNumRead, NULL);
        } while (!iswdigit(cBuf));

        putwchar(cBuf);

        if (cBuf == L'0') break;
    }

    return 0;
}

You have to kind of mash your keyboard after you run it, then press 0, and it crashes with the stack corruption.

I also can't reproduce the problem every time, it takes a few tries.
I was running it under Visual Studio 2010, after creating a new empty console project and adding a file with that code.

1

There are 1 answers

2
Harry Johnston On BEST ANSWER

As far as I can tell, this is a bug in Windows. Here is a slightly simpler program that demonstrates the problem:

#include <windows.h>
#include <crtdbg.h>

int wmain(int argc, wchar_t *argv[])
{
    DWORD dwNumRead;
    wchar_t cBuf[2];

    cBuf[0] = cBuf[1] = 65535;

    HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
    SetConsoleMode(hStdIn, 0);

    while (true)
    {
        _ASSERT(ReadConsoleW(hStdIn, &cBuf[0], 1, &dwNumRead, NULL));
        _ASSERT(dwNumRead == 1);
        _ASSERT(cBuf[1] == 65535);
        Sleep(5000);
    }
}

The sleep makes it a bit easier to trigger the issue, which occurs whenever more than one character is waiting at the time you call ReadConsoleW.

Looking at the content of cBuf[1] at the time the relevant assertion fails, it appears that ReadConsoleW is writing one extra byte at the end of the buffer.

The workaround is straightforward: make sure your buffer has at least one extra byte. In your case, use the first character of a two-character array.