summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/OSReauthenticator.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--security/manager/ssl/OSReauthenticator.cpp568
1 files changed, 568 insertions, 0 deletions
diff --git a/security/manager/ssl/OSReauthenticator.cpp b/security/manager/ssl/OSReauthenticator.cpp
new file mode 100644
index 0000000000..2000a50ffa
--- /dev/null
+++ b/security/manager/ssl/OSReauthenticator.cpp
@@ -0,0 +1,568 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "OSReauthenticator.h"
+
+#include "OSKeyStore.h"
+#include "nsNetCID.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Preferences.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsISupportsUtils.h"
+#include "nsIWidget.h"
+#include "nsPIDOMWindow.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/ipc/IPCTypes.h"
+
+NS_IMPL_ISUPPORTS(OSReauthenticator, nsIOSReauthenticator)
+
+extern mozilla::LazyLogModule gCredentialManagerSecretLog;
+
+using mozilla::LogLevel;
+using mozilla::Maybe;
+using mozilla::Preferences;
+using mozilla::WindowsHandle;
+using mozilla::dom::Promise;
+
+#define PREF_BLANK_PASSWORD "security.osreauthenticator.blank_password"
+#define PREF_PASSWORD_LAST_CHANGED_LO \
+ "security.osreauthenticator.password_last_changed_lo"
+#define PREF_PASSWORD_LAST_CHANGED_HI \
+ "security.osreauthenticator.password_last_changed_hi"
+
+#if defined(XP_WIN)
+# include <combaseapi.h>
+# include <ntsecapi.h>
+# include <wincred.h>
+# include <windows.h>
+# include "nsIWindowsRegKey.h" // Must be included after <windows.h> for HKEY definition
+# define SECURITY_WIN32
+# include <security.h>
+# include <shlwapi.h>
+# if !defined(__MINGW32__)
+# include <Lm.h>
+# undef ACCESS_READ // nsWindowsRegKey defines its own ACCESS_READ
+# endif // !defined(__MINGW32__)
+struct HandleCloser {
+ typedef HANDLE pointer;
+ void operator()(HANDLE h) {
+ if (h != INVALID_HANDLE_VALUE) {
+ CloseHandle(h);
+ }
+ }
+};
+struct BufferFreer {
+ typedef LPVOID pointer;
+ ULONG mSize;
+ explicit BufferFreer(ULONG size) : mSize(size) {}
+ void operator()(LPVOID b) {
+ SecureZeroMemory(b, mSize);
+ CoTaskMemFree(b);
+ }
+};
+struct LsaDeregistrator {
+ typedef HANDLE pointer;
+ void operator()(HANDLE h) {
+ if (h != INVALID_HANDLE_VALUE) {
+ LsaDeregisterLogonProcess(h);
+ }
+ }
+};
+typedef std::unique_ptr<HANDLE, HandleCloser> ScopedHANDLE;
+typedef std::unique_ptr<LPVOID, BufferFreer> ScopedBuffer;
+typedef std::unique_ptr<HANDLE, LsaDeregistrator> ScopedLsaHANDLE;
+
+constexpr int64_t Int32Modulo = 2147483648;
+
+// Get the token info holding the sid.
+std::unique_ptr<char[]> GetTokenInfo(ScopedHANDLE& token) {
+ DWORD length = 0;
+ // https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-gettokeninformation
+ mozilla::Unused << GetTokenInformation(token.get(), TokenUser, nullptr, 0,
+ &length);
+ if (!length || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("Unable to obtain current token info."));
+ return nullptr;
+ }
+ std::unique_ptr<char[]> token_info(new char[length]);
+ if (!GetTokenInformation(token.get(), TokenUser, token_info.get(), length,
+ &length)) {
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("Unable to obtain current token info (second call, possible "
+ "system error."));
+ return nullptr;
+ }
+ return token_info;
+}
+
+std::unique_ptr<char[]> GetUserTokenInfo() {
+ // Get current user sid to make sure the same user got logged in.
+ HANDLE token;
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
+ // Couldn't get a process token. This will fail any unlock attempts later.
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("Unable to obtain process token."));
+ return nullptr;
+ }
+ ScopedHANDLE scopedToken(token);
+ return GetTokenInfo(scopedToken);
+}
+
+Maybe<int64_t> GetPasswordLastChanged(const WCHAR* username) {
+# if defined(__MINGW32__)
+ // NetUserGetInfo requires Lm.h which is not provided in MinGW builds
+ return mozilla::Nothing();
+# else
+ LPUSER_INFO_1 user_info = NULL;
+ DWORD passwordAgeInSeconds = 0;
+
+ NET_API_STATUS ret =
+ NetUserGetInfo(NULL, username, 1, reinterpret_cast<LPBYTE*>(&user_info));
+
+ if (ret == NERR_Success) {
+ // Returns seconds since last password change.
+ passwordAgeInSeconds = user_info->usri1_password_age;
+ NetApiBufferFree(user_info);
+ } else {
+ return mozilla::Nothing();
+ }
+
+ // Return the time that the password was changed so we can use this
+ // for future comparisons.
+ return mozilla::Some(PR_Now() - passwordAgeInSeconds * PR_USEC_PER_SEC);
+# endif
+}
+
+bool IsAutoAdminLogonEnabled() {
+ // https://support.microsoft.com/en-us/help/324737/how-to-turn-on-automatic-logon-in-windows
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ rv = regKey->Open(
+ nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
+ nsLiteralString(
+ u"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"),
+ nsIWindowsRegKey::ACCESS_READ);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoString value;
+ rv = regKey->ReadStringValue(u"AutoAdminLogon"_ns, value);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ regKey->Close();
+
+ return value.Equals(u"1"_ns);
+}
+
+bool IsRequireSignonEnabled() {
+ // https://docs.microsoft.com/en-us/windows-hardware/customize/power-settings/no-subgroup-settings-prompt-for-password-on-resume
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
+ u"System\\CurrentControlSet\\Control\\Power\\User\\Power"
+ "Schemes"_ns,
+ nsIWindowsRegKey::ACCESS_READ);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ nsAutoString activePowerScheme;
+ rv = regKey->ReadStringValue(u"ActivePowerScheme"_ns, activePowerScheme);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ regKey->Close();
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
+ u"System\\CurrentControlSet\\Control\\Power\\User\\Power"
+ "Schemes\\"_ns +
+ activePowerScheme +
+ u"\\0e796bdb-100d-47d6-a2d5-f7d2daa51f51"_ns,
+ nsIWindowsRegKey::ACCESS_READ);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ uint32_t value;
+ rv = regKey->ReadIntValue(u"ACSettingIndex"_ns, &value);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ regKey->Close();
+
+ return !!value;
+}
+
+// Use the Windows credential prompt to ask the user to authenticate the
+// currently used account.
+static nsresult ReauthenticateUserWindows(
+ const nsAString& aMessageText, const nsAString& aCaptionText,
+ const WindowsHandle& hwndParent,
+ /* out */ bool& reauthenticated,
+ /* inout */ bool& isBlankPassword,
+ /* inout */ int64_t& prefLastChanged,
+ /* out */ bool& isAutoAdminLogonEnabled,
+ /* out */ bool& isRequireSignonEnabled) {
+ reauthenticated = false;
+ isAutoAdminLogonEnabled = false;
+ isRequireSignonEnabled = true;
+
+ // Check if the user has a blank password before proceeding
+ DWORD usernameLength = CREDUI_MAX_USERNAME_LENGTH + 1;
+ WCHAR username[CREDUI_MAX_USERNAME_LENGTH + 1] = {0};
+
+ if (!GetUserNameEx(NameSamCompatible, username, &usernameLength)) {
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("Error getting username"));
+ return NS_ERROR_FAILURE;
+ }
+
+# ifdef OS_DOMAINMEMBER
+ bool isDomainMember = IsOS(OS_DOMAINMEMBER);
+# else
+ // Bug 1633097
+ bool isDomainMember = false;
+# endif
+ if (!isDomainMember) {
+ const WCHAR* usernameNoDomain = username;
+ // Don't include the domain portion of the username when calling LogonUser.
+ LPCWSTR backslash = wcschr(username, L'\\');
+ if (backslash) {
+ usernameNoDomain = backslash + 1;
+ }
+
+ Maybe<int64_t> lastChanged = GetPasswordLastChanged(usernameNoDomain);
+ if (lastChanged.isSome()) {
+ bool shouldCheckAgain = lastChanged.value() > prefLastChanged;
+ // Update the value stored in preferences
+ prefLastChanged = lastChanged.value();
+
+ if (shouldCheckAgain) {
+ HANDLE logonUserHandle = INVALID_HANDLE_VALUE;
+ bool result =
+ LogonUser(usernameNoDomain, L".", L"", LOGON32_LOGON_INTERACTIVE,
+ LOGON32_PROVIDER_DEFAULT, &logonUserHandle);
+ if (result) {
+ CloseHandle(logonUserHandle);
+ }
+ // ERROR_ACCOUNT_RESTRICTION: Indicates a referenced user name and
+ // authentication information are valid, but some user account
+ // restriction has prevented successful authentication (such as
+ // time-of-day restrictions).
+ reauthenticated = isBlankPassword =
+ (result || GetLastError() == ERROR_ACCOUNT_RESTRICTION);
+ } else if (isBlankPassword) {
+ reauthenticated = true;
+ }
+
+ if (reauthenticated) {
+ return NS_OK;
+ }
+ } else {
+ isBlankPassword = false;
+ }
+ } else {
+ // Update any preferences, assuming domain members do not have blank
+ // passwords
+ isBlankPassword = false;
+ }
+
+ isAutoAdminLogonEnabled = IsAutoAdminLogonEnabled();
+
+ isRequireSignonEnabled = IsRequireSignonEnabled();
+
+ // Is used in next iteration if the previous login failed.
+ DWORD err = 0;
+ std::unique_ptr<char[]> userTokenInfo = GetUserTokenInfo();
+
+ // CredUI prompt.
+ CREDUI_INFOW credui = {};
+ credui.cbSize = sizeof(credui);
+ credui.hwndParent = reinterpret_cast<HWND>(hwndParent);
+ const nsString& messageText = PromiseFlatString(aMessageText);
+ credui.pszMessageText = messageText.get();
+ const nsString& captionText = PromiseFlatString(aCaptionText);
+ credui.pszCaptionText = captionText.get();
+ credui.hbmBanner = nullptr; // ignored
+
+ while (!reauthenticated) {
+ HANDLE lsa = INVALID_HANDLE_VALUE;
+ // Get authentication handle for future user authentications.
+ // https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/nf-ntsecapi-lsaconnectuntrusted
+ if (LsaConnectUntrusted(&lsa) != ERROR_SUCCESS) {
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("Error acquiring lsa. Authentication attempts will fail."));
+ return NS_ERROR_FAILURE;
+ }
+ ScopedLsaHANDLE scopedLsa(lsa);
+
+ if (!userTokenInfo || lsa == INVALID_HANDLE_VALUE) {
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("Error setting up login and user token."));
+ return NS_ERROR_FAILURE;
+ }
+
+ ULONG authPackage = 0;
+ ULONG outCredSize = 0;
+ LPVOID outCredBuffer = nullptr;
+
+ // Get user's Windows credentials.
+ // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creduipromptforwindowscredentialsw
+ err = CredUIPromptForWindowsCredentialsW(
+ &credui, err, &authPackage, nullptr, 0, &outCredBuffer, &outCredSize,
+ nullptr, CREDUIWIN_ENUMERATE_CURRENT_USER);
+ ScopedBuffer scopedOutCredBuffer(outCredBuffer, BufferFreer(outCredSize));
+ if (err == ERROR_CANCELLED) {
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("Error getting authPackage for user login, user cancel."));
+ return NS_OK;
+ }
+ if (err != ERROR_SUCCESS) {
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("Error getting authPackage for user login."));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Verify the credentials.
+ TOKEN_SOURCE source;
+ PCHAR contextName = const_cast<PCHAR>("Mozilla");
+ size_t nameLength =
+ std::min(TOKEN_SOURCE_LENGTH, static_cast<int>(strlen(contextName)));
+ // Note that the string must not be longer than TOKEN_SOURCE_LENGTH.
+ memcpy(source.SourceName, contextName, nameLength);
+ // https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-allocatelocallyuniqueid
+ if (!AllocateLocallyUniqueId(&source.SourceIdentifier)) {
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("Error allocating ID for logon process."));
+ return NS_ERROR_FAILURE;
+ }
+
+ NTSTATUS substs;
+ void* profileBuffer = nullptr;
+ ULONG profileBufferLength = 0;
+ QUOTA_LIMITS limits = {0};
+ LUID luid;
+ HANDLE token = INVALID_HANDLE_VALUE;
+ LSA_STRING name;
+ name.Buffer = contextName;
+ name.Length = strlen(name.Buffer);
+ name.MaximumLength = name.Length;
+ // https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/nf-ntsecapi-lsalogonuser
+ NTSTATUS sts = LsaLogonUser(
+ scopedLsa.get(), &name, (SECURITY_LOGON_TYPE)Interactive, authPackage,
+ scopedOutCredBuffer.get(), outCredSize, nullptr, &source,
+ &profileBuffer, &profileBufferLength, &luid, &token, &limits, &substs);
+ ScopedHANDLE scopedToken(token);
+ LsaFreeReturnBuffer(profileBuffer);
+ if (sts == ERROR_SUCCESS) {
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("User logged in successfully."));
+ } else {
+ err = LsaNtStatusToWinError(sts);
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("Login failed with %lx (%lx).", sts, err));
+ continue;
+ }
+
+ // The user can select any user to log-in on the authentication prompt.
+ // Make sure that the logged in user is the current user.
+ std::unique_ptr<char[]> logonTokenInfo = GetTokenInfo(scopedToken);
+ if (!logonTokenInfo) {
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("Error getting logon token info."));
+ return NS_ERROR_FAILURE;
+ }
+ PSID logonSID =
+ reinterpret_cast<TOKEN_USER*>(logonTokenInfo.get())->User.Sid;
+ PSID userSID = reinterpret_cast<TOKEN_USER*>(userTokenInfo.get())->User.Sid;
+ if (EqualSid(userSID, logonSID)) {
+ MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+ ("Login successfully (correct user)."));
+ reauthenticated = true;
+ break;
+ } else {
+ err = ERROR_LOGON_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+#endif // XP_WIN
+
+static nsresult ReauthenticateUser(const nsAString& prompt,
+ const nsAString& caption,
+ const WindowsHandle& hwndParent,
+ /* out */ bool& reauthenticated,
+ /* inout */ bool& isBlankPassword,
+ /* inout */ int64_t& prefLastChanged,
+ /* out */ bool& isAutoAdminLogonEnabled,
+ /* out */ bool& isRequireSignonEnabled) {
+ reauthenticated = false;
+#if defined(XP_WIN)
+ return ReauthenticateUserWindows(
+ prompt, caption, hwndParent, reauthenticated, isBlankPassword,
+ prefLastChanged, isAutoAdminLogonEnabled, isRequireSignonEnabled);
+#elif defined(XP_MACOSX)
+ return ReauthenticateUserMacOS(prompt, reauthenticated, isBlankPassword);
+#else
+ return NS_OK;
+#endif // Reauthentication is not implemented for this platform.
+}
+
+static void BackgroundReauthenticateUser(RefPtr<Promise>& aPromise,
+ const nsAString& aMessageText,
+ const nsAString& aCaptionText,
+ const WindowsHandle& hwndParent,
+ bool isBlankPassword,
+ int64_t prefLastChanged) {
+ nsAutoCString recovery;
+ bool reauthenticated;
+ bool isAutoAdminLogonEnabled;
+ bool isRequireSignonEnabled;
+ nsresult rv = ReauthenticateUser(
+ aMessageText, aCaptionText, hwndParent, reauthenticated, isBlankPassword,
+ prefLastChanged, isAutoAdminLogonEnabled, isRequireSignonEnabled);
+
+ nsTArray<int32_t> prefLastChangedUpdates;
+#if defined(XP_WIN)
+ // Increase the lastChanged time to account for clock skew.
+ prefLastChanged += PR_USEC_PER_SEC;
+ // Need to split the 64bit integer to its hi and lo bits before sending it
+ // back to JS.
+ int32_t prefLastChangedHi = prefLastChanged / Int32Modulo;
+ int32_t prefLastChangedLo = prefLastChanged % Int32Modulo;
+ prefLastChangedUpdates.AppendElement(prefLastChangedHi);
+ prefLastChangedUpdates.AppendElement(prefLastChangedLo);
+#endif
+
+ nsTArray<int32_t> results;
+ results.AppendElement(reauthenticated);
+ results.AppendElement(isBlankPassword);
+#if defined(XP_WIN)
+ results.AppendElement(isAutoAdminLogonEnabled);
+ results.AppendElement(isRequireSignonEnabled);
+#endif
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+ "BackgroundReauthenticateUserResolve",
+ [rv, results = std::move(results),
+ prefLastChangedUpdates = std::move(prefLastChangedUpdates),
+ aPromise = std::move(aPromise)]() {
+ if (NS_FAILED(rv)) {
+ aPromise->MaybeReject(rv);
+ } else {
+ aPromise->MaybeResolve(results);
+ }
+
+ nsresult rv = Preferences::SetBool(PREF_BLANK_PASSWORD, results[1]);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ if (prefLastChangedUpdates.Length() > 1) {
+ rv = Preferences::SetInt(PREF_PASSWORD_LAST_CHANGED_HI,
+ prefLastChangedUpdates[0]);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ Preferences::SetInt(PREF_PASSWORD_LAST_CHANGED_LO,
+ prefLastChangedUpdates[1]);
+ }
+ }));
+ NS_DispatchToMainThread(runnable.forget());
+}
+
+NS_IMETHODIMP
+OSReauthenticator::AsyncReauthenticateUser(const nsAString& aMessageText,
+ const nsAString& aCaptionText,
+ mozIDOMWindow* aParentWindow,
+ JSContext* aCx,
+ Promise** promiseOut) {
+ NS_ENSURE_ARG_POINTER(aCx);
+
+ RefPtr<Promise> promiseHandle;
+ nsresult rv = GetPromise(aCx, promiseHandle);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ WindowsHandle hwndParent = 0;
+ if (aParentWindow) {
+ nsPIDOMWindowInner* win = nsPIDOMWindowInner::From(aParentWindow);
+ nsIDocShell* docShell = win->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(docShell);
+ if (baseWindow) {
+ nsCOMPtr<nsIWidget> widget;
+ baseWindow->GetMainWidget(getter_AddRefs(widget));
+ if (widget) {
+ hwndParent = reinterpret_cast<WindowsHandle>(
+ widget->GetNativeData(NS_NATIVE_WINDOW));
+ }
+ }
+ }
+ }
+
+ int64_t prefLastChanged = 0;
+ bool isBlankPassword = false;
+#if defined(XP_WIN)
+ // These preferences are only supported on Windows.
+ // Preferences are read/write main-thread only.
+ int32_t prefLastChangedLo;
+ int32_t prefLastChangedHi;
+ rv = Preferences::GetBool(PREF_BLANK_PASSWORD, &isBlankPassword);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = Preferences::GetInt(PREF_PASSWORD_LAST_CHANGED_LO, &prefLastChangedLo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = Preferences::GetInt(PREF_PASSWORD_LAST_CHANGED_HI, &prefLastChangedHi);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ prefLastChanged = prefLastChangedHi * Int32Modulo + prefLastChangedLo;
+#endif
+
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+ "BackgroundReauthenticateUser",
+ [promiseHandle, aMessageText = nsAutoString(aMessageText),
+ aCaptionText = nsAutoString(aCaptionText), hwndParent, isBlankPassword,
+ prefLastChanged]() mutable {
+ BackgroundReauthenticateUser(promiseHandle, aMessageText, aCaptionText,
+ hwndParent, isBlankPassword,
+ prefLastChanged);
+ }));
+
+ nsCOMPtr<nsIEventTarget> target(
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID));
+ if (!target) {
+ return NS_ERROR_FAILURE;
+ }
+ rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promiseHandle.forget(promiseOut);
+ return NS_OK;
+}