Bug 1508468: Convert launcher process to use mozilla::Result for error propagation; r=mhowell
authorAaron Klotz <aklotz@mozilla.com>
Tue, 20 Nov 2018 23:49:36 +0000
changeset 503827 fa0d05453365c26f404803117864654c59ff6d30
parent 503826 e27269b076334ddd520fcbab374b584942b886f2
child 503828 da37c7e24c90cd585bdf68dff8acc45aa4ba7c3c
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmhowell
bugs1508468
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1508468: Convert launcher process to use mozilla::Result for error propagation; r=mhowell This patch does a couple of things: * I added a new class, |WindowsError| to WinHeaderOnlyUtils. The idea here is to encapsulate as much of the Windows error gamut as possible into one class. Since Win32 errors and NTSTATUS codes may both be encoded as HRESULTs, I used the latter type to store the error. It also contains functions for converting between the various error code formats, as well as stringification via FormatMessage. * I added |LauncherError| which also includes file and line number information, which I believe will be important for launcher process failure diagnostics. (Instantiation of LauncherErrors obviously must be done via macros to capture __FILE__ and __LINE__). * I then converted all of the launcher process code (and its few depenencies) to utilize this new functionality via the new |LauncherResult| type. * If we detect an error in one of the top-level launcher process functions, we pass it to |HandleLauncherError| for processing. This function currently just throws up a |MessageBox| like the previous code did, with the intention of enhancing that further in the future. Differential Revision: https://phabricator.services.mozilla.com/D12365
browser/app/winlauncher/DllBlocklistWin.cpp
browser/app/winlauncher/DllBlocklistWin.h
browser/app/winlauncher/ErrorHandler.cpp
browser/app/winlauncher/ErrorHandler.h
browser/app/winlauncher/LaunchUnelevated.cpp
browser/app/winlauncher/LaunchUnelevated.h
browser/app/winlauncher/LauncherProcessWin.cpp
browser/app/winlauncher/LauncherResult.h
browser/app/winlauncher/NativeNt.h
browser/app/winlauncher/ProcThreadAttributes.h
browser/app/winlauncher/moz.build
browser/app/winlauncher/test/moz.build
ipc/mscom/COMApartmentRegion.h
widget/windows/WinHeaderOnlyUtils.h
--- a/browser/app/winlauncher/DllBlocklistWin.cpp
+++ b/browser/app/winlauncher/DllBlocklistWin.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #include "NativeNt.h"
 #include "nsWindowsDllInterceptor.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Types.h"
 #include "mozilla/WindowsDllBlocklist.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
 
 #define MOZ_LITERAL_UNICODE_STRING(s) \
   { \
     /* Length of the string in bytes, less the null terminator */ \
     sizeof(s) - sizeof(wchar_t), \
     /* Length of the string in bytes, including the null terminator */ \
     sizeof(s), \
     /* Pointer to the buffer */ \
@@ -325,111 +326,124 @@ class MOZ_RAII AutoVirtualProtect final
 {
 public:
   AutoVirtualProtect(void* aAddress, size_t aLength, DWORD aProtFlags,
                      HANDLE aTargetProcess = nullptr)
     : mAddress(aAddress)
     , mLength(aLength)
     , mTargetProcess(aTargetProcess)
     , mPrevProt(0)
+    , mError(WindowsError::CreateSuccess())
   {
-    ::VirtualProtectEx(aTargetProcess, aAddress, aLength, aProtFlags,
-                       &mPrevProt);
+    if (!::VirtualProtectEx(aTargetProcess, aAddress, aLength, aProtFlags,
+                            &mPrevProt)) {
+      mError = WindowsError::FromLastError();
+    }
   }
 
   ~AutoVirtualProtect()
   {
-    if (!mPrevProt) {
+    if (!mError) {
       return;
     }
 
     ::VirtualProtectEx(mTargetProcess, mAddress, mLength, mPrevProt,
                        &mPrevProt);
   }
 
   explicit operator bool() const
   {
-    return !!mPrevProt;
+    return !!mError;
+  }
+
+  WindowsError GetError() const
+  {
+    return mError;
   }
 
   AutoVirtualProtect(const AutoVirtualProtect&) = delete;
   AutoVirtualProtect(AutoVirtualProtect&&) = delete;
   AutoVirtualProtect& operator=(const AutoVirtualProtect&) = delete;
   AutoVirtualProtect& operator=(AutoVirtualProtect&&) = delete;
 
 private:
-  void*   mAddress;
-  size_t  mLength;
-  HANDLE  mTargetProcess;
-  DWORD   mPrevProt;
+  void*         mAddress;
+  size_t        mLength;
+  HANDLE        mTargetProcess;
+  DWORD         mPrevProt;
+  WindowsError  mError;
 };
 
-bool
+LauncherVoidResult
 InitializeDllBlocklistOOP(HANDLE aChildProcess)
 {
   mozilla::CrossProcessDllInterceptor intcpt(aChildProcess);
   intcpt.Init(L"ntdll.dll");
   bool ok = stub_NtMapViewOfSection.SetDetour(aChildProcess, intcpt,
                                               "NtMapViewOfSection",
                                               &patched_NtMapViewOfSection);
   if (!ok) {
-    return false;
+    return LAUNCHER_ERROR_GENERIC();
   }
 
   // Because aChildProcess has just been created in a suspended state, its
   // dynamic linker has not yet been initialized, thus its executable has
   // not yet been linked with ntdll.dll. If the blocklist hook intercepts a
   // library load prior to the link, the hook will be unable to invoke any
   // ntdll.dll functions.
   //
   // We know that the executable for our *current* process's binary is already
   // linked into ntdll, so we obtain the IAT from our own executable and graft
   // it onto the child process's IAT, thus enabling the child process's hook to
   // safely make its ntdll calls.
   mozilla::nt::PEHeaders ourExeImage(::GetModuleHandleW(nullptr));
   if (!ourExeImage) {
-    return false;
+    return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
   }
 
   PIMAGE_IMPORT_DESCRIPTOR impDesc = ourExeImage.GetIATForModule("ntdll.dll");
   if (!impDesc) {
-    return false;
+    return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_DATA);
   }
 
   // This is the pointer we need to write
   auto firstIatThunk = ourExeImage.template
     RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk);
   if (!firstIatThunk) {
-    return false;
+    return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_DATA);
   }
 
   // Find the length by iterating through the table until we find a null entry
   PIMAGE_THUNK_DATA curIatThunk = firstIatThunk;
   while (mozilla::nt::PEHeaders::IsValid(curIatThunk)) {
     ++curIatThunk;
   }
 
   ptrdiff_t iatLength = (curIatThunk - firstIatThunk) * sizeof(IMAGE_THUNK_DATA);
 
   SIZE_T bytesWritten;
 
   { // Scope for prot
     AutoVirtualProtect prot(firstIatThunk, iatLength, PAGE_READWRITE,
                             aChildProcess);
     if (!prot) {
-      return false;
+      return LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(prot.GetError());
     }
 
     ok = !!::WriteProcessMemory(aChildProcess, firstIatThunk, firstIatThunk,
                                 iatLength, &bytesWritten);
     if (!ok) {
-      return false;
+      return LAUNCHER_ERROR_FROM_LAST();
     }
   }
 
   // Tell the mozglue blocklist that we have bootstrapped
   uint32_t newFlags = eDllBlocklistInitFlagWasBootstrapped;
   ok = !!::WriteProcessMemory(aChildProcess, &gBlocklistInitFlags, &newFlags,
                               sizeof(newFlags), &bytesWritten);
-  return ok;
+  if (!ok) {
+    return LAUNCHER_ERROR_FROM_LAST();
+  }
+
+  return Ok();
 }
 
 } // namespace mozilla
--- a/browser/app/winlauncher/DllBlocklistWin.h
+++ b/browser/app/winlauncher/DllBlocklistWin.h
@@ -4,15 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_DllBlocklistWin_h
 #define mozilla_DllBlocklistWin_h
 
 #include <windows.h>
 
+#include "LauncherResult.h"
+
 namespace mozilla {
 
-bool InitializeDllBlocklistOOP(HANDLE aChildProcess);
+LauncherVoidResult InitializeDllBlocklistOOP(HANDLE aChildProcess);
 
 } // namespace mozilla
 
 #endif // mozilla_DllBlocklistWin_h
new file mode 100644
--- /dev/null
+++ b/browser/app/winlauncher/ErrorHandler.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "ErrorHandler.h"
+
+namespace mozilla {
+
+void
+HandleLauncherError(const LauncherError& aError)
+{
+  // This is a placeholder error handler. We'll add telemetry and a fallback
+  // error log in future revisions.
+  WindowsError::UniqueString msg = aError.mError.AsString();
+  if (!msg) {
+    return;
+  }
+
+  ::MessageBoxW(nullptr, msg.get(), L"Firefox", MB_OK | MB_ICONERROR);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/browser/app/winlauncher/ErrorHandler.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ErrorHandler_h
+#define mozilla_ErrorHandler_h
+
+#include "mozilla/Assertions.h"
+
+#include "LauncherResult.h"
+
+namespace mozilla {
+
+/**
+ * All launcher process error handling should live in the implementation of
+ * this function.
+ */
+void HandleLauncherError(const LauncherError& aError);
+
+// This function is simply a convenience overload that automatically unwraps
+// the LauncherError from the provided LauncherResult and then forwards it to
+// the main implementation.
+template <typename T>
+inline void
+HandleLauncherError(const LauncherResult<T>& aResult)
+{
+  MOZ_ASSERT(aResult.isErr());
+  if (aResult.isOk()) {
+    return;
+  }
+
+  HandleLauncherError(aResult.unwrapErr());
+}
+
+// This function is simply a convenience overload that unwraps the provided
+// GenericErrorResult<LauncherError> and forwards it to the main implementation.
+inline void
+HandleLauncherError(const GenericErrorResult<LauncherError>& aResult)
+{
+  LauncherVoidResult r(aResult);
+  HandleLauncherError(r);
+}
+
+} // namespace mozilla
+
+#endif //  mozilla_ErrorHandler_h
--- a/browser/app/winlauncher/LaunchUnelevated.cpp
+++ b/browser/app/winlauncher/LaunchUnelevated.cpp
@@ -7,254 +7,273 @@
 #include "LaunchUnelevated.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/CmdLineAndEnvUtils.h"
 #include "mozilla/mscom/COMApartmentRegion.h"
 #include "mozilla/RefPtr.h"
 #include "nsWindowsHelpers.h"
 
+#include "LauncherResult.h"
+
 // For _bstr_t and _variant_t
 #include <comdef.h>
 #include <comutil.h>
 
 #include <windows.h>
 #include <exdisp.h>
 #include <objbase.h>
 #include <servprov.h>
 #include <shlobj.h>
 #include <shobjidl.h>
 
-static mozilla::Maybe<TOKEN_ELEVATION_TYPE>
+static mozilla::LauncherResult<TOKEN_ELEVATION_TYPE>
 GetElevationType(const nsAutoHandle& aToken)
 {
   DWORD retLen;
   TOKEN_ELEVATION_TYPE elevationType;
   if (!::GetTokenInformation(aToken.get(), TokenElevationType, &elevationType,
                              sizeof(elevationType), &retLen)) {
-    return mozilla::Nothing();
+    return LAUNCHER_ERROR_FROM_LAST();
   }
 
-  return mozilla::Some(elevationType);
+  return elevationType;
 }
 
-static mozilla::Maybe<bool>
+static mozilla::LauncherResult<bool>
 IsHighIntegrity(const nsAutoHandle& aToken)
 {
   DWORD reqdLen;
   if (!::GetTokenInformation(aToken.get(), TokenIntegrityLevel, nullptr, 0,
-                             &reqdLen) &&
-      ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
-    return mozilla::Nothing();
+                             &reqdLen)) {
+    DWORD err = ::GetLastError();
+    if (err != ERROR_INSUFFICIENT_BUFFER) {
+      return LAUNCHER_ERROR_FROM_WIN32(err);
+    }
   }
 
   auto buf = mozilla::MakeUnique<char[]>(reqdLen);
 
   if (!::GetTokenInformation(aToken.get(), TokenIntegrityLevel, buf.get(),
                              reqdLen, &reqdLen)) {
-    return mozilla::Nothing();
+    return LAUNCHER_ERROR_FROM_LAST();
   }
 
   auto tokenLabel = reinterpret_cast<PTOKEN_MANDATORY_LABEL>(buf.get());
 
   DWORD subAuthCount = *::GetSidSubAuthorityCount(tokenLabel->Label.Sid);
   DWORD integrityLevel = *::GetSidSubAuthority(tokenLabel->Label.Sid,
                                                subAuthCount - 1);
-  return mozilla::Some(integrityLevel > SECURITY_MANDATORY_MEDIUM_RID);
+  return integrityLevel > SECURITY_MANDATORY_MEDIUM_RID;
 }
 
-static nsReturnRef<HANDLE>
+static mozilla::LauncherResult<HANDLE>
 GetMediumIntegrityToken(const nsAutoHandle& aProcessToken)
 {
-  nsAutoHandle empty;
-
   HANDLE rawResult;
   if (!::DuplicateTokenEx(aProcessToken.get(), 0, nullptr,
                           SecurityImpersonation, TokenPrimary, &rawResult)) {
-    return empty.out();
+
+    return LAUNCHER_ERROR_FROM_LAST();
   }
 
   nsAutoHandle result(rawResult);
 
   BYTE mediumIlSid[SECURITY_MAX_SID_SIZE];
   DWORD mediumIlSidSize = sizeof(mediumIlSid);
   if (!::CreateWellKnownSid(WinMediumLabelSid, nullptr, mediumIlSid,
                             &mediumIlSidSize)) {
-    return empty.out();
+    return LAUNCHER_ERROR_FROM_LAST();
   }
 
   TOKEN_MANDATORY_LABEL integrityLevel = {};
   integrityLevel.Label.Attributes = SE_GROUP_INTEGRITY;
   integrityLevel.Label.Sid = reinterpret_cast<PSID>(mediumIlSid);
 
   if (!::SetTokenInformation(rawResult, TokenIntegrityLevel, &integrityLevel,
                              sizeof(integrityLevel))) {
-    return empty.out();
+    return LAUNCHER_ERROR_FROM_LAST();
   }
 
-  return result.out();
+  return result.disown();
 }
 
 namespace mozilla {
 
 // If we're running at an elevated integrity level, re-run ourselves at the
 // user's normal integrity level. We do this by locating the active explorer
 // shell, and then asking it to do a ShellExecute on our behalf. We do it this
 // way to ensure that the child process runs as the original user in the active
 // session; an elevated process could be running with different credentials than
 // those of the session.
 // See https://blogs.msdn.microsoft.com/oldnewthing/20131118-00/?p=2643
 
-bool
+LauncherVoidResult
 LaunchUnelevated(int aArgc, wchar_t* aArgv[])
 {
   // We require a single-threaded apartment to talk to Explorer.
   mscom::STARegion sta;
   if (!sta.IsValid()) {
-    return false;
+    return LAUNCHER_ERROR_FROM_HRESULT(sta.GetHResult());
   }
 
   // NB: Explorer is a local server, not an inproc server
   RefPtr<IShellWindows> shellWindows;
   HRESULT hr = ::CoCreateInstance(CLSID_ShellWindows, nullptr,
                                   CLSCTX_LOCAL_SERVER, IID_IShellWindows,
                                   getter_AddRefs(shellWindows));
   if (FAILED(hr)) {
-    return false;
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
   }
 
   // 1. Find the shell view for the desktop.
   _variant_t loc(CSIDL_DESKTOP);
   _variant_t empty;
   long hwnd;
   RefPtr<IDispatch> dispDesktop;
   hr = shellWindows->FindWindowSW(&loc, &empty, SWC_DESKTOP, &hwnd,
                                   SWFO_NEEDDISPATCH, getter_AddRefs(dispDesktop));
   if (FAILED(hr)) {
-    return false;
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
   }
 
   RefPtr<IServiceProvider> servProv;
   hr = dispDesktop->QueryInterface(IID_IServiceProvider, getter_AddRefs(servProv));
   if (FAILED(hr)) {
-    return false;
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
   }
 
   RefPtr<IShellBrowser> browser;
   hr = servProv->QueryService(SID_STopLevelBrowser, IID_IShellBrowser,
                               getter_AddRefs(browser));
   if (FAILED(hr)) {
-    return false;
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
   }
 
   RefPtr<IShellView> activeShellView;
   hr = browser->QueryActiveShellView(getter_AddRefs(activeShellView));
   if (FAILED(hr)) {
-    return false;
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
   }
 
   // 2. Get the automation object for the desktop.
   RefPtr<IDispatch> dispView;
   hr = activeShellView->GetItemObject(SVGIO_BACKGROUND, IID_IDispatch,
                                       getter_AddRefs(dispView));
   if (FAILED(hr)) {
-    return false;
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
   }
 
   RefPtr<IShellFolderViewDual> folderView;
   hr = dispView->QueryInterface(IID_IShellFolderViewDual,
                                 getter_AddRefs(folderView));
   if (FAILED(hr)) {
-    return false;
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
   }
 
   // 3. Get the interface to IShellDispatch2
   RefPtr<IDispatch> dispShell;
   hr = folderView->get_Application(getter_AddRefs(dispShell));
   if (FAILED(hr)) {
-    return false;
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
   }
 
   RefPtr<IShellDispatch2> shellDisp;
   hr = dispShell->QueryInterface(IID_IShellDispatch2, getter_AddRefs(shellDisp));
   if (FAILED(hr)) {
-    return false;
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
   }
 
   // 4. Now call IShellDispatch2::ShellExecute to ask Explorer to re-launch us.
 
   // Omit argv[0] because ShellExecute doesn't need it in params
   UniquePtr<wchar_t[]> cmdLine(MakeCommandLine(aArgc - 1, aArgv + 1));
   if (!cmdLine) {
-    return false;
+    return LAUNCHER_ERROR_GENERIC();
   }
 
   _bstr_t exe(aArgv[0]);
   _variant_t args(cmdLine.get());
   _variant_t operation(L"open");
   _variant_t directory;
   _variant_t showCmd(SW_SHOWNORMAL);
   hr = shellDisp->ShellExecute(exe, args, operation, directory, showCmd);
-  return SUCCEEDED(hr);
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  return Ok();
 }
 
-Maybe<ElevationState>
+LauncherResult<ElevationState>
 GetElevationState(mozilla::LauncherFlags aFlags, nsAutoHandle& aOutMediumIlToken)
 {
   aOutMediumIlToken.reset();
 
   const DWORD tokenFlags = TOKEN_QUERY | TOKEN_DUPLICATE |
                            TOKEN_ADJUST_DEFAULT | TOKEN_ASSIGN_PRIMARY;
   HANDLE rawToken;
   if (!::OpenProcessToken(::GetCurrentProcess(), tokenFlags, &rawToken)) {
-    return Nothing();
+    return LAUNCHER_ERROR_FROM_LAST();
   }
 
   nsAutoHandle token(rawToken);
 
-  Maybe<TOKEN_ELEVATION_TYPE> elevationType = GetElevationType(token);
-  if (!elevationType) {
-    return Nothing();
+  LauncherResult<TOKEN_ELEVATION_TYPE> elevationType = GetElevationType(token);
+  if (elevationType.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(elevationType);
   }
 
-  switch (elevationType.value()) {
+  switch (elevationType.unwrap()) {
     case TokenElevationTypeLimited:
-      return Some(ElevationState::eNormalUser);
+      return ElevationState::eNormalUser;
     case TokenElevationTypeFull:
       // If we want to start a non-elevated browser process and wait on it,
       // we're going to need a medium IL token.
       if ((aFlags & (mozilla::LauncherFlags::eWaitForBrowser |
                      mozilla::LauncherFlags::eNoDeelevate)) ==
           mozilla::LauncherFlags::eWaitForBrowser) {
-        aOutMediumIlToken = GetMediumIntegrityToken(token);
+        LauncherResult<HANDLE> tokenResult =
+          GetMediumIntegrityToken(token);
+        if (tokenResult.isOk()) {
+          aOutMediumIlToken.own(tokenResult.unwrap());
+        } else {
+          return LAUNCHER_ERROR_FROM_RESULT(tokenResult);
+        }
       }
 
-      return Some(ElevationState::eElevated);
+      return ElevationState::eElevated;
     case TokenElevationTypeDefault:
       break;
     default:
       MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?");
-      return Nothing();
+      return LAUNCHER_ERROR_GENERIC();
   }
 
   // In this case, UAC is disabled. We do not yet know whether or not we are
   // running at high integrity. If we are at high integrity, we can't relaunch
   // ourselves in a non-elevated state via Explorer, as we would just end up in
   // an infinite loop of launcher processes re-launching themselves.
 
-  Maybe<bool> isHighIntegrity = IsHighIntegrity(token);
-  if (!isHighIntegrity) {
-    return Nothing();
+  LauncherResult<bool> isHighIntegrity = IsHighIntegrity(token);
+  if (isHighIntegrity.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(isHighIntegrity);
   }
 
-  if (!isHighIntegrity.value()) {
-    return Some(ElevationState::eNormalUser);
+  if (!isHighIntegrity.unwrap()) {
+    return ElevationState::eNormalUser;
   }
 
   if (!(aFlags & mozilla::LauncherFlags::eNoDeelevate)) {
-    aOutMediumIlToken = GetMediumIntegrityToken(token);
+    LauncherResult<HANDLE> tokenResult =
+      GetMediumIntegrityToken(token);
+    if (tokenResult.isOk()) {
+      aOutMediumIlToken.own(tokenResult.unwrap());
+    } else {
+      return LAUNCHER_ERROR_FROM_RESULT(tokenResult);
+    }
   }
 
-  return Some(ElevationState::eHighIntegrityNoUAC);
+  return ElevationState::eHighIntegrityNoUAC;
 }
 
 } // namespace mozilla
 
--- a/browser/app/winlauncher/LaunchUnelevated.h
+++ b/browser/app/winlauncher/LaunchUnelevated.h
@@ -3,29 +3,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_LaunchUnelevated_h
 #define mozilla_LaunchUnelevated_h
 
 #include "LauncherProcessWin.h"
+#include "LauncherResult.h"
 #include "mozilla/Maybe.h"
 #include "nsWindowsHelpers.h"
 
 namespace mozilla {
 
 enum class ElevationState
 {
   eNormalUser = 0,
   eElevated = (1 << 0),
   eHighIntegrityNoUAC = (1 << 1),
 };
 
-mozilla::Maybe<ElevationState>
+LauncherResult<ElevationState>
 GetElevationState(LauncherFlags aFlags, nsAutoHandle& aOutMediumIlToken);
 
-bool LaunchUnelevated(int aArgc, wchar_t* aArgv[]);
+LauncherVoidResult LaunchUnelevated(int aArgc, wchar_t* aArgv[]);
 
 } // namespace mozilla
 
 #endif // mozilla_LaunchUnelevated_h
 
--- a/browser/app/winlauncher/LauncherProcessWin.cpp
+++ b/browser/app/winlauncher/LauncherProcessWin.cpp
@@ -20,34 +20,36 @@
 #include "mozilla/WindowsVersion.h"
 #include "mozilla/WinHeaderOnlyUtils.h"
 #include "nsWindowsHelpers.h"
 
 #include <windows.h>
 #include <processthreadsapi.h>
 
 #include "DllBlocklistWin.h"
+#include "ErrorHandler.h"
+#include "LauncherResult.h"
 #include "LaunchUnelevated.h"
 #include "ProcThreadAttributes.h"
 
 /**
  * At this point the child process has been created in a suspended state. Any
  * additional startup work (eg, blocklist setup) should go here.
  *
  * @return true if browser startup should proceed, otherwise false.
  */
-static bool
+static mozilla::LauncherVoidResult
 PostCreationSetup(HANDLE aChildProcess, HANDLE aChildMainThread,
                   const bool aIsSafeMode)
 {
   // The launcher process's DLL blocking code is incompatible with ASAN because
   // it is able to execute before ASAN itself has even initialized.
   // Also, the AArch64 build doesn't yet have a working interceptor.
 #if defined(MOZ_ASAN) || defined(_M_ARM64)
-  return true;
+  return mozilla::Ok();
 #else
   return mozilla::InitializeDllBlocklistOOP(aChildProcess);
 #endif // defined(MOZ_ASAN) || defined(_M_ARM64)
 }
 
 #if !defined(PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON)
 # define PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON (0x00000001ULL << 60)
 #endif // !defined(PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON)
@@ -65,37 +67,16 @@ SetProcessMitigationPolicy(PROCESS_MITIG
 static void
 SetMitigationPolicies(mozilla::ProcThreadAttributes& aAttrs, const bool aIsSafeMode)
 {
   if (mozilla::IsWin10AnniversaryUpdateOrLater()) {
     aAttrs.AddMitigationPolicy(PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON);
   }
 }
 
-static void
-ShowError(DWORD aError = ::GetLastError())
-{
-  if (aError == ERROR_SUCCESS) {
-    return;
-  }
-
-  LPWSTR rawMsgBuf = nullptr;
-  DWORD result = ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
-                                  FORMAT_MESSAGE_FROM_SYSTEM |
-                                  FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
-                                  aError, 0, reinterpret_cast<LPWSTR>(&rawMsgBuf),
-                                  0, nullptr);
-  if (!result) {
-    return;
-  }
-
-  ::MessageBoxW(nullptr, rawMsgBuf, L"Firefox", MB_OK | MB_ICONERROR);
-  ::LocalFree(rawMsgBuf);
-}
-
 static mozilla::LauncherFlags
 ProcessCmdLine(int& aArgc, wchar_t* aArgv[])
 {
   mozilla::LauncherFlags result = mozilla::LauncherFlags::eNone;
 
   if (mozilla::CheckArg(aArgc, aArgv, L"wait-for-browser",
                         static_cast<const wchar_t**>(nullptr),
                         mozilla::CheckArgFlag::RemoveArg) == mozilla::ARG_FOUND ||
@@ -161,79 +142,73 @@ MaybeBreakForBrowserDebugging()
   DWORD pauseLenMs = wcstoul(pauseLenS, nullptr, 10) * 1000;
   printf_stderr("\n\nBROWSERBROWSERBROWSERBROWSER\n  debug me @ %lu\n\n",
                 ::GetCurrentProcessId());
   ::Sleep(pauseLenMs);
 }
 
 #if defined(MOZ_LAUNCHER_PROCESS)
 
-static bool
+static mozilla::LauncherResult<bool>
 IsSameBinaryAsParentProcess()
 {
-  mozilla::Maybe<DWORD> parentPid = mozilla::nt::GetParentProcessId();
-  if (!parentPid) {
-    // If NtQueryInformationProcess failed (in GetParentProcessId()),
-    // we should not behave as the launcher process because it will also
-    // likely to fail in child processes.
-    MOZ_CRASH("NtQueryInformationProcess failed");
+  mozilla::LauncherResult<DWORD> parentPid = mozilla::nt::GetParentProcessId();
+  if (parentPid.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(parentPid);
   }
 
   nsAutoHandle parentProcess(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
-                                           FALSE, parentPid.value()));
+                                           FALSE, parentPid.unwrap()));
   if (!parentProcess.get()) {
-    // If OpenProcess failed, the parent process may not be present,
-    // may be already terminated, etc. So we will have to behave as the
-    // launcher proces in this case.
-    return false;
+    return LAUNCHER_ERROR_FROM_LAST();
   }
 
   WCHAR parentExe[MAX_PATH + 1] = {};
   DWORD parentExeLen = mozilla::ArrayLength(parentExe);
   if (!::QueryFullProcessImageNameW(parentProcess.get(), PROCESS_NAME_NATIVE,
                                     parentExe, &parentExeLen)) {
-    // If QueryFullProcessImageNameW failed, we should not behave as the
-    // launcher process for the same reason as NtQueryInformationProcess.
-    MOZ_CRASH("QueryFullProcessImageNameW failed");
+    return LAUNCHER_ERROR_FROM_LAST();
   }
 
   WCHAR ourExe[MAX_PATH + 1] = {};
   DWORD ourExeOk = ::GetModuleFileNameW(nullptr, ourExe,
                                         mozilla::ArrayLength(ourExe));
   if (!ourExeOk || ourExeOk == mozilla::ArrayLength(ourExe)) {
-    // If GetModuleFileNameW failed, we should not behave as the launcher
-    // process for the same reason as NtQueryInformationProcess.
-    MOZ_CRASH("GetModuleFileNameW failed");
+    return LAUNCHER_ERROR_FROM_LAST();
   }
 
-  mozilla::Maybe<bool> isSame =
+  mozilla::WindowsErrorResult<bool> isSame =
     mozilla::DoPathsPointToIdenticalFile(parentExe, ourExe,
-                                         mozilla::eNtPath);
-  if (!isSame) {
-    // If DoPathsPointToIdenticalFile failed, we should not behave as the
-    // launcher process for the same reason as NtQueryInformationProcess.
-    MOZ_CRASH("DoPathsPointToIdenticalFile failed");
+                                         mozilla::PathType::eNtPath);
+  if (isSame.isErr()) {
+    return LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(isSame.unwrapErr());
   }
-  return isSame.value();
+
+  return isSame.unwrap();
 }
 
 #endif // defined(MOZ_LAUNCHER_PROCESS)
 
 namespace mozilla {
 
 bool
 RunAsLauncherProcess(int& argc, wchar_t** argv)
 {
   // NB: We run all tests in this function instead of returning early in order
   // to ensure that all side effects take place, such as clearing environment
   // variables.
   bool result = false;
 
 #if defined(MOZ_LAUNCHER_PROCESS)
-  result = !IsSameBinaryAsParentProcess();
+  LauncherResult<bool> isSame = IsSameBinaryAsParentProcess();
+  if (isSame.isOk()) {
+    result = !isSame.unwrap();
+  } else {
+    HandleLauncherError(isSame.unwrapErr());
+  }
 #endif // defined(MOZ_LAUNCHER_PROCESS)
 
   if (mozilla::EnvHasValue("MOZ_LAUNCHER_PROCESS")) {
     mozilla::SaveToEnv("MOZ_LAUNCHER_PROCESS=");
     result = true;
   }
 
   result |= CheckArg(argc, argv, L"launcher",
@@ -263,47 +238,55 @@ LauncherMain(int argc, wchar_t* argv[])
       DebugOnly<BOOL> setOk = pSetProcessMitigationPolicy(ProcessImageLoadPolicy,
                                                           &imgLoadPol,
                                                           sizeof(imgLoadPol));
       MOZ_ASSERT(setOk);
     }
   }
 
   if (!SetArgv0ToFullBinaryPath(argv)) {
-    ShowError();
+    HandleLauncherError(LAUNCHER_ERROR_GENERIC());
     return 1;
   }
 
   LauncherFlags flags = ProcessCmdLine(argc, argv);
 
   nsAutoHandle mediumIlToken;
-  Maybe<ElevationState> elevationState = GetElevationState(flags, mediumIlToken);
-  if (!elevationState) {
+  LauncherResult<ElevationState> elevationState = GetElevationState(flags, mediumIlToken);
+  if (elevationState.isErr()) {
+    HandleLauncherError(elevationState);
     return 1;
   }
 
   // If we're elevated, we should relaunch ourselves as a normal user.
   // Note that we only call LaunchUnelevated when we don't need to wait for the
   // browser process.
-  if (elevationState.value() == ElevationState::eElevated &&
+  if (elevationState.unwrap() == ElevationState::eElevated &&
       !(flags & (LauncherFlags::eWaitForBrowser | LauncherFlags::eNoDeelevate)) &&
       !mediumIlToken.get()) {
-    return !LaunchUnelevated(argc, argv);
+    LauncherVoidResult launchedUnelevated = LaunchUnelevated(argc, argv);
+    bool failed = launchedUnelevated.isErr();
+    if (failed) {
+      HandleLauncherError(launchedUnelevated);
+    }
+
+    return failed;
   }
 
   // Now proceed with setting up the parameters for process creation
   UniquePtr<wchar_t[]> cmdLine(MakeCommandLine(argc, argv));
   if (!cmdLine) {
+    HandleLauncherError(LAUNCHER_ERROR_GENERIC());
     return 1;
   }
 
   const Maybe<bool> isSafeMode = IsSafeModeRequested(argc, argv,
                                                      SafeModeFlag::NoKeyPressCheck);
   if (!isSafeMode) {
-    ShowError(ERROR_INVALID_PARAMETER);
+    HandleLauncherError(LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_PARAMETER));
     return 1;
   }
 
   ProcThreadAttributes attrs;
   SetMitigationPolicies(attrs, isSafeMode.value());
 
   HANDLE stdHandles[] = {
     ::GetStdHandle(STD_INPUT_HANDLE),
@@ -311,25 +294,25 @@ LauncherMain(int argc, wchar_t* argv[])
     ::GetStdHandle(STD_ERROR_HANDLE)
   };
 
   attrs.AddInheritableHandles(stdHandles);
 
   DWORD creationFlags = CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT;
 
   STARTUPINFOEXW siex;
-  Maybe<bool> attrsOk = attrs.AssignTo(siex);
-  if (!attrsOk) {
-    ShowError();
+  LauncherResult<bool> attrsOk = attrs.AssignTo(siex);
+  if (attrsOk.isErr()) {
+    HandleLauncherError(attrsOk);
     return 1;
   }
 
   BOOL inheritHandles = FALSE;
 
-  if (attrsOk.value()) {
+  if (attrsOk.unwrap()) {
     creationFlags |= EXTENDED_STARTUPINFO_PRESENT;
 
     if (attrs.HasInheritableHandles()) {
       siex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
       siex.StartupInfo.hStdInput = stdHandles[0];
       siex.StartupInfo.hStdOutput = stdHandles[1];
       siex.StartupInfo.hStdError = stdHandles[2];
 
@@ -349,26 +332,33 @@ LauncherMain(int argc, wchar_t* argv[])
                                       &siex.StartupInfo, &pi);
   } else {
     createOk = ::CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr,
                                 inheritHandles, creationFlags, nullptr, nullptr,
                                 &siex.StartupInfo, &pi);
   }
 
   if (!createOk) {
-    ShowError();
+    HandleLauncherError(LAUNCHER_ERROR_FROM_LAST());
     return 1;
   }
 
   nsAutoHandle process(pi.hProcess);
   nsAutoHandle mainThread(pi.hThread);
 
-  if (!PostCreationSetup(process.get(), mainThread.get(), isSafeMode.value()) ||
-      ::ResumeThread(mainThread.get()) == static_cast<DWORD>(-1)) {
-    ShowError();
+  LauncherVoidResult setupResult =
+    PostCreationSetup(process.get(), mainThread.get(), isSafeMode.value());
+  if (setupResult.isErr()) {
+    HandleLauncherError(setupResult);
+    ::TerminateProcess(process.get(), 1);
+    return 1;
+  }
+
+  if (::ResumeThread(mainThread.get()) == static_cast<DWORD>(-1)) {
+    HandleLauncherError(LAUNCHER_ERROR_FROM_LAST());
     ::TerminateProcess(process.get(), 1);
     return 1;
   }
 
   if (flags & LauncherFlags::eWaitForBrowser) {
     DWORD exitCode;
     if (::WaitForSingleObject(process.get(), INFINITE) == WAIT_OBJECT_0 &&
         ::GetExitCodeProcess(process.get(), &exitCode)) {
new file mode 100644
--- /dev/null
+++ b/browser/app/winlauncher/LauncherResult.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LauncherResult_h
+#define mozilla_LauncherResult_h
+
+#include "mozilla/Result.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+namespace mozilla {
+
+struct LauncherError
+{
+  LauncherError(const char* aFile, int aLine, WindowsError aWin32Error)
+    : mFile(aFile)
+    , mLine(aLine)
+    , mError(aWin32Error)
+  {}
+
+  const char* mFile;
+  int mLine;
+  WindowsError mError;
+};
+
+template <typename T>
+using LauncherResult = Result<T, LauncherError>;
+
+using LauncherVoidResult = Result<Ok, LauncherError>;
+
+} // namespace mozilla
+
+#define LAUNCHER_ERROR_GENERIC() \
+  ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, \
+                 ::mozilla::WindowsError::CreateGeneric()))
+
+#define LAUNCHER_ERROR_FROM_WIN32(err) \
+  ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, \
+                 ::mozilla::WindowsError::FromWin32Error(err)))
+
+#define LAUNCHER_ERROR_FROM_LAST() \
+  ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, \
+                 ::mozilla::WindowsError::FromWin32Error(::GetLastError())))
+
+#define LAUNCHER_ERROR_FROM_NTSTATUS(ntstatus) \
+  ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, \
+                 ::mozilla::WindowsError::FromNtStatus(ntstatus)))
+
+#define LAUNCHER_ERROR_FROM_HRESULT(hresult) \
+  ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, \
+                 ::mozilla::WindowsError::FromHResult(hresult)))
+
+// This macro enables moving of a mozilla::LauncherError from a
+// mozilla::LauncherResult<Foo> into a mozilla::LauncherResult<Bar>
+#define LAUNCHER_ERROR_FROM_RESULT(result) \
+  ::mozilla::Err(result.unwrapErr())
+
+// This macro wraps the supplied WindowsError with a LauncherError
+#define LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(err) \
+  ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, err))
+
+#endif // mozilla_LauncherResult_h
--- a/browser/app/winlauncher/NativeNt.h
+++ b/browser/app/winlauncher/NativeNt.h
@@ -13,17 +13,18 @@
 
 #include <stdint.h>
 #include <windows.h>
 #include <winnt.h>
 #include <winternl.h>
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
-#include "mozilla/Maybe.h"
+
+#include "LauncherResult.h"
 
 extern "C" {
 
 #if !defined(STATUS_ACCESS_DENIED)
 #define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
 #endif // !defined(STATUS_ACCESS_DENIED)
 
 #if !defined(STATUS_DLL_NOT_FOUND)
@@ -563,38 +564,39 @@ private:
 inline HANDLE
 RtlGetProcessHeap()
 {
   PTEB teb = ::NtCurrentTeb();
   PPEB peb = teb->ProcessEnvironmentBlock;
   return peb->Reserved4[1];
 }
 
-inline Maybe<DWORD>
+inline LauncherResult<DWORD>
 GetParentProcessId()
 {
   struct PROCESS_BASIC_INFORMATION
   {
     NTSTATUS ExitStatus;
     PPEB PebBaseAddress;
     ULONG_PTR AffinityMask;
     LONG BasePriority;
     ULONG_PTR UniqueProcessId;
     ULONG_PTR InheritedFromUniqueProcessId;
   };
 
+  const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1);
   ULONG returnLength;
   PROCESS_BASIC_INFORMATION pbi = {};
-  NTSTATUS status = ::NtQueryInformationProcess(::GetCurrentProcess(),
+  NTSTATUS status = ::NtQueryInformationProcess(kCurrentProcess,
                                                 ProcessBasicInformation,
                                                 &pbi, sizeof(pbi),
                                                 &returnLength);
   if (!NT_SUCCESS(status)) {
-    return Nothing();
+    return LAUNCHER_ERROR_FROM_NTSTATUS(status);
   }
 
-  return Some(static_cast<DWORD>(pbi.InheritedFromUniqueProcessId & 0xFFFFFFFF));
+  return static_cast<DWORD>(pbi.InheritedFromUniqueProcessId & 0xFFFFFFFF);
 }
 
 } // namespace nt
 } // namespace mozilla
 
 #endif // mozilla_NativeNt_h
--- a/browser/app/winlauncher/ProcThreadAttributes.h
+++ b/browser/app/winlauncher/ProcThreadAttributes.h
@@ -81,24 +81,22 @@ public:
   }
 
   bool HasInheritableHandles() const
   {
     return !mInheritableHandles.empty();
   }
 
   /**
-   * @return Some(false) if the STARTUPINFOEXW::lpAttributeList was set to null
-   *                     as expected based on the state of |this|;
-   *         Some(true)  if the STARTUPINFOEXW::lpAttributeList was set to
-   *                     non-null;
-   *         Nothing()   if something went wrong in the assignment and we should
-   *                     not proceed.
+   * @return false if the STARTUPINFOEXW::lpAttributeList was set to null
+   *               as expected based on the state of |this|;
+   *         true  if the STARTUPINFOEXW::lpAttributeList was set to
+   *               non-null;
    */
-  Maybe<bool> AssignTo(STARTUPINFOEXW& aSiex)
+  LauncherResult<bool> AssignTo(STARTUPINFOEXW& aSiex)
   {
     ZeroMemory(&aSiex, sizeof(STARTUPINFOEXW));
 
     // We'll set the size to sizeof(STARTUPINFOW) until we determine whether the
     // extended fields will be used.
     aSiex.StartupInfo.cb = sizeof(STARTUPINFOW);
 
     DWORD numAttributes = 0;
@@ -106,67 +104,69 @@ public:
       ++numAttributes;
     }
 
     if (HasInheritableHandles()) {
       ++numAttributes;
     }
 
     if (!numAttributes) {
-      return Some(false);
+      return false;
     }
 
     SIZE_T listSize = 0;
     if (!::InitializeProcThreadAttributeList(nullptr, numAttributes, 0,
-                                             &listSize) &&
-        ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
-      return Nothing();
+                                             &listSize)) {
+      DWORD err = ::GetLastError();
+      if (err != ERROR_INSUFFICIENT_BUFFER) {
+        return LAUNCHER_ERROR_FROM_WIN32(err);
+      }
     }
 
     auto buf = MakeUnique<char[]>(listSize);
 
     LPPROC_THREAD_ATTRIBUTE_LIST tmpList =
       reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(buf.get());
 
     if (!::InitializeProcThreadAttributeList(tmpList, numAttributes, 0,
                                              &listSize)) {
-      return Nothing();
+      return LAUNCHER_ERROR_FROM_LAST();
     }
 
     // Transfer buf to a ProcThreadAttributeListPtr - now that the list is
     // initialized, we are no longer dealing with a plain old char array. We
     // must now deinitialize the attribute list before deallocating the
     // underlying buffer.
     ProcThreadAttributeListPtr
       attrList(reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(buf.release()));
 
     if (mMitigationPolicies) {
       if (!::UpdateProcThreadAttribute(attrList.get(), 0,
                                        PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,
                                        &mMitigationPolicies,
                                        sizeof(mMitigationPolicies), nullptr,
                                        nullptr)) {
-        return Nothing();
+        return LAUNCHER_ERROR_FROM_LAST();
       }
     }
 
     if (!mInheritableHandles.empty()) {
       if (!::UpdateProcThreadAttribute(attrList.get(), 0,
                                        PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
                                        mInheritableHandles.begin(),
                                        mInheritableHandles.length() * sizeof(HANDLE),
                                        nullptr, nullptr)) {
-        return Nothing();
+        return LAUNCHER_ERROR_FROM_LAST();
       }
     }
 
     mAttrList = std::move(attrList);
     aSiex.lpAttributeList = mAttrList.get();
     aSiex.StartupInfo.cb = sizeof(STARTUPINFOEXW);
-    return Some(true);
+    return true;
   }
 
 private:
   static const uint32_t kNumInline = 3; // Inline storage for the std handles
 
   DWORD64                     mMitigationPolicies;
   Vector<HANDLE, kNumInline>  mInheritableHandles;
   ProcThreadAttributeListPtr  mAttrList;
--- a/browser/app/winlauncher/moz.build
+++ b/browser/app/winlauncher/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 Library('winlauncher')
 
 FORCE_STATIC_LIB = True
 
 UNIFIED_SOURCES += [
     'DllBlocklistWin.cpp',
+    'ErrorHandler.cpp',
     'LauncherProcessWin.cpp',
     'LaunchUnelevated.cpp',
 ]
 
 OS_LIBS += [
     'ntdll',
     'oleaut32',
     'ole32',
--- a/browser/app/winlauncher/test/moz.build
+++ b/browser/app/winlauncher/test/moz.build
@@ -1,17 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DisableStlWrapping()
 
-CppUnitTests(['TestNativeNt'])
+GeckoCppUnitTests(['TestNativeNt'])
 
 LOCAL_INCLUDES += [
     '/browser/app/winlauncher',
 ]
 
 OS_LIBS += [
     'mincore',
     'ntdll',
--- a/ipc/mscom/COMApartmentRegion.h
+++ b/ipc/mscom/COMApartmentRegion.h
@@ -38,16 +38,21 @@ public:
     return mInitResult == S_OK;
   }
 
   bool IsValid() const
   {
     return SUCCEEDED(mInitResult);
   }
 
+  HRESULT GetHResult() const
+  {
+    return mInitResult;
+  }
+
 private:
   COMApartmentRegion(const COMApartmentRegion&) = delete;
   COMApartmentRegion& operator=(const COMApartmentRegion&) = delete;
   COMApartmentRegion(COMApartmentRegion&&) = delete;
   COMApartmentRegion& operator=(COMApartmentRegion&&) = delete;
 
   HRESULT mInitResult;
 };
--- a/widget/windows/WinHeaderOnlyUtils.h
+++ b/widget/windows/WinHeaderOnlyUtils.h
@@ -3,19 +3,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_WinHeaderOnlyUtils_h
 #define mozilla_WinHeaderOnlyUtils_h
 
 #include <windows.h>
+#include <winerror.h>
+#include <winnt.h>
 #include <winternl.h>
 
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/WindowsVersion.h"
 #include "nsWindowsHelpers.h"
 
 /**
  * This header is intended for self-contained, header-only, utility code for
  * Win32. It may be used outside of xul.dll, in places such as firefox.exe or
  * mozglue.dll. If your code creates dependencies on Mozilla libraries, you
  * should put it elsewhere.
@@ -27,18 +34,175 @@ typedef struct _FILE_ID_INFO
   ULONGLONG   VolumeSerialNumber;
   FILE_ID_128 FileId;
 } FILE_ID_INFO;
 
 #define FileIdInfo ((FILE_INFO_BY_HANDLE_CLASS)18)
 
 #endif // _WIN32_WINNT < _WIN32_WINNT_WIN8
 
+#if !defined(STATUS_SUCCESS)
+#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+#endif // !defined(STATUS_SUCCESS)
+
 namespace mozilla {
 
+class WindowsError final
+{
+private:
+  // HRESULT and NTSTATUS are both typedefs of LONG, so we cannot use
+  // overloading to properly differentiate between the two. Instead we'll use
+  // static functions to convert the various error types to HRESULTs before
+  // instantiating.
+  explicit WindowsError(HRESULT aHResult)
+    : mHResult(aHResult)
+  {
+  }
+
+public:
+  using UniqueString = UniquePtr<WCHAR[], LocalFreeDeleter>;
+
+  static WindowsError FromNtStatus(NTSTATUS aNtStatus)
+  {
+    if (aNtStatus == STATUS_SUCCESS) {
+      // Special case: we don't want to set FACILITY_NT_BIT
+      // (HRESULT_FROM_NT does not handle this case, unlike HRESULT_FROM_WIN32)
+      return WindowsError(S_OK);
+    }
+
+    return WindowsError(HRESULT_FROM_NT(aNtStatus));
+  }
+
+  static WindowsError FromHResult(HRESULT aHResult)
+  {
+    return WindowsError(aHResult);
+  }
+
+  static WindowsError FromWin32Error(DWORD aWin32Err)
+  {
+    return WindowsError(HRESULT_FROM_WIN32(aWin32Err));
+  }
+
+  static WindowsError FromLastError()
+  {
+    return FromWin32Error(::GetLastError());
+  }
+
+  static WindowsError CreateSuccess()
+  {
+    return WindowsError(S_OK);
+  }
+
+  static WindowsError CreateGeneric()
+  {
+    return FromWin32Error(ERROR_UNIDENTIFIED_ERROR);
+  }
+
+  explicit operator bool() const
+  {
+    return SUCCEEDED(mHResult);
+  }
+
+  bool IsAvailableAsWin32Error() const
+  {
+    return IsAvailableAsNtStatus() ||
+           HRESULT_FACILITY(mHResult) == FACILITY_WIN32;
+  }
+
+  bool IsAvailableAsNtStatus() const
+  {
+    return mHResult == S_OK || (mHResult & FACILITY_NT_BIT);
+  }
+
+  bool IsAvailableAsHResult() const
+  {
+    return true;
+  }
+
+  UniqueString AsString() const
+  {
+    LPWSTR rawMsgBuf = nullptr;
+    DWORD result = ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                                    FORMAT_MESSAGE_FROM_SYSTEM |
+                                    FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
+                                    mHResult, 0,
+                                    reinterpret_cast<LPWSTR>(&rawMsgBuf), 0,
+                                    nullptr);
+    if (!result) {
+      return nullptr;
+    }
+
+    return UniqueString(rawMsgBuf);
+  }
+
+  HRESULT AsHResult() const
+  {
+    return mHResult;
+  }
+
+  // Not all HRESULTs are convertible to Win32 Errors, so we use Maybe
+  Maybe<DWORD> AsWin32Error() const
+  {
+    if (mHResult == S_OK) {
+      return Some(static_cast<DWORD>(ERROR_SUCCESS));
+    }
+
+    if (HRESULT_FACILITY(mHResult) == FACILITY_WIN32) {
+      // This is the inverse of HRESULT_FROM_WIN32
+      return Some(static_cast<DWORD>(HRESULT_CODE(mHResult)));
+    }
+
+    // The NTSTATUS facility is a special case and thus does not utilize the
+    // HRESULT_FACILITY and HRESULT_CODE macros.
+    if (mHResult & FACILITY_NT_BIT) {
+      return Some(NtStatusToWin32Error(
+        static_cast<NTSTATUS>(mHResult & ~FACILITY_NT_BIT)));
+    }
+
+    return Nothing();
+  }
+
+  // Not all HRESULTs are convertible to NTSTATUS, so we use Maybe
+  Maybe<NTSTATUS> AsNtStatus() const
+  {
+    if (mHResult == S_OK) {
+      return Some(STATUS_SUCCESS);
+    }
+
+    // The NTSTATUS facility is a special case and thus does not utilize the
+    // HRESULT_FACILITY and HRESULT_CODE macros.
+    if (mHResult & FACILITY_NT_BIT) {
+      return Some(static_cast<NTSTATUS>(mHResult & ~FACILITY_NT_BIT));
+    }
+
+    return Nothing();
+  }
+
+  static DWORD NtStatusToWin32Error(NTSTATUS aNtStatus)
+  {
+    static const DynamicallyLinkedFunctionPtr<decltype(&RtlNtStatusToDosError)>
+      pRtlNtStatusToDosError(L"ntdll.dll", "RtlNtStatusToDosError");
+
+    MOZ_ASSERT(!!pRtlNtStatusToDosError);
+    if (!pRtlNtStatusToDosError) {
+      return ERROR_UNIDENTIFIED_ERROR;
+    }
+
+    return pRtlNtStatusToDosError(aNtStatus);
+  }
+
+private:
+  // We store the error code as an HRESULT because they can encode both Win32
+  // error codes and NTSTATUS codes.
+  HRESULT mHResult;
+};
+
+template <typename T>
+using WindowsErrorResult = Result<T, WindowsError>;
+
 // How long to wait for a created process to become available for input,
 // to prevent that process's windows being forced to the background.
 // This is used across update, restart, and the launcher.
 const DWORD kWaitForInputIdleTimeoutMS = 10*1000;
 
 /**
  * Wait for a child GUI process to become "idle." Idle means that the process
  * has created its message queue and has begun waiting for user input.
@@ -74,104 +238,122 @@ WaitForInputIdle(HANDLE aProcess, DWORD 
       ::Sleep(kSleepTimeMs);
       continue;
     }
 
     return false;
   }
 }
 
-enum PathType {
+enum class PathType
+{
   eNtPath,
   eDosPath,
 };
 
 class FileUniqueId final
 {
 public:
   explicit FileUniqueId(const wchar_t* aPath, PathType aPathType)
     : mId()
   {
     if (!aPath) {
       return;
     }
 
-    nsAutoHandle file(INVALID_HANDLE_VALUE);
+    nsAutoHandle file;
+
     switch (aPathType) {
-    default:
-      return;
+      default:
+        MOZ_ASSERT_UNREACHABLE("Unhandled PathType");
+        return;
 
-    case eNtPath:
-      {
-        UNICODE_STRING unicodeString;
-        ::RtlInitUnicodeString(&unicodeString, aPath);
-        OBJECT_ATTRIBUTES objectAttributes;
-        InitializeObjectAttributes(&objectAttributes, &unicodeString,
-                                   OBJ_CASE_INSENSITIVE, nullptr, nullptr);
-        IO_STATUS_BLOCK ioStatus = {};
-        HANDLE ntHandle;
-        NTSTATUS status = ::NtOpenFile(&ntHandle, SYNCHRONIZE |
-                                       FILE_READ_ATTRIBUTES,
-                                       &objectAttributes, &ioStatus,
-                                       FILE_SHARE_READ | FILE_SHARE_WRITE |
-                                       FILE_SHARE_DELETE,
-                                       FILE_SYNCHRONOUS_IO_NONALERT |
-                                       FILE_OPEN_FOR_BACKUP_INTENT);
-        if (!NT_SUCCESS(status) || ntHandle == INVALID_HANDLE_VALUE) {
+      case PathType::eNtPath: {
+          UNICODE_STRING unicodeString;
+          ::RtlInitUnicodeString(&unicodeString, aPath);
+          OBJECT_ATTRIBUTES objectAttributes;
+          InitializeObjectAttributes(&objectAttributes, &unicodeString,
+                                     OBJ_CASE_INSENSITIVE, nullptr, nullptr);
+          IO_STATUS_BLOCK ioStatus = {};
+          HANDLE ntHandle;
+          NTSTATUS status = ::NtOpenFile(&ntHandle, SYNCHRONIZE |
+                                         FILE_READ_ATTRIBUTES,
+                                         &objectAttributes, &ioStatus,
+                                         FILE_SHARE_READ | FILE_SHARE_WRITE |
+                                         FILE_SHARE_DELETE,
+                                         FILE_SYNCHRONOUS_IO_NONALERT |
+                                         FILE_OPEN_FOR_BACKUP_INTENT);
+          // We don't need to check |ntHandle| for INVALID_HANDLE_VALUE here,
+          // as that value is set by the Win32 layer.
+          if (!NT_SUCCESS(status)) {
+            mError = Some(WindowsError::FromNtStatus(status));
+            return;
+          }
+
+          file.own(ntHandle);
+        }
+
+        break;
+
+      case PathType::eDosPath: {
+        file.own(::CreateFileW(aPath, 0, FILE_SHARE_READ |
+                               FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                               nullptr, OPEN_EXISTING,
+                               FILE_FLAG_BACKUP_SEMANTICS, nullptr));
+        if (file == INVALID_HANDLE_VALUE) {
+          mError = Some(WindowsError::FromLastError());
           return;
         }
-        file.own(ntHandle);
+
+        break;
       }
-      break;
-
-    case eDosPath:
-      file.own(::CreateFileW(aPath, 0, FILE_SHARE_READ |
-                             FILE_SHARE_WRITE | FILE_SHARE_DELETE,
-                             nullptr, OPEN_EXISTING,
-                             FILE_FLAG_BACKUP_SEMANTICS, nullptr));
-      if (file == INVALID_HANDLE_VALUE) {
-        return;
-      }
-      break;
     }
 
     GetId(file);
   }
 
   explicit FileUniqueId(const nsAutoHandle& aFile)
     : mId()
   {
     GetId(aFile);
   }
 
   FileUniqueId(const FileUniqueId& aOther)
     : mId(aOther.mId)
+    , mError(aOther.mError)
   {
   }
 
   ~FileUniqueId() = default;
 
   explicit operator bool() const
   {
     FILE_ID_INFO zeros = {};
-    return memcmp(&mId, &zeros, sizeof(FILE_ID_INFO));
+    return !mError && memcmp(&mId, &zeros, sizeof(FILE_ID_INFO));
+  }
+
+  Maybe<WindowsError> GetError() const
+  {
+    return mError;
   }
 
   FileUniqueId& operator=(const FileUniqueId& aOther)
   {
     mId = aOther.mId;
+    mError = aOther.mError;
     return *this;
   }
 
   FileUniqueId(FileUniqueId&& aOther) = delete;
   FileUniqueId& operator=(FileUniqueId&& aOther) = delete;
 
   bool operator==(const FileUniqueId& aOther) const
   {
-    return !memcmp(&mId, &aOther.mId, sizeof(FILE_ID_INFO));
+    return !mError && !aOther.mError &&
+           !memcmp(&mId, &aOther.mId, sizeof(FILE_ID_INFO));
   }
 
   bool operator!=(const FileUniqueId& aOther) const
   {
     return !((*this) == aOther);
   }
 
 private:
@@ -182,43 +364,47 @@ private:
         return;
       }
       // Only NTFS and ReFS support FileIdInfo. So we have to fallback if
       // GetFileInformationByHandleEx failed.
     }
 
     BY_HANDLE_FILE_INFORMATION info = {};
     if (!::GetFileInformationByHandle(aFile.get(), &info)) {
+      mError = Some(WindowsError::FromLastError());
       return;
     }
 
     mId.VolumeSerialNumber = info.dwVolumeSerialNumber;
     memcpy(&mId.FileId.Identifier[0], &info.nFileIndexLow,
            sizeof(DWORD));
     memcpy(&mId.FileId.Identifier[sizeof(DWORD)], &info.nFileIndexHigh,
            sizeof(DWORD));
   }
 
 private:
-  FILE_ID_INFO  mId;
+  FILE_ID_INFO        mId;
+  Maybe<WindowsError> mError;
 };
 
-inline Maybe<bool>
+inline WindowsErrorResult<bool>
 DoPathsPointToIdenticalFile(const wchar_t* aPath1, const wchar_t* aPath2,
-                            PathType aPathType1 = eDosPath,
-                            PathType aPathType2 = eDosPath)
+                            PathType aPathType1 = PathType::eDosPath,
+                            PathType aPathType2 = PathType::eDosPath)
 {
   FileUniqueId id1(aPath1, aPathType1);
   if (!id1) {
-    return Nothing();
+    Maybe<WindowsError> error = id1.GetError();
+    return Err(error.valueOr(WindowsError::CreateGeneric()));
   }
 
   FileUniqueId id2(aPath2, aPathType2);
   if (!id2) {
-    return Nothing();
+    Maybe<WindowsError> error = id2.GetError();
+    return Err(error.valueOr(WindowsError::CreateGeneric()));
   }
 
-  return Some(id1 == id2);
+  return id1 == id2;
 }
 
 } // namespace mozilla
 
 #endif // mozilla_WinHeaderOnlyUtils_h