1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #if defined(XP_WIN)
6 #include <windows.h>
7 #include <winioctl.h> // for FSCTL_GET_REPARSE_POINT
8 #endif
9
10 #include <stdio.h>
11 #include <string.h>
12 #include <stdlib.h>
13 #include <stdarg.h>
14
15 #include "updatecommon.h"
16 #ifdef XP_WIN
17 #include "updatehelper.h"
18 #include "nsWindowsHelpers.h"
19 #include "mozilla/UniquePtr.h"
20
21 // This struct isn't in any SDK header, so this definition was copied from:
22 // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/ns-ntifs-_reparse_data_buffer
23 typedef struct _REPARSE_DATA_BUFFER {
24 ULONG ReparseTag;
25 USHORT ReparseDataLength;
26 USHORT Reserved;
27 union {
28 struct {
29 USHORT SubstituteNameOffset;
30 USHORT SubstituteNameLength;
31 USHORT PrintNameOffset;
32 USHORT PrintNameLength;
33 ULONG Flags;
34 WCHAR PathBuffer[1];
35 } SymbolicLinkReparseBuffer;
36 struct {
37 USHORT SubstituteNameOffset;
38 USHORT SubstituteNameLength;
39 USHORT PrintNameOffset;
40 USHORT PrintNameLength;
41 WCHAR PathBuffer[1];
42 } MountPointReparseBuffer;
43 struct {
44 UCHAR DataBuffer[1];
45 } GenericReparseBuffer;
46 } DUMMYUNIONNAME;
47 } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
48 #endif
49
UpdateLog()50 UpdateLog::UpdateLog() : logFP(nullptr) {}
51
Init(NS_tchar * sourcePath,const NS_tchar * fileName)52 void UpdateLog::Init(NS_tchar* sourcePath, const NS_tchar* fileName) {
53 if (logFP) {
54 return;
55 }
56
57 int dstFilePathLen =
58 NS_tsnprintf(mDstFilePath, sizeof(mDstFilePath) / sizeof(mDstFilePath[0]),
59 NS_T("%s/%s"), sourcePath, fileName);
60 // If the destination path was over the length limit,
61 // disable logging by skipping opening the file and setting logFP.
62 if ((dstFilePathLen > 0) &&
63 (dstFilePathLen <
64 static_cast<int>(sizeof(mDstFilePath) / sizeof(mDstFilePath[0])))) {
65 #ifdef XP_WIN
66 if (GetUUIDTempFilePath(sourcePath, L"log", mTmpFilePath)) {
67 logFP = NS_tfopen(mTmpFilePath, NS_T("w"));
68 // Delete this file now so it is possible to tell from the unelevated
69 // updater process if the elevated updater process has written the log.
70 DeleteFileW(mDstFilePath);
71 }
72 #elif XP_MACOSX
73 logFP = NS_tfopen(mDstFilePath, NS_T("w"));
74 #else
75 // On platforms that have an updates directory in the installation directory
76 // (e.g. platforms other than Windows and Mac) the update log is written to
77 // a temporary file and then to the update log file. This is needed since
78 // the installation directory is moved during a replace request. This can be
79 // removed when the platform's updates directory is located outside of the
80 // installation directory.
81 logFP = tmpfile();
82 #endif
83 }
84 }
85
Finish()86 void UpdateLog::Finish() {
87 if (!logFP) {
88 return;
89 }
90
91 #if !defined(XP_WIN) && !defined(XP_MACOSX)
92 const int blockSize = 1024;
93 char buffer[blockSize];
94 fflush(logFP);
95 rewind(logFP);
96
97 FILE* updateLogFP = NS_tfopen(mDstFilePath, NS_T("wb+"));
98 while (!feof(logFP)) {
99 size_t read = fread(buffer, 1, blockSize, logFP);
100 if (ferror(logFP)) {
101 fclose(logFP);
102 logFP = nullptr;
103 fclose(updateLogFP);
104 updateLogFP = nullptr;
105 return;
106 }
107
108 size_t written = 0;
109
110 while (written < read) {
111 size_t chunkWritten = fwrite(buffer, 1, read - written, updateLogFP);
112 if (chunkWritten <= 0) {
113 fclose(logFP);
114 logFP = nullptr;
115 fclose(updateLogFP);
116 updateLogFP = nullptr;
117 return;
118 }
119
120 written += chunkWritten;
121 }
122 }
123 fclose(updateLogFP);
124 updateLogFP = nullptr;
125 #endif
126
127 fclose(logFP);
128 logFP = nullptr;
129
130 #ifdef XP_WIN
131 // When the log file already exists then the elevated updater has already
132 // written the log file and the temp file for the log should be discarded.
133 if (!NS_taccess(mDstFilePath, F_OK)) {
134 DeleteFileW(mTmpFilePath);
135 } else {
136 MoveFileW(mTmpFilePath, mDstFilePath);
137 }
138 #endif
139 }
140
Flush()141 void UpdateLog::Flush() {
142 if (!logFP) {
143 return;
144 }
145
146 fflush(logFP);
147 }
148
Printf(const char * fmt,...)149 void UpdateLog::Printf(const char* fmt, ...) {
150 if (!logFP) {
151 return;
152 }
153
154 va_list ap;
155 va_start(ap, fmt);
156 vfprintf(logFP, fmt, ap);
157 fprintf(logFP, "\n");
158 va_end(ap);
159 }
160
WarnPrintf(const char * fmt,...)161 void UpdateLog::WarnPrintf(const char* fmt, ...) {
162 if (!logFP) {
163 return;
164 }
165
166 va_list ap;
167 va_start(ap, fmt);
168 fprintf(logFP, "*** Warning: ");
169 vfprintf(logFP, fmt, ap);
170 fprintf(logFP, "***\n");
171 va_end(ap);
172 }
173
174 #ifdef XP_WIN
175 /**
176 * Determine if a path contains symlinks or junctions to disallowed locations
177 *
178 * @param fullPath The full path to check.
179 * @return true if the path contains invalid links or on errors,
180 * false if the check passes and the path can be used
181 */
PathContainsInvalidLinks(wchar_t * const fullPath)182 bool PathContainsInvalidLinks(wchar_t* const fullPath) {
183 wchar_t pathCopy[MAXPATHLEN + 1] = L"";
184 wcsncpy(pathCopy, fullPath, MAXPATHLEN);
185 wchar_t* remainingPath = nullptr;
186 wchar_t* nextToken = wcstok_s(pathCopy, L"\\", &remainingPath);
187 wchar_t* partialPath = nextToken;
188
189 while (nextToken) {
190 if ((GetFileAttributesW(partialPath) & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
191 nsAutoHandle h(CreateFileW(
192 partialPath, 0,
193 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
194 OPEN_EXISTING,
195 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr));
196 if (h == INVALID_HANDLE_VALUE) {
197 if (GetLastError() == ERROR_FILE_NOT_FOUND) {
198 // The path can't be an invalid link if it doesn't exist.
199 return false;
200 } else {
201 return true;
202 }
203 }
204
205 mozilla::UniquePtr<UINT8[]> byteBuffer =
206 mozilla::MakeUnique<UINT8[]>(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
207 if (!byteBuffer) {
208 return true;
209 }
210 ZeroMemory(byteBuffer.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
211 REPARSE_DATA_BUFFER* buffer = (REPARSE_DATA_BUFFER*)byteBuffer.get();
212 DWORD bytes = 0;
213 if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, nullptr, 0, buffer,
214 MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bytes, nullptr)) {
215 return true;
216 }
217
218 wchar_t* reparseTarget = nullptr;
219 switch (buffer->ReparseTag) {
220 case IO_REPARSE_TAG_MOUNT_POINT:
221 reparseTarget =
222 buffer->MountPointReparseBuffer.PathBuffer +
223 (buffer->MountPointReparseBuffer.SubstituteNameOffset /
224 sizeof(wchar_t));
225 if (buffer->MountPointReparseBuffer.SubstituteNameLength <
226 ARRAYSIZE(L"\\??\\")) {
227 return false;
228 }
229 break;
230 case IO_REPARSE_TAG_SYMLINK:
231 reparseTarget =
232 buffer->SymbolicLinkReparseBuffer.PathBuffer +
233 (buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset /
234 sizeof(wchar_t));
235 if (buffer->SymbolicLinkReparseBuffer.SubstituteNameLength <
236 ARRAYSIZE(L"\\??\\")) {
237 return false;
238 }
239 break;
240 default:
241 return true;
242 break;
243 }
244
245 if (!reparseTarget) {
246 return false;
247 }
248 if (wcsncmp(reparseTarget, L"\\??\\", ARRAYSIZE(L"\\??\\") - 1) != 0) {
249 return true;
250 }
251 }
252
253 nextToken = wcstok_s(nullptr, L"\\", &remainingPath);
254 PathAppendW(partialPath, nextToken);
255 }
256
257 return false;
258 }
259 #endif
260
261 /**
262 * Performs checks of a full path for validity for this application.
263 *
264 * @param origFullPath
265 * The full path to check.
266 * @return true if the path is valid for this application and false otherwise.
267 */
IsValidFullPath(NS_tchar * origFullPath)268 bool IsValidFullPath(NS_tchar* origFullPath) {
269 // Subtract 1 from MAXPATHLEN for null termination.
270 if (NS_tstrlen(origFullPath) > MAXPATHLEN - 1) {
271 // The path is longer than acceptable for this application.
272 return false;
273 }
274
275 #ifdef XP_WIN
276 NS_tchar testPath[MAXPATHLEN] = {NS_T('\0')};
277 // GetFullPathNameW will replace / with \ which PathCanonicalizeW requires.
278 if (GetFullPathNameW(origFullPath, MAXPATHLEN, testPath, nullptr) == 0) {
279 // Unable to get the full name for the path (e.g. invalid path).
280 return false;
281 }
282
283 NS_tchar canonicalPath[MAXPATHLEN] = {NS_T('\0')};
284 if (!PathCanonicalizeW(canonicalPath, testPath)) {
285 // Path could not be canonicalized (e.g. invalid path).
286 return false;
287 }
288
289 // Check if the path passed in resolves to a differerent path.
290 if (NS_tstricmp(origFullPath, canonicalPath) != 0) {
291 // Case insensitive string comparison between the supplied path and the
292 // canonical path are not equal. This will prevent directory traversal and
293 // the use of / in paths since they are converted to \.
294 return false;
295 }
296
297 NS_tstrncpy(testPath, origFullPath, MAXPATHLEN);
298 if (!PathStripToRootW(testPath)) {
299 // It should always be possible to strip a valid path to its root.
300 return false;
301 }
302
303 if (origFullPath[0] == NS_T('\\')) {
304 // Only allow UNC server share paths.
305 if (!PathIsUNCServerShareW(testPath)) {
306 return false;
307 }
308 }
309
310 if (PathContainsInvalidLinks(canonicalPath)) {
311 return false;
312 }
313 #else
314 // Only allow full paths.
315 if (origFullPath[0] != NS_T('/')) {
316 return false;
317 }
318
319 // The path must not traverse directories
320 if (NS_tstrstr(origFullPath, NS_T("..")) != nullptr ||
321 NS_tstrstr(origFullPath, NS_T("./")) != nullptr) {
322 return false;
323 }
324 #endif
325 return true;
326 }
327