ReadConsoleOutput fails with ERROR_ACCESS_DENIED across IL boundary #7550

Closed
opened 2026-01-31 01:07:02 +00:00 by claunia · 12 comments
Owner

Originally created by @luke1961 on GitHub (Apr 22, 2020).

Environment:
Microsoft Windows [Version 10.0.18363.720]

Conhost:
Version 10.0.18362.1

Steps to reproduce:

  1. Make sure you are not using the 'legacy mode' or the problem will not occur
  2. Launch Program A on SYSTEM account with CREATE_NEW_CONSOLE flag
  3. Program A calls LogonUser, gets a token and then Uses CreateProcessAsUser to launch CMD.EXE for that user. CMD.EXE shares the same console with Program A.

Expected behavior:
Both Program A and processes running from CMD.exe should be able to use ReadConsoleOutput.

Actual behavior:
Processes running from CMD.exe get error 5 (access denied) but Process A can call the API without error.

Originally created by @luke1961 on GitHub (Apr 22, 2020). Environment: Microsoft Windows [Version 10.0.18363.720] Conhost: Version 10.0.18362.1 Steps to reproduce: 1. Make sure you are not using the 'legacy mode' or the problem will not occur 2. Launch Program A on SYSTEM account with CREATE_NEW_CONSOLE flag 3. Program A calls LogonUser, gets a token and then Uses CreateProcessAsUser to launch CMD.EXE for that user. CMD.EXE shares the same console with Program A. Expected behavior: Both Program A and processes running from CMD.exe should be able to use ReadConsoleOutput. Actual behavior: Processes running from CMD.exe get error 5 (access denied) but Process A can call the API without error.
claunia added the Product-ConhostResolution-By-DesignNeeds-Tag-Fix labels 2026-01-31 01:07:02 +00:00
Author
Owner

@DHowett-MSFT commented on GitHub (Apr 22, 2020):

@luke1961 you've filed a couple bugs on us without filling out the template. It makes it very difficult to triage them, as we have no information to go on.

Can you make sure to fill out the template in the future?

I'm going to close this one out, but you can still edit it and tell me you filled out the template. I'll reopen it. No need to file a new bug.

@DHowett-MSFT commented on GitHub (Apr 22, 2020): @luke1961 you've filed a couple bugs on us without filling out the template. It makes it very difficult to triage them, as we have no information to go on. Can you make sure to fill out the template in the future? I'm going to close this one out, but you can still _edit it_ and tell me you filled out the template. I'll reopen it. No need to file a new bug.
Author
Owner

@luke1961 commented on GitHub (Apr 22, 2020):

Environment:
Microsoft Windows [Version 10.0.18363.720]

Conhost:
Version 10.0.18362.1

Steps to reproduce:

  1. Make sure you are not using the 'legacy mode' or the problem will not occur
  2. Launch Program A on SYSTEM account with CREATE_NEW_CONSOLE flag
  3. Program A calls LogonUser, gets a token and then Uses CreateProcessAsUser to launch CMD.EXE for that user. CMD.EXE shares the same console with Program A.

Expected behavior:
Both Program A and processes running from CMD.exe should be able to use ReadConsoleOutput.

Actual behavior:
Processes running from CMD.exe get error 5 (access denied) but Process A can call the API without error.

@luke1961 commented on GitHub (Apr 22, 2020): Environment: Microsoft Windows [Version 10.0.18363.720] Conhost: Version 10.0.18362.1 Steps to reproduce: 1. Make sure you are not using the 'legacy mode' or the problem will not occur 2. Launch Program A on SYSTEM account with CREATE_NEW_CONSOLE flag 3. Program A calls LogonUser, gets a token and then Uses CreateProcessAsUser to launch CMD.EXE for that user. CMD.EXE shares the same console with Program A. Expected behavior: Both Program A and processes running from CMD.exe should be able to use ReadConsoleOutput. Actual behavior: Processes running from CMD.exe get error 5 (access denied) but Process A can call the API without error.
Author
Owner

