Skip to content
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

Enforce single install across winget processes #3118

Merged
merged 5 commits into from
Mar 31, 2023

Conversation

JohnMcPMS
Copy link
Member

@JohnMcPMS JohnMcPMS commented Mar 30, 2023

Change

This change uses a named mutex to enforce a single install/uninstall operation is occurring for the entire session. The current behavior is that every process was able to install a single thing at a time, but as we have more usage this could lead to a potential issue.

The mutex is acquired and held for the workflow task that routes out to the installer type specific tasks. Some installer types are explicitly excluded, as they are known to contain their own synchronization internally.

In addition to the mutex, the ability to set a message to show with the indefinite progress (spinner) was added. This allows for the message to be removed when the wait is over. The message was not implemented into the progress bar, nor is it being sent to COM progress.

The COM scheduling was not made aware of the mutex, and so it will execute as it has in the past, blocking on the mutex when appropriate. This could be improved in the future to enable the parallel capable installer types to operate. One potential option would be a second installer processing thread that only operated on the excluded types, and thus would never block.

Validation

Manually validated the user experience with an interactive install.
Automated tests are added to ensure that the lock is held during the installation workflow.

Microsoft Reviewers: Open in CodeFlow

@JohnMcPMS JohnMcPMS requested a review from a team as a code owner March 30, 2023 00:07
else
{
// Best effort when no VT (arbitrary number of spaces that seems to work)
m_out << "\r \r";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: How about something like m_out << '\r' << std::string(N, ' ') << '\r'; so that we know how many spaces there are and can extend it if needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to get the current console width.


CrossProcessInstallLock::CrossProcessInstallLock()
{
m_mutex.create(L"WinGetCrossProcessInstallLock", 0, SYNCHRONIZE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be worried about some third party acquiring this named mutex and just making winget unusable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add a random suffix to the mutex (and any other named mutexes), but we would have to write that information into a location that the unelevated user token can access. While it would make it slightly harder to execute this attack, it wouldn't be any harder from a security perspective.

I suppose that we could put a conditional ACE on it to require our package identity (when packaged) to even read from it. Even that is just another minor bump in the road that can be overcome with more code (but not any more security access).

@@ -1821,4 +1821,7 @@ Please specify one of them using the --source option to proceed.</value>
<value>Reboot required to fully enable the Windows Feature(s); proceeding due to --force</value>
<comment>"Windows Feature(s)" is the name of the options Windows features setting.</comment>
</data>
<data name="InstallWaitingOnAnother" xml:space="preserve">
<value>Waiting on another install or uninstall to complete...</value>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Shouldn't it be "Waiting for"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out that my southern US origins may be the source of this based on a brief look. They both are "correct" but various sources give differing opinions, but that one vs the other is colloquial. I have no problem changing it though.

@@ -24,7 +24,7 @@ TEST_CASE("CPRWL_MultipleReaders", "[CrossProcessReaderWriteLock]")
otherThread.detach();

// Wait up to a second for the other thread to do one thing...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Update comment

REQUIRE(signal.wait(500));
}

TEST_CASE("CPIL_BlocksOthers", "[CrossProcessInstallLock]")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Do we really need to abbreviate CPIL?


std::thread otherThread([&signal, &progress]() {
CrossProcessInstallLock otherThreadLock;
otherThreadLock.Acquire(progress);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we REQUIRE(!otherThreadLock.Acquire(progres);?

@JohnMcPMS JohnMcPMS merged commit 741244e into microsoft:master Mar 31, 2023
@JohnMcPMS JohnMcPMS deleted the single-install branch March 31, 2023 00:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants