Bug 1481907 Part 1 - Block updater code from using paths containing invalid symlinks. r=rstrong
authorMatt Howell <mhowell@mozilla.com>
Mon, 13 Aug 2018 13:50:51 -0700
changeset 486651 710af6f30b99dac3c43ac3488a68e0d879f63b50
parent 486650 e72eac515afc77cfc9be68b3a33020cb79330eb9
child 486652 d3e5f5bcef6048adf5f324b24e862691a6a2efe4
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrstrong
bugs1481907
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1481907 Part 1 - Block updater code from using paths containing invalid symlinks. r=rstrong
toolkit/mozapps/update/common/updatecommon.cpp
--- a/toolkit/mozapps/update/common/updatecommon.cpp
+++ b/toolkit/mozapps/update/common/updatecommon.cpp
@@ -1,23 +1,62 @@
 /* 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
 #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"
+
+// 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* sourcePath,
                      const NS_tchar* fileName)
 {
@@ -142,16 +181,88 @@ void UpdateLog::WarnPrintf(const char *f
   va_list ap;
   va_start(ap, fmt);
   fprintf(logFP, "*** Warning: ");
   vfprintf(logFP, fmt, ap);
   fprintf(logFP, "***\n");
   va_end(ap);
 }
 
+#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(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;
+          break;
+        case IO_REPARSE_TAG_SYMLINK:
+          reparseTarget = buffer->SymbolicLinkReparseBuffer.PathBuffer;
+          break;
+        default:
+          return true;
+          break;
+      }
+
+      if (wcsncmp(reparseTarget, L"\\??\\", ARRAYSIZE(L"\\??\\") - 1) != 0) {
+        return true;
+      }
+    }
+
+    nextToken = wcstok(nullptr, L"\\", &remainingPath);
+    PathAppendW(partialPath, nextToken);
+  }
+
+  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
@@ -192,16 +303,20 @@ IsValidFullPath(NS_tchar* origFullPath)
   }
 
   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 ||