diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index 035a981483b..ccf90a56a15 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -1,6 +1,7 @@ apc calt ccmp +cybersecurity Apc clickable clig diff --git a/.github/actions/spelling/expect/web.txt b/.github/actions/spelling/expect/web.txt index f50d31a6e0a..3072b0075b4 100644 --- a/.github/actions/spelling/expect/web.txt +++ b/.github/actions/spelling/expect/web.txt @@ -11,6 +11,7 @@ leonerd fixterms winui appshellintegration +mdtauk cppreference gfycat Guake diff --git a/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md b/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md new file mode 100644 index 00000000000..1757352e9ca --- /dev/null +++ b/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md @@ -0,0 +1,619 @@ +--- +author: Mike Griese @zadjii-msft +created on: 2020-11-20 +last updated: 2021-08-17 +issue id: #1032 +--- + +# Elevation Quality of Life Improvements + +## Abstract + +For a long time, we've been researching adding support to the Windows Terminal +for running both unelevated and elevated (admin) tabs side-by-side, in the same +window. However, after much research, we've determined that there isn't a safe +way to do this without opening the Terminal up as a potential +escalation-of-privilege vector. + +Instead, we'll be adding a number of features to the Terminal to improve the +user experience of working in elevated scenarios. These improvements include: + +* A visible indicator that the Terminal window is elevated ([#1939]) +* Configuring the Terminal to always run elevated ([#632]) +* Configuring a specific profile to always open elevated ([#632]) +* Allowing new tabs, panes to be opened elevated directly from an unelevated + window +* Dynamic profile appearance that changes depending on if the Terminal is + elevated or not. ([#1939], [#8311]) + +## Background + +_This section was originally authored in the [Process Model 2.0 Spec]. Please +refer to it there for its original context._ + +Let's presume that you're a user who wants to be able to open an elevated tab +within an otherwise unelevated Terminal window. We call this scenario "mixed +elevation" - the tabs within the Terminal can be running either unelevated _or_ +elevated client applications. + +It wouldn't be terribly difficult for the unelevated Terminal to request the +permission of the user to spawn an elevated client application. The user would +see a UAC prompt, they'd accept, and then they'd be able to have an elevated +shell alongside their unelevated tabs. + +However, this creates an escalation of privilege vector. Now, there's an +unelevated window which is connected directly to an elevated process. At this +point, **any other unelevated application could send input to the Terminal's +`HWND`**. This would make it possible for another unelevated process to "drive" +the Terminal window, and send commands to the elevated client application. + +It was initially theorized that the window/content model architecture would also +help enable "mixed elevation". With mixed elevation, tabs could run at different +integrity levels within the same terminal window. However, after investigation +and research, it has become apparent that this scenario is not possible to do +safely after all. There are numerous technical difficulties involved, and each +with their own security risks. At the end of the day, the team wouldn't be +comfortable shipping a mixed-elevation solution, because there's simply no way +for us to be confident that we haven't introduced an escalation-of-privilege +vector utilizing the Terminal. No matter how small the attack surface might be, +we wouldn't be confident that there are _no_ vectors for an attack. + +Some things we considered during this investigation: + +* If a user requests a new elevated tab from an otherwise unelevated window, we + could use UAC to create a new, elevated window process, and "move" all the + current tabs to that window process, as well as the new elevated client. Now, + the window process would be elevated, preventing it from input injection, and + it would still contains all the previously existing tabs. The original window + process could now be discarded, as the new elevated window process will + pretend to be the original window. + - However, it is unfortunately not possible with COM to have an elevated + client attach to an unelevated server that's registered at runtime. Even in + a packaged environment, the OS will reject the attempt to `CoCreateInstance` + the content process object. this will prevent elevated windows from + re-connecting to unelevated client processes. + - We could theoretically build an RPC tunnel between content and window + processes, and use the RPC connection to marshal the content process to the + elevated window. However, then _we_ would need to be responsible for + securing access the the RPC endpoint, and we feel even less confident doing + that. + - Attempts were also made to use a window-broker-content architecture, with + the broker process having a static CLSID in the registry, and having the + window and content processes at mixed elevation levels `CoCreateInstance` + that broker. This however _also_ did not work across elevation levels. This + may be due to a lack of Packaged COM support for mixed elevation levels. + + It's also possible that the author forgot that packaged WinRT doesn't play + nicely with creating objects in an elevated context. The Terminal has + previously needed to manually manifest all its classes in a SxS manifest for + Unpackaged WinRT to allow the classes to be activated, rather than relying + on the packaged catalog. It's theoretically possible that doing that would + have allowed the broker to be activated across integrity levels. + + Even if this approach did end up working, we would still need to be + responsible for securing the elevated windows so that an unelevated attacker + couldn't hijack a content process and trigger unexpected code in the window + process. We didn't feel confident that we could properly secure this channel + either. + +We also considered allowing mixed content in windows that were _originally_ +elevated. If the window is already elevated, then it can launch new unelevated +processes. We could allow elevated windows to still create unelevated +connections. However, we'd want to indicate per-pane what the elevation state +of each connection is. The user would then need to keep track themselves of +which terminal instances are elevated, and which are not. + +This also marks a departure from the current behavior, where everything in an +elevated window would be elevated by default. The user would need to specify for +each thing in the elevated window that they'd want to create it elevated. Or the +Terminal would need to provide some setting like +`"autoElevateEverythingInAnElevatedWindow"`. + +We cannot support mixed elevation when starting in a unelevated window. +Therefore, it doesn't make a lot of UX sense to support it in the other +direction. It's a cleaner UX story to just have everything in a single window at +the same elevation level. + +## Solution Design + +Instead of supporting mixed elevation in the same window, we'll introduce the +following features to the Terminal. These are meant as a way of improving the +quality of life for users who work in mixed-elevation (or even just elevated) +environments. + +### Visible indicator for elevated windows + +As requested in [#1939], it would be nice if it was easy to visibly identify if +a Terminal window was elevated or not. + +One easy way of doing this is by adding a simple UAC shield to the left of the +tabs for elevated windows. This shield could be configured by the theme (see +[#3327]). We could provide the following states: +* Colored (the default) +* Monochrome +* Hidden, to hide the shield even on elevated windows. This is the current + behavior. + +![UAC-shield-in-titlebar](UAC-shield-in-titlebar.png) +_figure 1: a monochrome UAC shield in the titlebar of the window, courtesy of @mdtauk_ + +We could also simplify this to only allow a boolean true/false for displaying +the shield. As we do often with other enums, we could define `true` to be the +same as the default appearance, and `false` to be the hidden option. As always, +the development of the Terminal is an iterative process, where we can +incrementally improve from no setting, to a boolean setting, to a enum-backed +one. + +### Configuring a profile to always run elevated + +Oftentimes, users might have a particular tool chain that only works when +running elevated. In these scenarios, it would be convenient for the user to be +able to identify that the profile should _always_ run elevated. That way, they +could open the profile from the dropdown menu of an otherwise unelevated window +and have the elevated window open with the profile automatically. + +We'll be adding the `"elevate": true|false` setting as a per-profile setting, +with a default value of `false`. When set to `true`, we'll try to auto-elevate +the profile whenever it's launched. We'll check to see if this window is +elevated before creating the connection for this profile. If the window is not +elevated, then we'll create a new window with the requested elevation level to +handle the new connection. + +`"elevate": false` will do nothing. If the window is already elevated, then the +profile won't open an un-elevated window. + +If the user tries to open an `"elevate": true` profile in a window that's +already elevated, then a new tab/split will open in the existing window, rather +than spawning an additional elevated window. + +There are three situations where we're creating new terminal instances: new +tabs, new splits, and new windows. Currently, these are all actions that are +also exposed in the `wt` commandline as subcommands. We can convert from the +commandline arguments into these actions already. Therefore, it shouldn't be too +challenging to convert these actions back into the equal commandline arguments. + +For the following examples, let's assume the user is currently in an unelevated +Terminal window. + +When the user tries to create a new elevated **tab**, we'll need to create a new +process, elevated, with the following commandline: + +``` +wt new-tab [args...] +``` + +When we create this new `wt` instance, it will obey the glomming rules as +specified in [Session Management Spec]. It might end up glomming to another +existing window at that elevation level, or possibly create its own window. + +Similarly, for a new elevated **window**, we can make sure to pass the `-w new` +arguments to `wt`. These parameters indicate that we definitely want this +command to run in a new window, regardless of the current glomming settings. + +``` +wt -w new new-tab [args...] +``` + +However, creating a new **pane** is a little trickier. Invoking the `wt +split-pane [args...]` is straightforward enough. + + + +After discussing with the team, we have decided that the most sensible approach +for handling a cross-elevation `split-pane` is to just create a new tab in the +elevated window. The user can always re-attach the pane as a split with the +`move-pane` command once the new pane in the elevated window. + +#### Configure the Terminal to _always_ run elevated + +`elevate` is a per-profile property, not a global property. If a user +wants to always have all instances of the Terminal run elevated, they +could set `"elevate": true` in their profile defaults. That would cause _all_ +profiles they launch to always spawn as elevated windows. + +#### `elevate` in Actions + +Additionally, we'll add the `elevate` property to the `NewTerminalArgs` used in +the `newTab`, `splitPane`, and `newWindow` actions. This is similar to how other +properties of profiles can be overridden at launch time. This will allow +windows, tabs and panes to all be created specifically as elevated windows. + +In the `NewTerminalArgs`, `elevate` will be an optional boolean, with the +following behavior: +* `null` (_default_): Don't modify the `elevate` property for this profile +* `true`: This launch should act like the profile had `"elevate": true` in its + properties. +* `false`: This launch should act like the profile had `"elevate": false` in its + properties. + +We'll also add an iterable command for opening a profile in an +elevated tab, with the following json: + +```jsonc +{ + // New elevated tab... + "name": { "key": "NewElevatedTabParentCommandName", "icon": "UAC-Shield.png" }, + "commands": [ + { + "iterateOn": "profiles", + "icon": "${profile.icon}", + "name": "${profile.name}", + "command": { "action": "newTab", "profile": "${profile.name}", "elevated": true } + } + ] +}, +``` + +#### Elevation from the dropdown + +Currently, the new tab dropdown supports opening a new pane by +Alt+clicking on a profile. We could similarly add support to open a +tab elevated with Ctrl+click. This is similar to the behavior of the +Windows taskbar. It supports creating an elevated instance of a program by +Ctrl+clicking on entries as well. + +## Implementation Details + +### Starting an elevated process from an unelevated process + +It seems that we're able to create an elevated process by passing the `"runas"` +verb to +[`ShellExecute`](https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea). +So we could use something like + +```c++ +ShellExecute(nullptr, + L"runas", + L"wt.exe", + L"-w new new-tab [args...]", + nullptr, + SW_SHOWNORMAL); +``` + +This will ask the shell to perform a UAC prompt before spawning `wt.exe` as an +elevated process. + +> 👉 NOTE: This mechanism won't always work on non-Desktop SKUs of Windows. For +> more discussion, see [Elevation on OneCore SKUs](#Elevation-on-OneCore-SKUs). + +## Potential Issues + +
Accessibility | ++ +The set of changes proposed here are not expected to introduce any new +accessibility issues. Users can already create elevated Terminal windows. Making +it easier to create these windows doesn't really change our accessibility story. + + | +
Security | +
+
+We won't be doing anything especially unique, so there aren't expected to be any
+substantial security risks associated with these changes. Users can already
+create elevated Terminal windows, so we're not really introducing any new
+functionality, from a security perspective.
+
+We're relying on the inherent security of the `runas` verb of `ShellExecute` to
+prevent any sort of unexpected escalation-of-privilege.
+
+ + +One security concern is the fact that the `settings.json` file is currently a +totally unsecured file. It's completely writable by any medium-IL process. That +means it's totally possible for a malicious program to change the file. The +malicious program could find a user's "Elevated PowerShell" profile, and change +the commandline to `malicious.exe`. The user might then think that their +"Elevated PowerShell" will run `powershell.exe` elevated, but will actually +auto-elevate this attacker. + +If all we expose to the user is the name of the profile in the UAC dialog, then +there's no way for the user to be sure that the program that's about to be +launched is actually what they expect. + +To help mitigate this, we should _always_ pass the evaluated `commandline` as a +part of the call to `ShellExecute`. the arguments that are passed to +`ShellExecute` are visible to the user, though they need to click the "More +Details" dropdown to reveal them. + +We will need to mitigate this vulnerability regardless of adding support for the +auto-elevation of individual terminal tabs/panes. If a user is launching the +Terminal elevated (i.e. from the Win+X menu in Windows 11), then it's possible +for a malicious program to overwrite the `commandline` of their default profile. +The user may now unknowingly invoke this malicious program while thinking they +are simply launching the Terminal. + +To deal with this more broadly, we will display a dialog within the Terminal +window before creating **any** elevated terminal instance. In that dialog, we'll +display the commandline that will be executed, so the user can very easily +confirm the commandline. + +This will need to happen for all elevated terminal instances. For an elevated +Windows Terminal window, this means _all_ connections made by the Terminal. +Every time the user opens a new profile or a new commandline in a pane, we'll +need to prompt them first to confirm the commandline. This dialog within the +elevated window will also prevent an attacker from editing the `settings.json` +file while the user already has an elevated Terminal window open and hijacking a +profile. + +The dialog options will certainly be annoying to users who don't want to be +taken out of their flow to confirm the commandline that they wish to launch. +There's precedent for a similar warning being implemented by VSCode, with their +[Workspace Trust] feature. They too faced a similar backlash when the feature +first shipped. However, in light of recent global cybersecurity attacks, this is +seen as an acceptable UX degradation in the name of application trust. We don't +want to provide an avenue that's too easy to abuse. + +When the user confirms the commandline of this profile as something safe to run, +we'll add it to an elevated-only version of `state.json`. (see [#7972] for more +details). This elevated version of the file will only be accessible by the +elevated Terminal, so an attacker cannot hijack the contents of the file. This +will help mitigate the UX discomfort caused by prompting on every commandline +launched. This should mean that the discomfort is only limited to the first +elevated launch of a particular profile. Subsequent launches (without modifying +the `commandline`) will work as they always have. + +The dialog for confirming these commandlines should have a link to the docs for +"Learn more...". Transparency in the face of this dialog should +mitigate some dissatisfaction. + +The dialog will _not_ appear if the user does not have a split token - if the +user's PC does not have UAC enabled, then they're _already_ running as an +Administrator. Everything they do is elevated, so they shouldn't be prompted in +this way. + +The Settings UI should also expose a way of viewing and removing these cached +entries. This page should only be populated in the elevated version of the +Terminal. + + |
+
Reliability | ++ +No changes to our reliability are expected as a part of this change. + + | +
Compatibility | ++ +There are no serious compatibility concerns expected with this changelist. The +new `elevate` property will be unset by default, so users will heed to opt-in +to the new auto-elevating behavior. + +There is one minor concern regarding introducing the UAC shield on the window. +We're planning on using themes to configure the appearance of the shield. That +means we'll need to ship themes before the user will be able to hide the shield +again. + + | +
Performance, Power, and Efficiency | ++ +No changes to our performance are expected as a part of this change. + + | +