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