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

Open
opened 2026-01-30 21:53:46 +00:00 by claunia · 4 comments
Owner

Originally created by @eryksun on GitHub (Dec 29, 2018).

Originally assigned to: @lhecker on GitHub.

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.

Originally created by @eryksun on GitHub (Dec 29, 2018). Originally assigned to: @lhecker on GitHub. In Windows 8+, a [`ReadFile`](https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-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`.
claunia added the Product-ConhostIssue-BugArea-ServerImpact-Compatibility labels 2026-01-30 21:53:46 +00:00
Author
Owner

@eryksun commented on GitHub (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.

@eryksun commented on GitHub (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`: * [`DirectReadData::Notify`](https://github.com/microsoft/terminal/blob/v0.1.1211.0/src/host/readDataDirect.cpp#L43) * [`RAW_READ_DATA::Notify`](https://github.com/microsoft/terminal/blob/v0.1.1211.0/src/host/readDataRaw.cpp#L46) * [`COOKED_READ_DATA::Notify`](https://github.com/microsoft/terminal/blob/v0.1.1211.0/src/host/readDataCooked.cpp#L294) 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`.
Author
Owner

@lanza commented on GitHub (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?

@lanza commented on GitHub (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?
Author
Owner

@alexrp commented on GitHub (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.

@alexrp commented on GitHub (Mar 20, 2020): I just ran into this exact issue as well. I tested with this C# program: ```csharp 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`.
Author
Owner

@eryksun commented on GitHub (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.

@eryksun commented on GitHub (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 in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#497