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