Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Console ReadFile canceled by Ctrl+C fails to set ERROR_OPERATION_ABORTED #334

Open
eryksun opened this issue Dec 29, 2018 · 4 comments
Open
Assignees
Labels
Area-Server Down in the muck of API call servicing, interprocess communication, eventing, etc. Impact-Compatibility Sure don't work like it used to. Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Conhost For issues in the Console codebase
Milestone

Comments

@eryksun
Copy link

eryksun commented Dec 29, 2018

In Windows 8+, a ReadFile call on a console input handle that's interrupted by Ctrl+C or Ctrl+Break doesn't set the last error to ERROR_OPERATION_ABORTED (995). It used to do this in previous versions, and it's documented as such. (It still works for ReadConsole in all versions.) The consequence is that there's no immediate way to distinguish Ctrl+C from EOF (i.e. success with 0 bytes read) when reading from the console via ReadFile.

When a read is interrupted by Ctrl+C, the console returns (and has always returned, AFAIK) the NT status code STATUS_ALERTED (0x00000101), which is a success code for an alerted wait (e.g. via NtAlertThread). This status code is being misused outside of its intended context, but previously its usage was completely private to the console client/server implementation. What changed is that in Windows 8+ the console uses the ConDrv device instead of an LPC port, and console files are now kernel file objects. ReadFile and ReadConsole used to have a common implementation that special cased STATUS_ALERTED. But now they're split up. ReadFile calls NtReadFile and ReadConsole calls NtDeviceIoControlFile (due to the pInputControl parameter). ReadConsole can still special case STATUS_ALERTED. On the other hand, to ReadFile it's simply a successful read, since there's no immediate way to detect a console handle without making another system call.

Maybe ReadFile can safely assume that no other device would be so weird as to return STATUS_ALERTED for a read request. Alternatively, ConDrv or the I/O manager could bring back the practice of flagging console handles by setting the lower 2 bits, as was done prior to Windows 8. Then ReadFile could easily detect a console handle and special case STATUS_ALERTED.

The proper status code is STATUS_CANCELLED (0xC0000120), which automatically maps to ERROR_OPERATION_ABORTED. It's a failure code, however, and according to the docs this case is supposed to succeed with an error set. I don't understand this. The call really has been canceled and really has failed. It's no different from an I/O request getting canceled by CancelIo or CancelSynchronousIo.

@miniksa miniksa added Product-Conhost For issues in the Console codebase Area-Server Down in the muck of API call servicing, interprocess communication, eventing, etc. labels Jan 18, 2019
@eryksun
Copy link
Author

eryksun commented May 16, 2019

If designing this from scratch, I'd expect the following methods to return with the reply status set to STATUS_CANCELLED instead of the misused code STATUS_ALERTED:

In turn, ReadFile and ReadConsole would both fail with ERROR_OPERATION_ABORTED.

Unfortunately, failing the call goes against the documented behavior of ReadFile and also its observed behavior prior to Windows 8 and the observed behavior of ReadConsole in all versions. Here's what the ReadFile documentation promises:

Characters can be read from the console input buffer by using ReadFile with a handle to console input. The console mode determines the exact behavior of the ReadFile function. By default, the console mode is ENABLE_LINE_INPUT, which indicates that ReadFile should read until it reaches a carriage return. If you press Ctrl+C, the call succeeds, but GetLastError returns ERROR_OPERATION_ABORTED.

Since this has been broken for many years now, I'd prefer it was fixed by internally returning STATUS_CANCELLED and updating the documentation of ReadFile to state that the call fails in Windows 10. The behavior of ReadConsole with Ctrl+C was never documented.

If retaining the original documented behavior is desired, without any changes to the console host, then ReadFile needs to detect a console handle if NtReadFile returns STATUS_ALERTED. As is, maybe the simplest way is to call NtQueryVolumeInformationFile : FileFsDeviceInformation to check for the device type FILE_DEVICE_CONSOLE.

@ghost ghost added the Needs-Tag-Fix Doesn't match tag requirements label May 17, 2019
@miniksa miniksa added Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Conhost For issues in the Console codebase and removed Mass-Chaos Product-Conhost For issues in the Console codebase labels May 17, 2019
@ghost ghost removed the Needs-Tag-Fix Doesn't match tag requirements label May 18, 2019
@lanza
Copy link

lanza commented Jul 12, 2019

In Windows 8+, a ReadFile call on a console input handle that's interrupted by Ctrl+C or Ctrl+Break doesn't set the last error to ERROR_OPERATION_ABORTED (995). It used to do this in previous versions, and it's documented as such. (It still works for ReadConsole in all versions.) The consequence is that there's no immediate way to distinguish Ctrl+C from EOF (i.e. success with 0 bytes read) when reading from the console via ReadFile.

Any update on this?

@zadjii-msft zadjii-msft added this to the Console Backlog milestone Aug 1, 2019
@DHowett-MSFT DHowett-MSFT added the Impact-Compatibility Sure don't work like it used to. label Mar 19, 2020
@alexrp
Copy link

alexrp commented Mar 20, 2020

I just ran into this exact issue as well.

I tested with this C# program:

using System;
using System.Threading;
using Vanara.PInvoke;
using static Vanara.PInvoke.Kernel32;

namespace Test
{
    static class Program
    {
        static HandlerRoutine _handler;

        static bool Handler(CTRL_EVENT e)
        {
            Console.WriteLine("Got {0} signal", e);
            return true; // Suppress.
        }

        unsafe static void Main()
        {
            _handler = Handler;
            SetConsoleCtrlHandler(_handler, true);

            var stdin = GetStdHandle(StdHandleType.STD_INPUT_HANDLE);

            Span<byte> buf = stackalloc byte[4096];

            fixed (byte* p = buf)
            {
                var ret = ReadFile(stdin, (IntPtr)p, (uint)buf.Length, out var read, IntPtr.Zero);
                Console.WriteLine("ret={0}, err={1}, read={2}", ret, Win32Error.GetLastError(), read);
            }

            Console.WriteLine("Exiting...");
            Thread.Sleep(1000);
        }
    }
}

What makes this particularly painful in my case is that I don't want ReadFile/ReadConsole to be canceled since I have a Ctrl+C handler set up which suppresses the signal. At least in the case of ReadConsole I can detect ERROR_OPERATION_ABORTED and resume reading if I know a handler is in place, but I have no such luck with ReadFile.

@eryksun
Copy link
Author

eryksun commented Mar 27, 2020

If retaining the original documented behavior is desired, without any changes to the console host, then ReadFile needs to detect a console handle if NtReadFile returns STATUS_ALERTED. As is, maybe the simplest way is to call NtQueryVolumeInformationFile : FileFsDeviceInformation to check for the device type FILE_DEVICE_CONSOLE.

Unfortunately making a second system call has a race condition. The handle may have been closed and possibly reassigned to a different kernel file object.

If the success-error behavior has to be retained, maybe the server could return a new I/O facility information status code (i.e. 0x4004_XXXX) such as STATUS_CONTROL_C_INTERRUPT. The latter name is based on the error status code STATUS_CONTROL_C_EXIT (0xC000013A), which the default control-event handler sets as the process exit code. WINAPI ReadFile could reliably special case this new status code, as opposed to making cringe-worthy assumptions about STATUS_ALERTED. ReadConsole would also need to be modified to check for the new status code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Server Down in the muck of API call servicing, interprocess communication, eventing, etc. Impact-Compatibility Sure don't work like it used to. Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Conhost For issues in the Console codebase
Projects
None yet
Development

No branches or pull requests

7 participants