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

WinUpdater: Quote path for args when relaunching Dolphin. #11666

Merged
merged 1 commit into from Mar 18, 2023

Conversation

AdmiralCurtiss
Copy link
Contributor

This should fix https://forums.dolphin-emu.org/Thread-recent-update-triggering-this-error-on-auto-update

I think just adding quotes on both ends is enough, because a valid path on Windows cannot contain quotes by itself.

@delroth delroth requested a review from shuffle2 March 17, 2023 04:13
@shuffle2
Copy link
Contributor

shuffle2 commented Mar 17, 2023

since it only broke recently, i think it's better to only do this for CreateProcess path.

https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa

also, if it's taking the ShellExecute path...it means the user is running dolphin as admin (or, the updater decided it needed to elevate), which Is Bad. And it appears to be in My Documents, which doesn't require admin, anyway.

@AdmiralCurtiss
Copy link
Contributor Author

Fair enough.

@shuffle2
Copy link
Contributor

shuffle2 commented Mar 17, 2023

Also, you can use test-updater.py to repro the bug and check that it’s fixed (passing dir param to temporarydirectory). Maybe the test should just be changed to ensure there’s always a space and Unicode in the path or something…

Copy link
Contributor

@shuffle2 shuffle2 left a comment

Choose a reason for hiding this comment

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

According to msdn both args to create process need quotes. Try using the test script :)

@AdmiralCurtiss
Copy link
Contributor Author

AdmiralCurtiss commented Mar 17, 2023

I did test this yesterday and it works as written, but yeah maybe some dumb Windows fallback is saving me here...

(seriously though why is the API like this? yeah backcompat and all but this really feels like someone should have made a saner variant in a later windows version...)

@AdmiralCurtiss
Copy link
Contributor Author

Btw the way I'm reading the MSDN doc is that the application name only needs to be quoted if either lpApplicationName or lpCommandLine is NULL (since that causes the non-NULL of them to be interpreted as the command line); and if neither is NULL then the lpApplicationName is the path to the application (which doesn't need quotes, based on the security remark at the bottom) and the lpCommandLine does need quotes since it doesn't know where to split the command line arguments otherwise.

@AdmiralCurtiss
Copy link
Contributor Author

Okay, I tested this out under the following scenario taking into account the Security stuff on the MSDN page:

Clipboard0233


c:\Test\a dir\a.exe is the 'correct' executable that we want to launch. It's a simple C# WinForms application that prints 'Correct Executable', and in the textbox its own path, then its working directory, then its arguments.

Clipboard01

C# Code
namespace WinFormsApp1 {
	internal static class Program {
		/// <summary>
		///  The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main(string[] args) {
			// To customize application configuration such as set high DPI settings or default font,
			// see https://aka.ms/applicationconfiguration.
			ApplicationConfiguration.Initialize();
			Application.Run(new Form1(args));
		}
	}

	public partial class Form1 : Form {
		public Form1(string[] args = null) {
			InitializeComponent();

			textBox1.Text += System.Reflection.Assembly.GetExecutingAssembly().Location;
			textBox1.Text += "\r\n";
			textBox1.Text += Directory.GetCurrentDirectory();
			textBox1.Text += "\r\n";
			textBox1.Text += "\r\n";
			foreach (string s in args) {
				textBox1.Text += s + "\r\n";
			}
		}
	}
}

c:\Test\a.exe is largely identical but prints 'Wrong Executable' instead.

Clipboard04


We now attempt to launch this in a few different ways using CreateProcessW():

Both parameters, both unquoted
#include <string>

#include <Windows.h>

int main(int argc, char** argv) {
	std::wstring unquoted = L"c:\\Test\\a dir\\a.exe";
	std::wstring quoted = L"\"" + unquoted + L"\"";

	std::wstring lpApplicationName = unquoted;
	std::wstring lpCommandLine = unquoted;

	STARTUPINFOW startup_info{ .cb = sizeof(startup_info) };
	PROCESS_INFORMATION process_info;
	BOOL rv = CreateProcessW(lpApplicationName.data(), lpCommandLine.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup_info, &process_info);
	printf("%s\n", rv ? "launch success" : "launch failed");
}

==> Launches correct executable (prints launch success) with:

c:\Test\a dir\a.dll
c:\Test

dir\a.exe

So we launched the correct file, but with the argument dir\a.exe. If we delete c:\Test\a dir\a.exe nothing gets launched (prints launch failed).

Both parameters, both quoted
#include <string>

#include <Windows.h>

int main(int argc, char** argv) {
	std::wstring unquoted = L"c:\\Test\\a dir\\a.exe";
	std::wstring quoted = L"\"" + unquoted + L"\"";

	std::wstring lpApplicationName = quoted;
	std::wstring lpCommandLine = quoted;

	STARTUPINFOW startup_info{ .cb = sizeof(startup_info) };
	PROCESS_INFORMATION process_info;
	BOOL rv = CreateProcessW(lpApplicationName.data(), lpCommandLine.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup_info, &process_info);
	printf("%s\n", rv ? "launch success" : "launch failed");
}

==> Does not launch anything. Prints launch failed.

Both parameters, lpApplicationName unquoted, lpCommandLine quoted
#include <string>

#include <Windows.h>

int main(int argc, char** argv) {
	std::wstring unquoted = L"c:\\Test\\a dir\\a.exe";
	std::wstring quoted = L"\"" + unquoted + L"\"";

	std::wstring lpApplicationName = unquoted;
	std::wstring lpCommandLine = quoted;

	STARTUPINFOW startup_info{ .cb = sizeof(startup_info) };
	PROCESS_INFORMATION process_info;
	BOOL rv = CreateProcessW(lpApplicationName.data(), lpCommandLine.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup_info, &process_info);
	printf("%s\n", rv ? "launch success" : "launch failed");
}

