-
Notifications
You must be signed in to change notification settings - Fork 8.5k
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
Allow windows created by console apps to appear above the Terminal #12799
Conversation
…viously_ that's gonna blow this diff up.
Doesn't this introduce a fairly straightforward bypass for the SetForegroundWindow restrictions? Couldn't I write a (malicious/user-hostile) program that creates a PTY, tells ConPTY it has focus (when it doesn't), then use this mechanism to launch an instance of itself into the foreground? |
.... yes. Yes it does. I'm clearly not up and up on all my Win32-isms, so I maybe wasn't totally familiar with that restriction. Okay, thought: we link this up with #12526. When Terminal gets the focus notification, it checks if the owner hwnd's process is the foreground process, with @malxau I actually would really appreciate your feedback here, cause clearly I'm not familiar with the intricacies of this bit of User32 |
## Window shenanigans, part the first: This PR enables terminals to tell ConPTY what the owning window for the pseudo window should be. This allows thigs like MessageBoxes created by console applications to work. It also enables console apps to use `GetAncestor(GetConsoleWindow(), GA_ROOT)` to get directly at the HWND of the Terminal (but _don't please_). This is tested with our internal partners and seems to work for their scenario. See #2988, #12799, #12515, #12570. ## PR Checklist This is 1/3 of #2988.
const bool hasFocus{ WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS) }; | ||
const auto grantSetForeground{ hasFocus }; | ||
gci.ProcessHandleList.ModifyConsoleProcessFocus(grantSetForeground); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this change needed? It's equivalent to the previous code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I suppose the code change bits aren't needed anymore. They were artifacts of debugging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we revert it and leave the comment?
|
||
if (!WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS)) | ||
// GH#2988: ConPTY can now be focused, but it doesn't need to do any of this work either. | ||
if (inConpty || !WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plz file a followup to make sure that this timer doesn't even get scheduled? For every hidden OpenConsole window, we are burning a timer every 250-500ms to... check some booleans that will never be true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CONSOLE_INFORMATION
creates_blinker
as a stack memberCursorBlinker
ctor doesCreateThreadpoolTimer
- We don't start the timer till the call to
SetCaretTimer
SetCaretTimer
is called in two places:CursorBlinker::FocusStart
, which is only called inWM_SETFOCUS
inwndproc.cpp
, so that's never hit for conptyCursorBlinker::SettingsChanged
, which is conveniently only calledWM_SETTINGCHANGE
. So also never for conpty.
It's probably coincidence that the blinker was never started for conpty, but I'm not terribly afraid of this regressing any time soon
const bool hasFocus{ WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS) }; | ||
const auto grantSetForeground{ hasFocus }; | ||
gci.ProcessHandleList.ModifyConsoleProcessFocus(grantSetForeground); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we revert it and leave the comment?
####⚠️ _Targets #12799_⚠️ This is an atomic bit of code that partners with #12799. It's separated as an individual PR to keep diffs more simple. This ensures that when a terminal tells ConPTY that it's focused, that ConPTY doesn't do the `ConsoleControl(CONSOLE_FOREGROUND` thing unless the terminal application is actually in the foreground. This prevents a trivial exploit whereby a `malicious.exe` could create a PTY, tell ConPTY it has focus (when it doesn't), then use this mechanism to launch an instance of itself into the foreground. When the terminal tells us it's in the foreground, we're gonna look at the owner of the ConPTY window handle. If that owner has focus, then cool, this is allowed. Otherwise, we won't grant them the FG right. For this to work, the terminal just have already called `ReparentPseudoConsole`. * built on top of #12799 and #12526 * [x] Part of #2988 * [x] Tested manually.
Further builds on #12799. #12799 assumes that the connection is prepared to receive FocusIn/FocusOut events as input. For ConPTY we can be relatively sure of that, but that's not _technically_ correct. In the hypothetical world where the connection is not a ConPTY connection, then the other side might not be expecting those sequences. This remedies the issue by * ConPTY will always request focus event mode (from the terminal) when it starts up * when a client tries to disable focus events in conpty, conpty is gonna note that internally, but never transmit that to the hosting terminal, to leave the terminal in focus event mode. * `TerminalDispatch` and `ControlCore` are hooked up now to only send focus events when the Terminal is in focus event mode (which will be always for conpty) * At this point, it was like, 4LOC in `terminalInput.cpp` to add support for focus events to conhost as well. ## checklist * [x] closes #11682 * This combined with #12515 will finally close out #2988 as well, but we can do that manually. * [x] I work here * [ ] There aren't tests for this. There probably should be.
🎉 Handy links: |
See also: #12799, the origin of much of this. This change evolved over multiple phases. ### Part the first When we create a defterm connection in `TerminalPage::_OnNewConnection`, we don't have the hosting HWND yet, so the tab gets created without one. We'll later get called with the owner, in `Initialize`. To remedy this, we need to: * In `Initialize`, make sure to update any existing controls with the new owner. * In `ControlCore`, actually propogate the new owner down to the connection ### Part the second DefTerm launches don't actually request focus mode, so the Terminal never sends them focus events. We need those focus events so that the console can request foreground rights. To remedy this, we need to: * pass `--win32input` to the commandline used to initialize OpenConsole in ConPTY mode. We request focus events at the same time we request win32-input-mode. * I also added `--resizeQuirk`, because _by all accounts that should be there_. Resizing in defterm windows should be _wacky_ without it, and I'm a little surprised we haven't seen any bugs due to this yet. ### Part the third `ConsoleSetForeground` expects a `HANDLE` to the process we want to give foreground rights to. The problem is, the wire format we used _also_ decided that a HANDLE value was a good idea. It's not. If we pass the literal value of the HANDLE to the process from OpenConsole to conhost, so conhost can call that API, the value that conhost uses there will most likely be an invalid handle. The HANDLE's value is its value in _OpenConsole_, not in conhost. To remedy this, we need to: * Just not forward `ConsoleSetForeground`. Turns out, we _can_ just call that in OpenConsole safely. There's no validation. So just instantiate a static version of the Win32 version of ConsoleControl, just to use for SetForeground. (thanks Dustin) * [x] Tested manually - Win+R `powershell`, `notepad` spawns on top. Closes #13211
See also: #12799, the origin of much of this. This change evolved over multiple phases. When we create a defterm connection in `TerminalPage::_OnNewConnection`, we don't have the hosting HWND yet, so the tab gets created without one. We'll later get called with the owner, in `Initialize`. To remedy this, we need to: * In `Initialize`, make sure to update any existing controls with the new owner. * In `ControlCore`, actually propogate the new owner down to the connection DefTerm launches don't actually request focus mode, so the Terminal never sends them focus events. We need those focus events so that the console can request foreground rights. To remedy this, we need to: * pass `--win32input` to the commandline used to initialize OpenConsole in ConPTY mode. We request focus events at the same time we request win32-input-mode. * I also added `--resizeQuirk`, because _by all accounts that should be there_. Resizing in defterm windows should be _wacky_ without it, and I'm a little surprised we haven't seen any bugs due to this yet. `ConsoleSetForeground` expects a `HANDLE` to the process we want to give foreground rights to. The problem is, the wire format we used _also_ decided that a HANDLE value was a good idea. It's not. If we pass the literal value of the HANDLE to the process from OpenConsole to conhost, so conhost can call that API, the value that conhost uses there will most likely be an invalid handle. The HANDLE's value is its value in _OpenConsole_, not in conhost. To remedy this, we need to: * Just not forward `ConsoleSetForeground`. Turns out, we _can_ just call that in OpenConsole safely. There's no validation. So just instantiate a static version of the Win32 version of ConsoleControl, just to use for SetForeground. (thanks Dustin) * [x] Tested manually - Win+R `powershell`, `notepad` spawns on top. Closes #13211 (cherry picked from commit 2a7dd8b) Service-Card-Id: 83026847 Service-Version: 1.14
Window shenanigans, part the third:
Hooks the Terminal's focus state up to the underlying ConPTY. This is LOAD BEARING for allowing windows created by console applications to bring themselves to the foreground.
We're using the FocusIn/FocusOut sequences to communicate to ConPTY when a control gains/loses focus. Theoretically, other terminals could do this as well.
References
#11682 tracks real support for this sequence in Console & conpty. When we do that, we should consider even if a client application disables this mode, the Terminal & conpty should always request this from the hosting terminal (and just ignore internally to ConPTY).
See also #12515, #12526, which are the other two parts of this effort. This was tested with all three merged together, and they worked beautifully for all our scenarios. They are kept separate for ease of review.
PR Checklist
GetConsoleWindow
) #2988Detailed Description of the Pull Request / Additional comments
This allows windows spawned by console processes to bring themselves to the foreground when the console is focused. (Historically, this is also called in the WndProc, when focus changes).
Notably, before this, ConPTY was never focused, so windows could never bring themselves to the foreground when run from a ConPTY console. We're not blanket granting the SetForeground right to all console apps when run in ConPTY. It's the responsibility of the hosting terminal emulator to always tell ConPTY when a particular instance is focused.
Validation Steps Performed
(gif below)