@DHowett-MSFT commented on GitHub (Apr 22, 2020):

Oh, I see. No, this is absolutely by design. A lower-integrity process is not allowed to read the console output of a higher-integrity console.

In general, there is no reason to ever use the ReadConsoleOutput API.

@DHowett-MSFT commented on GitHub (Apr 22, 2020): Oh, I see. No, this is absolutely by design. A lower-integrity process is not allowed to read the console output of a higher-integrity console. In general, there is no reason to ever use the ReadConsoleOutput API.
Author
Owner

@luke1961 commented on GitHub (Apr 22, 2020):

I tried to fill out the template, I need to find out what I am doing wrong.

@luke1961 commented on GitHub (Apr 22, 2020): I tried to fill out the template, I need to find out what I am doing wrong.
Author
Owner

@luke1961 commented on GitHub (Apr 22, 2020):

WriteConsoleOutput works fine in the user level processes.

ReadConsoleOutput is used to preserve an area under a pulldown menu. I understand this is an 'old school' approach and one should probably use double buffering for things like that. Do you see a workaround? I tried:

//// ReadConsoleOutput fix attempt
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESIZE;
SECURITY_ATTRIBUTES SecurityAttributes;
SECURITY_DESCRIPTOR SecurityDescriptor;
//
// For now give world access.
//
if (!InitializeSecurityDescriptor(&SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION))
{
	ms15_diag(MS15_WARN, WHERE, "InitializeSecurityDescriptor");
	return FALSE;
}
if (!SetSecurityDescriptorDacl(&SecurityDescriptor, TRUE, NULL, FALSE))
{
	ms15_diag(MS15_WARN, WHERE, "SetSecurityDescriptorDacl");
	return FALSE;
}
SecurityAttributes.nLength = sizeof(SecurityAttributes);
SecurityAttributes.lpSecurityDescriptor = &SecurityDescriptor;
SecurityAttributes.bInheritHandle = TRUE; // Shell will inherit handles
HANDLE h = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &SecurityAttributes, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (h == INVALID_HANDLE_VALUE)
{
	ms15_diag(MS15_WARN, WHERE, "get CONOUT$");
	return FALSE;
}
si.hStdOutput = h;
SetStdHandle(STD_OUTPUT_HANDLE, h);
si.hStdError = h;
SetStdHandle(STD_ERROR_HANDLE, h);
h = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &SecurityAttributes, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (h == INVALID_HANDLE_VALUE)
{
	ms15_diag(MS15_WARN, WHERE, "get CONIN$");
	return FALSE;
}
si.hStdInput = h;
SetStdHandle(STD_INPUT_HANDLE, h);
ms15_diag(MS15_WARN, WHERE, "handles set");
///

before calling CreateProcessAsUseer() but that did not make any difference

@luke1961 commented on GitHub (Apr 22, 2020): WriteConsoleOutput works fine in the user level processes. ReadConsoleOutput is used to preserve an area under a pulldown menu. I understand this is an 'old school' approach and one should probably use double buffering for things like that. Do you see a workaround? I tried: //// ReadConsoleOutput fix attempt si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESIZE; SECURITY_ATTRIBUTES SecurityAttributes; SECURITY_DESCRIPTOR SecurityDescriptor; // // For now give world access. // if (!InitializeSecurityDescriptor(&SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION)) { ms15_diag(MS15_WARN, WHERE, "InitializeSecurityDescriptor"); return FALSE; } if (!SetSecurityDescriptorDacl(&SecurityDescriptor, TRUE, NULL, FALSE)) { ms15_diag(MS15_WARN, WHERE, "SetSecurityDescriptorDacl"); return FALSE; } SecurityAttributes.nLength = sizeof(SecurityAttributes); SecurityAttributes.lpSecurityDescriptor = &SecurityDescriptor; SecurityAttributes.bInheritHandle = TRUE; // Shell will inherit handles HANDLE h = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &SecurityAttributes, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (h == INVALID_HANDLE_VALUE) { ms15_diag(MS15_WARN, WHERE, "get CONOUT$"); return FALSE; } si.hStdOutput = h; SetStdHandle(STD_OUTPUT_HANDLE, h); si.hStdError = h; SetStdHandle(STD_ERROR_HANDLE, h); h = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &SecurityAttributes, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (h == INVALID_HANDLE_VALUE) { ms15_diag(MS15_WARN, WHERE, "get CONIN$"); return FALSE; } si.hStdInput = h; SetStdHandle(STD_INPUT_HANDLE, h); ms15_diag(MS15_WARN, WHERE, "handles set"); /// before calling CreateProcessAsUseer() but that did not make any difference
Author
Owner

@DHowett-MSFT commented on GitHub (Apr 22, 2020):

There’s no workaround; this restriction is in place so that a lower-integrity application cannot scrape the screen contents of a higher-integrity guest.

@DHowett-MSFT commented on GitHub (Apr 22, 2020): There’s no workaround; this restriction is in place so that a lower-integrity application cannot scrape the screen contents of a higher-integrity guest.
Author
Owner

@luke1961 commented on GitHub (Apr 22, 2020):

I see. This is a new behavior for the non-legacy conhost, right? It worked for over 20 years.

@luke1961 commented on GitHub (Apr 22, 2020): I see. This is a new behavior for the non-legacy conhost, right? It worked for over 20 years.
Author
Owner

@DHowett-MSFT commented on GitHub (Apr 22, 2020):

This is a behavior that was introduced into conhostv2 3-4 windows releases ago. Not sure on the specific date, since it predates me leading the team 😄

@DHowett-MSFT commented on GitHub (Apr 22, 2020): This is a behavior that was introduced into conhostv2 3-4 windows releases ago. Not sure on the specific date, since it predates me leading the team :smile:
Author
Owner

@luke1961 commented on GitHub (Apr 22, 2020):

I was thinking that a handle to "CONOUT$" obtained from CreateFile is a 'normal' securable object and SYSTEM account should be able to grant READ access to a user. I am not trying to be mean and try to find a hole in your software, just trying to fix an older piece of software. I am doing it because the new conhost is awesome and provides some great features that I want the older software to be able to use.

@luke1961 commented on GitHub (Apr 22, 2020): I was thinking that a handle to "CONOUT$" obtained from CreateFile is a 'normal' securable object and SYSTEM account should be able to grant READ access to a user. I am not trying to be mean and try to find a hole in your software, just trying to fix an older piece of software. I am doing it because the new conhost is awesome and provides some great features that I want the older software to be able to use.
Author
Owner

@DHowett-MSFT commented on GitHub (Apr 22, 2020):

You're absolutely right that it should be ACL-driven.

@DHowett-MSFT commented on GitHub (Apr 22, 2020): You're absolutely right that it should be ACL-driven.
Author
Owner

@luke1961 commented on GitHub (Apr 22, 2020):

Thanks for looking into the issue.

@luke1961 commented on GitHub (Apr 22, 2020): Thanks for looking into the issue.
Author
Owner

@luke1961 commented on GitHub (Apr 23, 2020):

I was able to find a fix for the problem. Here it is for those who have a similar issue. Create the CMD.EXE with CREATE_NEW_CONSOLE flag. Then Process A needs to call FreeConsole followed by AttachConsole and now both processes can call the ugly ReadConsoleOutput. That being said, I agree with Microsoft that ReadConsoleOutput is a bad idea generally and a VERY bad idea for programs using new Windows Terminal features because, for example, you will not be able to read all of the color information.

@luke1961 commented on GitHub (Apr 23, 2020): I was able to find a fix for the problem. Here it is for those who have a similar issue. Create the CMD.EXE with CREATE_NEW_CONSOLE flag. Then Process A needs to call FreeConsole followed by AttachConsole and now both processes can call the ugly ReadConsoleOutput. That being said, I agree with Microsoft that ReadConsoleOutput is a bad idea generally and a VERY bad idea for programs using new Windows Terminal features because, for example, you will not be able to read all of the color information.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#7550