Client code - Bug 1342742 - check that the app update patch dir, install dir, and working dir paths are valid. r=mhowell

--HG--
rename : toolkit/mozapps/update/common/updatelogging.cpp => toolkit/mozapps/update/common/updatecommon.cpp
rename : toolkit/mozapps/update/common/updatelogging.h => toolkit/mozapps/update/common/updatecommon.h
This commit is contained in:
Robert Strong 2017-04-28 16:36:45 -07:00
Родитель c9357cb3b0
Коммит 0de416e483
13 изменённых файлов: 273 добавлений и 34 удалений

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <windows.h>
#include "updatelogging.h"
#include "updatecommon.h"
BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
BOOL VerifySameFiles(LPCWSTR file1Path, LPCWSTR file2Path, BOOL &sameContent);

Просмотреть файл

@ -22,6 +22,7 @@
#include "registrycertificates.h"
#include "uachelper.h"
#include "updatehelper.h"
#include "pathhash.h"
#include "errors.h"
// Wait 15 minutes for an update operation to run at most.
@ -32,6 +33,7 @@ wchar_t* MakeCommandLine(int argc, wchar_t** argv);
BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
LPCWSTR newFileName);
BOOL DoesFallbackKeyExist();
/*
* Read the update.status file and sets isApplying to true if
@ -415,8 +417,7 @@ ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv)
// We can only update update.status if argv[1] exists. argv[1] is
// the directory where the update.status file exists.
if (argc < 2 ||
!WriteStatusFailure(argv[1],
SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) {
!WriteStatusFailure(argv[1], SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) {
LOG_WARN(("Could not write update.status service update failure. (%d)",
GetLastError()));
}
@ -426,8 +427,7 @@ ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv)
WCHAR installDir[MAX_PATH + 1] = {L'\0'};
if (!GetInstallationDir(argc, argv, installDir)) {
LOG_WARN(("Could not get the installation directory"));
if (!WriteStatusFailure(argv[1],
SERVICE_INSTALLDIR_ERROR)) {
if (!WriteStatusFailure(argv[1], SERVICE_INSTALLDIR_ERROR)) {
LOG_WARN(("Could not write update.status for GetInstallationDir failure."));
}
return FALSE;
@ -587,6 +587,73 @@ ExecuteServiceCommand(int argc, LPWSTR *argv)
BOOL result = FALSE;
if (!lstrcmpi(argv[2], L"software-update")) {
// This check is also performed in updater.cpp and is performed here
// as well since the maintenance service can be called directly.
if (argc < 4 || !IsValidFullPath(argv[4])) {
// Since the status file is written to the patch directory and the patch
// directory is invalid don't write the status file.
LOG_WARN(("The patch directory path is not valid for this application."));
return FALSE;
}
// This check is also performed in updater.cpp and is performed here
// as well since the maintenance service can be called directly.
if (argc < 5 || !IsValidFullPath(argv[5])) {
LOG_WARN(("The install directory path is not valid for this application."));
if (!WriteStatusFailure(argv[4], SERVICE_INVALID_INSTALL_DIR_PATH_ERROR)) {
LOG_WARN(("Could not write update.status for previous failure."));
}
return FALSE;
}
if (!IsOldCommandline(argc - 3, argv + 3)) {
// This check is also performed in updater.cpp and is performed here
// as well since the maintenance service can be called directly.
if (argc < 6 || !IsValidFullPath(argv[6])) {
LOG_WARN(("The working directory path is not valid for this application."));
if (!WriteStatusFailure(argv[4], SERVICE_INVALID_WORKING_DIR_PATH_ERROR)) {
LOG_WARN(("Could not write update.status for previous failure."));
}
return FALSE;
}
// These checks are also performed in updater.cpp and is performed here
// as well since the maintenance service can be called directly.
if (_wcsnicmp(argv[6], argv[5], MAX_PATH) != 0) {
if (wcscmp(argv[7], L"-1") != 0 && !wcsstr(argv[7], L"/replace")) {
LOG_WARN(("Installation directory and working directory must be the "
"same for non-staged updates. Exiting."));
if (!WriteStatusFailure(argv[4], SERVICE_INVALID_APPLYTO_DIR_ERROR)) {
LOG_WARN(("Could not write update.status for previous failure."));
}
return FALSE;
}
NS_tchar workingDirParent[MAX_PATH];
NS_tsnprintf(workingDirParent,
sizeof(workingDirParent) / sizeof(workingDirParent[0]),
NS_T("%s"), argv[6]);
if (!PathRemoveFileSpecW(workingDirParent)) {
LOG_WARN(("Couldn't remove file spec when attempting to verify the "
"working directory path. (%d)", GetLastError()));
if (!WriteStatusFailure(argv[4], REMOVE_FILE_SPEC_ERROR)) {
LOG_WARN(("Could not write update.status for previous failure."));
}
return FALSE;
}
if (_wcsnicmp(workingDirParent, argv[5], MAX_PATH) != 0) {
LOG_WARN(("The apply-to directory must be the same as or "
"a child of the installation directory! Exiting."));
if (!WriteStatusFailure(argv[4], SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR)) {
LOG_WARN(("Could not write update.status for previous failure."));
}
return FALSE;
}
}
}
// Use the passed in command line arguments for the update, except for the
// path to updater.exe. We always look for updater.exe in the installation
// directory, then we copy updater.exe to a the directory of the
@ -596,12 +663,37 @@ ExecuteServiceCommand(int argc, LPWSTR *argv)
WCHAR installDir[MAX_PATH + 1] = { L'\0' };
if (!GetInstallationDir(argc - 3, argv + 3, installDir)) {
LOG_WARN(("Could not get the installation directory"));
if (!WriteStatusFailure(argv[1],
SERVICE_INSTALLDIR_ERROR)) {
LOG_WARN(("Could not write update.status for GetInstallationDir failure."));
if (!WriteStatusFailure(argv[4], SERVICE_INSTALLDIR_ERROR)) {
LOG_WARN(("Could not write update.status for previous failure."));
}
return FALSE;
}
if (!DoesFallbackKeyExist()) {
WCHAR maintenanceServiceKey[MAX_PATH + 1];
if (CalculateRegistryPathFromFilePath(installDir, maintenanceServiceKey)) {
LOG(("Checking for Maintenance Service registry. key: '%ls'",
maintenanceServiceKey));
HKEY baseKey = nullptr;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
maintenanceServiceKey, 0,
KEY_READ | KEY_WOW64_64KEY,
&baseKey) != ERROR_SUCCESS) {
LOG_WARN(("The maintenance service registry key does not exist."));
if (!WriteStatusFailure(argv[4], SERVICE_INSTALL_DIR_REG_ERROR)) {
LOG_WARN(("Could not write update.status for previous failure."));
}
return FALSE;
}
RegCloseKey(baseKey);
} else {
if (!WriteStatusFailure(argv[4], SERVICE_CALC_REG_PATH_ERROR)) {
LOG_WARN(("Could not write update.status for previous failure."));
}
return FALSE;
}
}
WCHAR installDirUpdater[MAX_PATH + 1] = { L'\0' };
wcsncpy(installDirUpdater, installDir, MAX_PATH);
if (!PathAppendSafe(installDirUpdater, L"updater.exe")) {
@ -609,7 +701,7 @@ ExecuteServiceCommand(int argc, LPWSTR *argv)
result = FALSE;
}
result = UpdaterIsValid(installDirUpdater, installDir, argv[5]);
result = UpdaterIsValid(installDirUpdater, installDir, argv[4]);
WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' };
if (result) {
@ -617,7 +709,7 @@ ExecuteServiceCommand(int argc, LPWSTR *argv)
}
if (result) {
LOG(("Passed in path: '%ls'; Using this path for updating: '%ls'.",
installDirUpdater, secureUpdaterPath));
installDirUpdater, secureUpdaterPath));
DeleteSecureUpdater(secureUpdaterPath);
result = CopyFileW(installDirUpdater, secureUpdaterPath, FALSE);
}
@ -625,8 +717,7 @@ ExecuteServiceCommand(int argc, LPWSTR *argv)
if (!result) {
LOG_WARN(("Could not copy path to secure location. (%d)",
GetLastError()));
if (argc > 4 && !WriteStatusFailure(argv[4],
SERVICE_COULD_NOT_COPY_UPDATER)) {
if (!WriteStatusFailure(argv[4], SERVICE_COULD_NOT_COPY_UPDATER)) {
LOG_WARN(("Could not write update.status could not copy updater error"));
}
} else {

Просмотреть файл

@ -9,7 +9,7 @@
#include <wintrust.h>
#include "certificatecheck.h"
#include "updatelogging.h"
#include "updatecommon.h"
static const int ENCODING = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;

Просмотреть файл

@ -41,7 +41,7 @@
#define MAR_CHANNEL_MISMATCH_ERROR 22
#define VERSION_DOWNGRADE_ERROR 23
// Error codes 24-33 and 49-51 are for the Windows maintenance service.
// Error codes 24-33 and 49-57 are for the Windows maintenance service.
#define SERVICE_UPDATER_COULD_NOT_BE_STARTED 24
#define SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS 25
#define SERVICE_UPDATER_SIGN_ERROR 26
@ -66,10 +66,16 @@
#define DELETE_ERROR_EXPECTED_FILE 47
#define RENAME_ERROR_EXPECTED_FILE 48
// Error codes 24-33 and 49-51 are for the Windows maintenance service.
// Error codes 24-33 and 49-57 are for the Windows maintenance service.
#define SERVICE_COULD_NOT_COPY_UPDATER 49
#define SERVICE_STILL_APPLYING_TERMINATED 50
#define SERVICE_STILL_APPLYING_NO_EXIT_CODE 51
#define SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR 52
#define SERVICE_CALC_REG_PATH_ERROR 53
#define SERVICE_INVALID_APPLYTO_DIR_ERROR 54
#define SERVICE_INVALID_INSTALL_DIR_PATH_ERROR 55
#define SERVICE_INVALID_WORKING_DIR_PATH_ERROR 56
#define SERVICE_INSTALL_DIR_REG_ERROR 57
#define WRITE_ERROR_FILE_COPY 61
#define WRITE_ERROR_DELETE_FILE 62
@ -85,6 +91,8 @@
#define INVALID_APPLYTO_DIR_STAGED_ERROR 72
#define LOCK_ERROR_PATCH_FILE 73
#define INVALID_APPLYTO_DIR_ERROR 74
#define INVALID_INSTALL_DIR_PATH_ERROR 75
#define INVALID_WORKING_DIR_PATH_ERROR 76
// Error codes 80 through 99 are reserved for nsUpdateService.js

Просмотреть файл

@ -6,8 +6,8 @@
EXPORTS += [
'readstrings.h',
'updatecommon.h',
'updatedefines.h',
'updatelogging.h',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':

Просмотреть файл

@ -8,7 +8,7 @@
#include "registrycertificates.h"
#include "pathhash.h"
#include "updatelogging.h"
#include "updatecommon.h"
#include "updatehelper.h"
#define MAX_KEY_LENGTH 255

Просмотреть файл

@ -22,7 +22,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
sources += [
'readstrings.cpp',
'updatelogging.cpp',
'updatecommon.cpp',
]
SOURCES += sorted(['%s/%s' % (srcdir, s) for s in sources])

Просмотреть файл

@ -5,7 +5,7 @@
#include <windows.h>
#include <wtsapi32.h>
#include "uachelper.h"
#include "updatelogging.h"
#include "updatecommon.h"
// See the MSDN documentation with title: Privilege Constants
// At the time of this writing, this documentation is located at:

Просмотреть файл

@ -12,7 +12,7 @@
#include <stdlib.h>
#include <stdarg.h>
#include "updatelogging.h"
#include "updatecommon.h"
UpdateLog::UpdateLog() : logFP(nullptr)
{
@ -34,12 +34,13 @@ void UpdateLog::Init(NS_tchar* sourcePath,
(dstFilePathLen <
static_cast<int>(sizeof(mDstFilePath)/sizeof(mDstFilePath[0])))) {
#ifdef XP_WIN
GetTempFileNameW(sourcePath, L"log", 0, mTmpFilePath);
logFP = NS_tfopen(mTmpFilePath, NS_T("w"));
if (GetTempFileNameW(sourcePath, L"log", 0, mTmpFilePath) != 0) {
logFP = NS_tfopen(mTmpFilePath, NS_T("w"));
// Delete this file now so it is possible to tell from the unelevated
// updater process if the elevated updater process has written the log.
DeleteFileW(mDstFilePath);
// Delete this file now so it is possible to tell from the unelevated
// updater process if the elevated updater process has written the log.
DeleteFileW(mDstFilePath);
}
#elif XP_MACOSX
logFP = NS_tfopen(mDstFilePath, NS_T("w"));
#else
@ -145,3 +146,68 @@ void UpdateLog::WarnPrintf(const char *fmt, ... )
fprintf(logFP, "***\n");
va_end(ap);
}
/**
* 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;
}
}
#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 ||
NS_tstrstr(origFullPath, NS_T("./")) != nullptr) {
return false;
}
#endif
return true;
}

Просмотреть файл

@ -2,8 +2,8 @@
* 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/. */
#ifndef UPDATELOGGING_H
#define UPDATELOGGING_H
#ifndef UPDATECOMMON_H
#define UPDATECOMMON_H
#include "updatedefines.h"
#include <stdio.h>
@ -35,6 +35,8 @@ protected:
NS_tchar mDstFilePath[MAXPATHLEN];
};
bool IsValidFullPath(NS_tchar* fullPath);
#define LOG_WARN(args) UpdateLog::GetPrimaryLog().WarnPrintf args
#define LOG(args) UpdateLog::GetPrimaryLog().Printf args
#define LogInit(PATHNAME_, FILENAME_) \

Просмотреть файл

@ -273,7 +273,9 @@ WriteStatusFailure(LPCWSTR updateDirPath, int errorCode)
// The temp file is not removed on failure since there is client code that
// will remove it.
WCHAR tmpUpdateStatusFilePath[MAX_PATH + 1] = { L'\0' };
GetTempFileNameW(updateDirPath, L"svc", 0, tmpUpdateStatusFilePath);
if (GetTempFileNameW(updateDirPath, L"svc", 0, tmpUpdateStatusFilePath) == 0) {
return FALSE;
}
HANDLE tmpStatusFile = CreateFileW(tmpUpdateStatusFilePath, GENERIC_WRITE, 0,
nullptr, CREATE_ALWAYS, 0, nullptr);

Просмотреть файл

@ -79,6 +79,13 @@ LaunchMacPostProcess(const char* aAppBundle)
return;
}
// The path must not traverse directories and it must be a relative path.
if ([exeRelPath rangeOfString:@".."].location != NSNotFound ||
[exeRelPath rangeOfString:@"./"].location != NSNotFound ||
[exeRelPath rangeOfString:@"/"].location == 0) {
return;
}
NSString* exeFullPath = [NSString stringWithUTF8String:aAppBundle];
exeFullPath = [exeFullPath stringByAppendingPathComponent:exeRelPath];

Просмотреть файл

@ -52,7 +52,7 @@
#include <errno.h>
#include <algorithm>
#include "updatelogging.h"
#include "updatecommon.h"
#ifdef XP_MACOSX
#include "updaterfileutils_osx.h"
#endif // XP_MACOSX
@ -281,7 +281,7 @@ private:
//-----------------------------------------------------------------------------
static NS_tchar* gPatchDirPath;
static NS_tchar gPatchDirPath[MAXPATHLEN];
static NS_tchar gInstallDirPath[MAXPATHLEN];
static NS_tchar gWorkingDirPath[MAXPATHLEN];
static ArchiveReader gArchiveReader;
@ -1972,8 +1972,20 @@ LaunchWinPostProcess(const WCHAR *installationDir,
return false;
}
// Verify that exeFile doesn't contain relative paths
if (wcsstr(exefile, L"..") != nullptr) {
// The relative path must not contain directory traversals, current directory,
// or colons.
if (wcsstr(exefile, L"..") != nullptr ||
wcsstr(exefile, L"./") != nullptr ||
wcsstr(exefile, L".\\") != nullptr ||
wcsstr(exefile, L":") != nullptr) {
return false;
}
// The relative path must not start with a decimal point, backslash, or
// forward slash.
if (exefile[0] == L'.' ||
exefile[0] == L'\\' ||
exefile[0] == L'/') {
return false;
}
@ -1983,6 +1995,10 @@ LaunchWinPostProcess(const WCHAR *installationDir,
return false;
}
if (!IsValidFullPath(exefullpath)) {
return false;
}
#if !defined(TEST_UPDATER) && defined(MOZ_MAINTENANCE_SERVICE)
if (sUsingService &&
!DoesBinaryMatchAllowedCertificates(installationDir, exefullpath)) {
@ -2089,7 +2105,9 @@ WriteStatusFile(const char* aStatus)
#if defined(XP_WIN)
// The temp file is not removed on failure since there is client code that
// will remove it.
GetTempFileNameW(gPatchDirPath, L"sta", 0, filename);
if (GetTempFileNameW(gPatchDirPath, L"sta", 0, filename) == 0) {
return false;
}
#else
NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
NS_T("%s/update.status"), gPatchDirPath);
@ -2720,9 +2738,38 @@ int NS_main(int argc, NS_tchar **argv)
return 1;
}
// This check is also performed in workmonitor.cpp since the maintenance
// service can be called directly.
if (!IsValidFullPath(argv[1])) {
// Since the status file is written to the patch directory and the patch
// directory is invalid don't write the status file.
fprintf(stderr, "The patch directory path is not valid for this " \
"application (" LOG_S ")\n", argv[1]);
#ifdef XP_MACOSX
if (isElevated) {
freeArguments(argc, argv);
CleanupElevatedMacUpdate(true);
}
#endif
return 1;
}
// The directory containing the update information.
gPatchDirPath = argv[1];
NS_tstrncpy(gPatchDirPath, argv[1], MAXPATHLEN);
// This check is also performed in workmonitor.cpp since the maintenance
// service can be called directly.
if (!IsValidFullPath(argv[2])) {
WriteStatusFile(INVALID_INSTALL_DIR_PATH_ERROR);
fprintf(stderr, "The install directory path is not valid for this " \
"application (" LOG_S ")\n", argv[2]);
#ifdef XP_MACOSX
if (isElevated) {
freeArguments(argc, argv);
CleanupElevatedMacUpdate(true);
}
#endif
return 1;
}
// The directory we're going to update to.
// We copy this string because we need to remove trailing slashes. The C++
// standard says that it's always safe to write to strings pointed to by argv
@ -2795,6 +2842,20 @@ int NS_main(int argc, NS_tchar **argv)
}
}
// This check is also performed in workmonitor.cpp since the maintenance
// service can be called directly.
if (!IsValidFullPath(argv[3])) {
WriteStatusFile(INVALID_WORKING_DIR_PATH_ERROR);
fprintf(stderr, "The working directory path is not valid for this " \
"application (" LOG_S ")\n", argv[3]);
#ifdef XP_MACOSX
if (isElevated) {
freeArguments(argc, argv);
CleanupElevatedMacUpdate(true);
}
#endif
return 1;
}
// The directory we're going to update to.
// We copy this string because we need to remove trailing slashes. The C++
// standard says that it's always safe to write to strings pointed to by argv
@ -2851,6 +2912,8 @@ int NS_main(int argc, NS_tchar **argv)
LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath));
#if defined(XP_WIN)
// These checks are also performed in workmonitor.cpp since the maintenance
// service can be called directly.
if (_wcsnicmp(gWorkingDirPath, gInstallDirPath, MAX_PATH) != 0) {
if (!sStagedUpdate && !sReplaceRequest) {
WriteStatusFile(INVALID_APPLYTO_DIR_ERROR);