==> Launches correct executable (prints launch success) with:

c:\Test\a dir\a.dll
c:\Test

So the process launched and did not get any arguments, exactly what we want.

Both parameters, lpApplicationName quoted, lpCommandLine unquoted
#include <string>

#include <Windows.h>

int main(int argc, char** argv) {
	std::wstring unquoted = L"c:\\Test\\a dir\\a.exe";
	std::wstring quoted = L"\"" + unquoted + L"\"";

	std::wstring lpApplicationName = quoted;
	std::wstring lpCommandLine = unquoted;

	STARTUPINFOW startup_info{ .cb = sizeof(startup_info) };
	PROCESS_INFORMATION process_info;
	BOOL rv = CreateProcessW(lpApplicationName.data(), lpCommandLine.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup_info, &process_info);
	printf("%s\n", rv ? "launch success" : "launch failed");
}

==> Does not launch anything. Prints launch failed.

For completeness sake:

lpCommandLine only, unquoted
#include <string>

#include <Windows.h>

int main(int argc, char** argv) {
	std::wstring unquoted = L"c:\\Test\\a dir\\a.exe";
	std::wstring quoted = L"\"" + unquoted + L"\"";

	//std::wstring lpApplicationName;
	std::wstring lpCommandLine = unquoted;

	STARTUPINFOW startup_info{ .cb = sizeof(startup_info) };
	PROCESS_INFORMATION process_info;
	BOOL rv = CreateProcessW(NULL, lpCommandLine.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup_info, &process_info);
	printf("%s\n", rv ? "launch success" : "launch failed");
}

==> Launches the wrong executable (launch success) with:

c:\Test\a.dll
c:\Test

dir\a.exe

That's not too surprising, that's exactly what the MSDN article warns against doing.

lpApplicationName only, unquoted
#include <string>

#include <Windows.h>

int main(int argc, char** argv) {
	std::wstring unquoted = L"c:\\Test\\a dir\\a.exe";
	std::wstring quoted = L"\"" + unquoted + L"\"";

	std::wstring lpApplicationName = unquoted;
	//std::wstring lpCommandLine = unquoted;

	STARTUPINFOW startup_info{ .cb = sizeof(startup_info) };
	PROCESS_INFORMATION process_info;
	BOOL rv = CreateProcessW(lpApplicationName.data(), NULL, nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup_info, &process_info);
	printf("%s\n", rv ? "launch success" : "launch failed");
}

==> Launches the correct executable (launch success) with:

c:\Test\a dir\a.dll
c:\Test

This one actually surprises me! I would have expected this to behave the same as the lpCommandLine only variant based on the documentation.

If we delete the correct executable this fails. (launch failed)

lpApplicationName only, quoted
#include <string>

#include <Windows.h>

int main(int argc, char** argv) {
	std::wstring unquoted = L"c:\\Test\\a dir\\a.exe";
	std::wstring quoted = L"\"" + unquoted + L"\"";

	std::wstring lpApplicationName = quoted;
	//std::wstring lpCommandLine = unquoted;

	STARTUPINFOW startup_info{ .cb = sizeof(startup_info) };
	PROCESS_INFORMATION process_info;
	BOOL rv = CreateProcessW(lpApplicationName.data(), NULL, nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup_info, &process_info);
	printf("%s\n", rv ? "launch success" : "launch failed");
}

==> Fails to launch anything. (launch failed)

lpCommandLine only, quoted
#include <string>

#include <Windows.h>

int main(int argc, char** argv) {
	std::wstring unquoted = L"c:\\Test\\a dir\\a.exe";
	std::wstring quoted = L"\"" + unquoted + L"\"";

	//std::wstring lpApplicationName = quoted;
	std::wstring lpCommandLine = quoted;

	STARTUPINFOW startup_info{ .cb = sizeof(startup_info) };
	PROCESS_INFORMATION process_info;
	BOOL rv = CreateProcessW(NULL, lpCommandLine.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup_info, &process_info);
	printf("%s\n", rv ? "launch success" : "launch failed");
}

==> Launches the correct executable (launch success) with:

c:\Test\a dir\a.dll
c:\Test

If we delete the executable it fails. (launch failed)

@AdmiralCurtiss
Copy link
Contributor Author

Based on this:

  • Adding quotes to the first parameter is definitely wrong, it never launches anything if you do that.
  • Adding quotes to the second parameter is correct.
  • Leaving the second parameter NULL seems to behave exactly like passing no arguments. The documentation seems to disagree with this (The lpCommandLine parameter can be NULL. In that case, the function uses the string pointed to by lpApplicationName as the command line.) so I'd avoid doing this.

@AdmiralCurtiss
Copy link
Contributor Author

tldr: The PR should be correct as-is. I'll also test ShellExecuteW() shortly, just so we know what's up there...

@AdmiralCurtiss
Copy link
Contributor Author

AdmiralCurtiss commented Mar 18, 2023

#include <string>

#include <Windows.h>

int main(int argc, char** argv) {
	std::wstring unquoted = L"c:\\Test\\a dir\\a.exe";
	std::wstring quoted = L"\"" + unquoted + L"\"";
	ShellExecuteW(nullptr, nullptr, L"explorer.exe", unquoted.c_str(), nullptr, SW_SHOW);;
}

Launches the correct executable, regardless of if I pass it quoted or unquoted. So that should also be fine as-is.

If I delete the executable it just opens Explorer (in My Documents), which is weird but at least not harmful.

@AdmiralCurtiss AdmiralCurtiss merged commit fb7a371 into dolphin-emu:master Mar 18, 2023
14 checks passed
@AdmiralCurtiss AdmiralCurtiss deleted the updater-spaces branch March 18, 2023 17:32
@MayImilae
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants