1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_SameBinary_h 8 #define mozilla_SameBinary_h 9 10 #include "mozilla/WinHeaderOnlyUtils.h" 11 #include "mozilla/NativeNt.h" 12 #include "nsWindowsHelpers.h" 13 14 namespace mozilla { 15 16 class ProcessImagePath final { 17 PathType mType; 18 LauncherVoidResult mLastError; 19 20 // Using a larger buffer because an NT path may exceed MAX_PATH. 21 WCHAR mPathBuffer[(MAX_PATH * 2) + 1]; 22 23 public: 24 // Initialize with an NT path string of a given process handle ProcessImagePath(const nsAutoHandle & aProcess)25 explicit ProcessImagePath(const nsAutoHandle& aProcess) 26 : mType(PathType::eNtPath), mLastError(Ok()) { 27 DWORD len = mozilla::ArrayLength(mPathBuffer); 28 if (!::QueryFullProcessImageNameW(aProcess.get(), PROCESS_NAME_NATIVE, 29 mPathBuffer, &len)) { 30 mLastError = LAUNCHER_ERROR_FROM_LAST(); 31 return; 32 } 33 } 34 35 // Initizlize with a DOS path string of a given imagebase address ProcessImagePath(HMODULE aImageBase)36 explicit ProcessImagePath(HMODULE aImageBase) 37 : mType(PathType::eDosPath), mLastError(Ok()) { 38 DWORD len = ::GetModuleFileNameW(aImageBase, mPathBuffer, 39 mozilla::ArrayLength(mPathBuffer)); 40 if (!len || len == mozilla::ArrayLength(mPathBuffer)) { 41 mLastError = LAUNCHER_ERROR_FROM_LAST(); 42 return; 43 } 44 } 45 IsError()46 bool IsError() const { return mLastError.isErr(); } 47 GetError()48 const WindowsErrorType& GetError() const { return mLastError.inspectErr(); } 49 GetId()50 FileUniqueId GetId() const { return FileUniqueId(mPathBuffer, mType); } 51 CompareNtPaths(const ProcessImagePath & aOther)52 bool CompareNtPaths(const ProcessImagePath& aOther) const { 53 if (mLastError.isErr() || aOther.mLastError.isErr() || 54 mType != PathType::eNtPath || aOther.mType != PathType::eNtPath) { 55 return false; 56 } 57 58 UNICODE_STRING path1, path2; 59 ::RtlInitUnicodeString(&path1, mPathBuffer); 60 ::RtlInitUnicodeString(&path2, aOther.mPathBuffer); 61 return !!::RtlEqualUnicodeString(&path1, &path2, TRUE); 62 } 63 }; 64 65 enum class ImageFileCompareOption { 66 Default, 67 CompareNtPathsOnly, 68 }; 69 70 static inline mozilla::LauncherResult<bool> IsSameBinaryAsParentProcess( 71 ImageFileCompareOption aOption = ImageFileCompareOption::Default) { 72 mozilla::LauncherResult<DWORD> parentPid = mozilla::nt::GetParentProcessId(); 73 if (parentPid.isErr()) { 74 return parentPid.propagateErr(); 75 } 76 77 nsAutoHandle parentProcess(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 78 FALSE, parentPid.unwrap())); 79 if (!parentProcess.get()) { 80 DWORD err = ::GetLastError(); 81 if (err == ERROR_INVALID_PARAMETER || err == ERROR_ACCESS_DENIED) { 82 // In the ERROR_INVALID_PARAMETER case, the process identified by 83 // parentPid has already exited. This is a common case when the parent 84 // process is not Firefox, thus we should return false instead of erroring 85 // out. 86 // The ERROR_ACCESS_DENIED case can happen when the parent process is 87 // something that we don't have permission to query. For example, we may 88 // encounter this when Firefox is launched by the Windows Task Scheduler. 89 return false; 90 } 91 92 return LAUNCHER_ERROR_FROM_WIN32(err); 93 } 94 95 ProcessImagePath parentExe(parentProcess); 96 if (parentExe.IsError()) { 97 return ::mozilla::Err(parentExe.GetError()); 98 } 99 100 if (aOption == ImageFileCompareOption::Default) { 101 bool skipFileIdComparison = false; 102 103 FileUniqueId id1 = parentExe.GetId(); 104 if (id1.IsError()) { 105 // We saw a number of Win7 users failed to call NtOpenFile with 106 // STATUS_OBJECT_PATH_NOT_FOUND for an unknown reason. In this 107 // particular case, we fall back to the logic to compare NT path 108 // strings instead of a file id which will not fail because we don't 109 // need to open a file handle. 110 #if !defined(STATUS_OBJECT_PATH_NOT_FOUND) 111 constexpr NTSTATUS STATUS_OBJECT_PATH_NOT_FOUND = 0xc000003a; 112 #endif 113 const LauncherError& err = id1.GetError(); 114 if (err.mError != 115 WindowsError::FromNtStatus(STATUS_OBJECT_PATH_NOT_FOUND)) { 116 return ::mozilla::Err(err); 117 } 118 119 skipFileIdComparison = true; 120 } 121 122 if (!skipFileIdComparison) { 123 ProcessImagePath ourExe(nullptr); 124 if (ourExe.IsError()) { 125 return ::mozilla::Err(ourExe.GetError()); 126 } 127 128 FileUniqueId id2 = ourExe.GetId(); 129 if (id2.IsError()) { 130 return ::mozilla::Err(id2.GetError()); 131 } 132 return id1 == id2; 133 } 134 } 135 136 nsAutoHandle ourProcess(::GetCurrentProcess()); 137 ProcessImagePath ourExeNt(ourProcess); 138 if (ourExeNt.IsError()) { 139 return ::mozilla::Err(ourExeNt.GetError()); 140 } 141 return parentExe.CompareNtPaths(ourExeNt); 142 } 143 144 } // namespace mozilla 145 146 #endif // mozilla_SameBinary_h 147