microsoft / microsoft/winget-cli

Automatically drop elevation for "ElevationRequirement: elevationProhibited" pkgs

Open
#4,084 0 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue-Feature
Dominant language
C++
Stars
26.4k
Forks
1.8k
Avg merge
1d 11h
Merged PRs (30d)
15

Description

Description of the new feature / enhancement

Winget should automatically drop privileges when trying to install a package that has "ElevationRequirement: elevationProhibited" specified (example package Spotify.Spotify).
Currently winget just fails. This is bad from a UX perspective in two ways.

  1. winget currently requests elevation for every single app it tries to install and taht request them. This normally would be mitigated by just starting it from an elevated prompt or when ran from a script starting its process using the elevation flag set.
  2. When the approach from 1 is used winget will fail the installation without any way for a caller to prevent this. Also a caller has no way to know upfront which packages need and prohibit elevation. Therefore a caller only has the option to start winget without elevation, which will cause individual UAC prompts. However this can easily cause "security fatigue", e.g. users disabling the UAC prompts entirely and using silent elevation.
Proposed technical implementation details

Use ImpersonateLoggedOnUser to impersonate our own liked (LUA) access token (which we can acquire e.g. by calling CreateRestrictedToken with the LUA_TOKEN flag set as well as our own token from GetCurrentProcessToken). And from within that LUA context, we can call CreateProcessAsUser (instead of just CreateProcess, we however could just switch to always using CreateProcessAsUser and keep our code simple) and it will inherit the LUA token instead of the full one (avoiding the undesired "feature" of it that would allow the new process to access the executable and its directory using our full token).

Interestingly we cannot use the CreateProcessWithToken function, as it mandates the SeImpersonatePrivilege even for our case. Which is probably the only difference between it and the combination of CreateProcessAsUser and ImpersonateLoggedOnUser.

Note: Technically as we're running using the full token, we should have the SeImpersonatePrivilege, but

  1. edge cases exist using the e.g. "PowerUsers" group (does anything that supports winget still allow its usage?)
  2. security hardening measures within SOHO and enterprise may have removed Administrators from the allow list within secpol.
  3. It generates unnecessary and misleading audit events.

Edit: Technically using the above approach we could also solve the reverse use case of wanting to call multiple installers but only prompting once. We can just switch into the full token context (which will cause an UAC prompt) then install everything that needs (or basically anything that doesn't prohibit) full privileges and then switch back and destroy the privileged token (But we probably should adjust the ACLs to our own process first then, as otherwise any LUA process of the same user would be able to do stuff like e.g. copy our privileged token (which technically should be useless because of it's own ACLs) or try to inject code into our process/thread to get control over the execution...). HOWEVER these concerns are mostly irrelevant for now, as they do not apply to the full => LUA case.

Edit2: After looking into the source code of winget a bit more, it is currently using ShellExecuteExW and not CreateProcess as I assumed initially. This detail however shouldn't change the overall proposed technical implementation much.

Edit3: Relevant code sections:

  1. Where it currently fails: https://github.com/microsoft/winget-cli/blob/master/src/AppInstallerCLICore/Workflows/InstallFlow.cpp#L616
  2. Where the ShellExecuteExW Win32-API call would happen: https://github.com/microsoft/winget-cli/blob/master/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp#L38

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the failure path in src/AppInstallerCLICore/Workflows/InstallFlow.cpp around line 616, then trace installer launching in ShellExecuteInstallerHandler.cpp around line 38. Follow how elevationProhibited is handled and how ShellExecuteExW is called. Done means such packages install from an elevated winget process without retaining elevation, with the relevant behavior covered by existing tests or new tests identified during investigation.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
cli, operating-systems, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.