summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/update/common/updatecommon.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/update/common/updatecommon.cpp')
-rw-r--r--toolkit/mozapps/update/common/updatecommon.cpp464
1 files changed, 464 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/common/updatecommon.cpp b/toolkit/mozapps/update/common/updatecommon.cpp
new file mode 100644
index 0000000000..e270d71b7e
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatecommon.cpp
@@ -0,0 +1,464 @@
+/* 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/. */
+
+#if defined(XP_WIN)
+# include <windows.h>
+# include <winioctl.h> // for FSCTL_GET_REPARSE_POINT
+# include <shlobj.h>
+# ifndef RRF_SUBKEY_WOW6464KEY
+# define RRF_SUBKEY_WOW6464KEY 0x00010000
+# endif
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "updatecommon.h"
+#ifdef XP_WIN
+# include "updatehelper.h"
+# include "nsWindowsHelpers.h"
+# include "mozilla/UniquePtr.h"
+# include "mozilla/WinHeaderOnlyUtils.h"
+
+// This struct isn't in any SDK header, so this definition was copied from:
+// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/ns-ntifs-_reparse_data_buffer
+typedef struct _REPARSE_DATA_BUFFER {
+ ULONG ReparseTag;
+ USHORT ReparseDataLength;
+ USHORT Reserved;
+ union {
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ ULONG Flags;
+ WCHAR PathBuffer[1];
+ } SymbolicLinkReparseBuffer;
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ WCHAR PathBuffer[1];
+ } MountPointReparseBuffer;
+ struct {
+ UCHAR DataBuffer[1];
+ } GenericReparseBuffer;
+ } DUMMYUNIONNAME;
+} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+#endif
+
+UpdateLog::UpdateLog() : logFP(nullptr) {}
+
+void UpdateLog::Init(NS_tchar* logFilePath) {
+ if (logFP) {
+ return;
+ }
+
+ // When the path is over the length limit disable logging by not opening the
+ // file and not setting logFP.
+ int dstFilePathLen = NS_tstrlen(logFilePath);
+ if (dstFilePathLen > 0 && dstFilePathLen < MAXPATHLEN - 1) {
+ NS_tstrncpy(mDstFilePath, logFilePath, MAXPATHLEN);
+#if defined(XP_WIN) || defined(XP_MACOSX)
+ logFP = NS_tfopen(mDstFilePath, NS_T("w"));
+#else
+ // On platforms that have an updates directory in the installation directory
+ // (e.g. platforms other than Windows and Mac) the update log is written to
+ // a temporary file and then to the update log file. This is needed since
+ // the installation directory is moved during a replace request. This can be
+ // removed when the platform's updates directory is located outside of the
+ // installation directory.
+ logFP = tmpfile();
+#endif
+ }
+}
+
+void UpdateLog::Finish() {
+ if (!logFP) {
+ return;
+ }
+
+#if !defined(XP_WIN) && !defined(XP_MACOSX)
+ const int blockSize = 1024;
+ char buffer[blockSize];
+ fflush(logFP);
+ rewind(logFP);
+
+ FILE* updateLogFP = NS_tfopen(mDstFilePath, NS_T("wb+"));
+ while (!feof(logFP)) {
+ size_t read = fread(buffer, 1, blockSize, logFP);
+ if (ferror(logFP)) {
+ fclose(logFP);
+ logFP = nullptr;
+ fclose(updateLogFP);
+ updateLogFP = nullptr;
+ return;
+ }
+
+ size_t written = 0;
+
+ while (written < read) {
+ size_t chunkWritten = fwrite(buffer, 1, read - written, updateLogFP);
+ if (chunkWritten <= 0) {
+ fclose(logFP);
+ logFP = nullptr;
+ fclose(updateLogFP);
+ updateLogFP = nullptr;
+ return;
+ }
+
+ written += chunkWritten;
+ }
+ }
+ fclose(updateLogFP);
+ updateLogFP = nullptr;
+#endif
+
+ fclose(logFP);
+ logFP = nullptr;
+}
+
+void UpdateLog::Flush() {
+ if (!logFP) {
+ return;
+ }
+
+ fflush(logFP);
+}
+
+void UpdateLog::Printf(const char* fmt, ...) {
+ if (!logFP) {
+ return;
+ }
+
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(logFP, fmt, ap);
+ fprintf(logFP, "\n");
+ va_end(ap);
+#if defined(XP_WIN) && defined(MOZ_DEBUG)
+ // When the updater crashes on Windows the log file won't be flushed and this
+ // can make it easier to debug what is going on.
+ fflush(logFP);
+#endif
+}
+
+void UpdateLog::WarnPrintf(const char* fmt, ...) {
+ if (!logFP) {
+ return;
+ }
+
+ va_list ap;
+ va_start(ap, fmt);
+ fprintf(logFP, "*** Warning: ");
+ vfprintf(logFP, fmt, ap);
+ fprintf(logFP, "***\n");
+ va_end(ap);
+#if defined(XP_WIN) && defined(MOZ_DEBUG)
+ // When the updater crashes on Windows the log file won't be flushed and this
+ // can make it easier to debug what is going on.
+ fflush(logFP);
+#endif
+}
+
+#ifdef XP_WIN
+/**
+ * Determine if a path contains symlinks or junctions to disallowed locations
+ *
+ * @param fullPath The full path to check.
+ * @return true if the path contains invalid links or on errors,
+ * false if the check passes and the path can be used
+ */
+bool PathContainsInvalidLinks(wchar_t* const fullPath) {
+ wchar_t pathCopy[MAXPATHLEN + 1] = L"";
+ wcsncpy(pathCopy, fullPath, MAXPATHLEN);
+ wchar_t* remainingPath = nullptr;
+ wchar_t* nextToken = wcstok_s(pathCopy, L"\\", &remainingPath);
+ wchar_t* partialPath = nextToken;
+
+ while (nextToken) {
+ if ((GetFileAttributesW(partialPath) & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
+ nsAutoHandle h(CreateFileW(
+ partialPath, 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr));
+ if (h == INVALID_HANDLE_VALUE) {
+ if (GetLastError() == ERROR_FILE_NOT_FOUND) {
+ // The path can't be an invalid link if it doesn't exist.
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ mozilla::UniquePtr<UINT8[]> byteBuffer =
+ mozilla::MakeUnique<UINT8[]>(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+ if (!byteBuffer) {
+ return true;
+ }
+ ZeroMemory(byteBuffer.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+ REPARSE_DATA_BUFFER* buffer = (REPARSE_DATA_BUFFER*)byteBuffer.get();
+ DWORD bytes = 0;
+ if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, nullptr, 0, buffer,
+ MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bytes, nullptr)) {
+ return true;
+ }
+
+ wchar_t* reparseTarget = nullptr;
+ switch (buffer->ReparseTag) {
+ case IO_REPARSE_TAG_MOUNT_POINT:
+ reparseTarget =
+ buffer->MountPointReparseBuffer.PathBuffer +
+ (buffer->MountPointReparseBuffer.SubstituteNameOffset /
+ sizeof(wchar_t));
+ if (buffer->MountPointReparseBuffer.SubstituteNameLength <
+ ARRAYSIZE(L"\\??\\")) {
+ return false;
+ }
+ break;
+ case IO_REPARSE_TAG_SYMLINK:
+ reparseTarget =
+ buffer->SymbolicLinkReparseBuffer.PathBuffer +
+ (buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset /
+ sizeof(wchar_t));
+ if (buffer->SymbolicLinkReparseBuffer.SubstituteNameLength <
+ ARRAYSIZE(L"\\??\\")) {
+ return false;
+ }
+ break;
+ default:
+ return true;
+ break;
+ }
+
+ if (!reparseTarget) {
+ return false;
+ }
+ if (wcsncmp(reparseTarget, L"\\??\\", ARRAYSIZE(L"\\??\\") - 1) != 0) {
+ return true;
+ }
+ }
+
+ nextToken = wcstok_s(nullptr, L"\\", &remainingPath);
+ PathAppendW(partialPath, nextToken);
+ }
+
+ return false;
+}
+
+/**
+ * Determine if a path is located within Program Files, either native or x86
+ *
+ * @param fullPath The full path to check.
+ * @return true if fullPath begins with either Program Files directory,
+ * false if it does not or if an error is encountered
+ */
+bool IsProgramFilesPath(NS_tchar* fullPath) {
+ // Make sure we don't try to compare against a short path.
+ DWORD longInstallPathChars = GetLongPathNameW(fullPath, nullptr, 0);
+ if (longInstallPathChars == 0) {
+ return false;
+ }
+ mozilla::UniquePtr<wchar_t[]> longInstallPath =
+ mozilla::MakeUnique<wchar_t[]>(longInstallPathChars);
+ if (!longInstallPath || !GetLongPathNameW(fullPath, longInstallPath.get(),
+ longInstallPathChars)) {
+ return false;
+ }
+
+ // First check for Program Files (x86).
+ {
+ PWSTR programFiles32PathRaw = nullptr;
+ // FOLDERID_ProgramFilesX86 gets native Program Files directory on a 32-bit
+ // OS or the (x86) directory on a 64-bit OS regardless of this binary's
+ // bitness.
+ if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, 0, nullptr,
+ &programFiles32PathRaw))) {
+ // That call should never fail on any supported OS version.
+ return false;
+ }
+ mozilla::UniquePtr<wchar_t, mozilla::CoTaskMemFreeDeleter>
+ programFiles32Path(programFiles32PathRaw);
+ // We need this path to have a trailing slash so our prefix test doesn't
+ // match on a different folder which happens to have a name beginning with
+ // the prefix we're looking for but then also more characters after that.
+ size_t length = wcslen(programFiles32Path.get());
+ if (length == 0) {
+ return false;
+ }
+ if (programFiles32Path.get()[length - 1] == L'\\') {
+ if (wcsnicmp(longInstallPath.get(), programFiles32Path.get(), length) ==
+ 0) {
+ return true;
+ }
+ } else {
+ // Allocate space for a copy of the string along with a terminator and one
+ // extra character for the trailing backslash.
+ length += 1;
+ mozilla::UniquePtr<wchar_t[]> programFiles32PathWithSlash =
+ mozilla::MakeUnique<wchar_t[]>(length + 1);
+ if (!programFiles32PathWithSlash) {
+ return false;
+ }
+
+ NS_tsnprintf(programFiles32PathWithSlash.get(), length + 1, NS_T("%s\\"),
+ programFiles32Path.get());
+
+ if (wcsnicmp(longInstallPath.get(), programFiles32PathWithSlash.get(),
+ length) == 0) {
+ return true;
+ }
+ }
+ }
+
+ // If we didn't find (x86), check for the native Program Files.
+ {
+ // In case we're a 32-bit binary on 64-bit Windows, we now have a problem
+ // getting the right "native" Program Files path, which is that there is no
+ // FOLDERID_* value that returns that path. So we always read that one out
+ // of its canonical registry location instead. If we're on a 32-bit OS, this
+ // will be the same path that we just checked. First get the buffer size to
+ // allocate for the path.
+ DWORD length = 0;
+ if (RegGetValueW(HKEY_LOCAL_MACHINE,
+ L"Software\\Microsoft\\Windows\\CurrentVersion",
+ L"ProgramFilesDir", RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY,
+ nullptr, nullptr, &length) != ERROR_SUCCESS) {
+ return false;
+ }
+ // RegGetValue returns the length including the terminator, but it's in
+ // bytes, so convert that to characters.
+ DWORD lengthChars = (length / sizeof(wchar_t));
+ if (lengthChars <= 1) {
+ return false;
+ }
+ mozilla::UniquePtr<wchar_t[]> programFilesNativePath =
+ mozilla::MakeUnique<wchar_t[]>(lengthChars);
+ if (!programFilesNativePath) {
+ return false;
+ }
+
+ // Now actually read the value.
+ if (RegGetValueW(
+ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion",
+ L"ProgramFilesDir", RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, nullptr,
+ programFilesNativePath.get(), &length) != ERROR_SUCCESS) {
+ return false;
+ }
+ size_t nativePathStrLen =
+ wcsnlen_s(programFilesNativePath.get(), lengthChars);
+ if (nativePathStrLen == 0) {
+ return false;
+ }
+
+ // As before, append a backslash if there isn't one already.
+ if (programFilesNativePath.get()[nativePathStrLen - 1] == L'\\') {
+ if (wcsnicmp(longInstallPath.get(), programFilesNativePath.get(),
+ nativePathStrLen) == 0) {
+ return true;
+ }
+ } else {
+ // Allocate space for a copy of the string along with a terminator and one
+ // extra character for the trailing backslash.
+ nativePathStrLen += 1;
+ mozilla::UniquePtr<wchar_t[]> programFilesNativePathWithSlash =
+ mozilla::MakeUnique<wchar_t[]>(nativePathStrLen + 1);
+ if (!programFilesNativePathWithSlash) {
+ return false;
+ }
+
+ NS_tsnprintf(programFilesNativePathWithSlash.get(), nativePathStrLen + 1,
+ NS_T("%s\\"), programFilesNativePath.get());
+
+ if (wcsnicmp(longInstallPath.get(), programFilesNativePathWithSlash.get(),
+ nativePathStrLen) == 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+#endif
+
+/**
+ * Performs checks of a full path for validity for this application.
+ *
+ * @param origFullPath
+ * The full path to check.
+ * @return true if the path is valid for this application and false otherwise.
+ */
+bool IsValidFullPath(NS_tchar* origFullPath) {
+ // Subtract 1 from MAXPATHLEN for null termination.
+ if (NS_tstrlen(origFullPath) > MAXPATHLEN - 1) {
+ // The path is longer than acceptable for this application.
+ return false;
+ }
+
+#ifdef XP_WIN
+ NS_tchar testPath[MAXPATHLEN] = {NS_T('\0')};
+ // GetFullPathNameW will replace / with \ which PathCanonicalizeW requires.
+ if (GetFullPathNameW(origFullPath, MAXPATHLEN, testPath, nullptr) == 0) {
+ // Unable to get the full name for the path (e.g. invalid path).
+ return false;
+ }
+
+ NS_tchar canonicalPath[MAXPATHLEN] = {NS_T('\0')};
+ if (!PathCanonicalizeW(canonicalPath, testPath)) {
+ // Path could not be canonicalized (e.g. invalid path).
+ return false;
+ }
+
+ // Check if the path passed in resolves to a differerent path.
+ if (NS_tstricmp(origFullPath, canonicalPath) != 0) {
+ // Case insensitive string comparison between the supplied path and the
+ // canonical path are not equal. This will prevent directory traversal and
+ // the use of / in paths since they are converted to \.
+ return false;
+ }
+
+ NS_tstrncpy(testPath, origFullPath, MAXPATHLEN);
+ if (!PathStripToRootW(testPath)) {
+ // It should always be possible to strip a valid path to its root.
+ return false;
+ }
+
+ if (origFullPath[0] == NS_T('\\')) {
+ // Only allow UNC server share paths.
+ if (!PathIsUNCServerShareW(testPath)) {
+ return false;
+ }
+ }
+
+ if (PathContainsInvalidLinks(canonicalPath)) {
+ return false;
+ }
+#else
+ // Only allow full paths.
+ if (origFullPath[0] != NS_T('/')) {
+ return false;
+ }
+
+ // The path must not traverse directories
+ if (NS_tstrstr(origFullPath, NS_T("/../")) != nullptr) {
+ return false;
+ }
+
+ // The path shall not have a path traversal suffix
+ const NS_tchar invalidSuffix[] = NS_T("/..");
+ size_t pathLen = NS_tstrlen(origFullPath);
+ size_t invalidSuffixLen = NS_tstrlen(invalidSuffix);
+ if (invalidSuffixLen <= pathLen &&
+ NS_tstrncmp(origFullPath + pathLen - invalidSuffixLen, invalidSuffix,
+ invalidSuffixLen) == 0) {
+ return false;
+ }
+#endif
+ return true;
+}