Commit f5112cb4 authored by Sorin Jianu's avatar Sorin Jianu Committed by Commit Bot

Create a Windows installer for the Chrome Updater.

The required updater files are archived as an uncompressed LZMA archive
(similar to a tar file), which then is compressed using LZMA, and the
result of the compression is inserted as a binary resource into the
updater installer file.

updater.7z -> updater.packed.7z -> UpdaterSetup.exe

At the moment, the updater files are: updater.exe and uninstall.cmd.
However, in the debug component build, some other run time dependencies
are needed. The dependencies are collected at build time and archived
with the updater files.

The vast majority of this code has been lifted from
//chrome/installer/mini_installer. It is possible to further make the
code a shared library between the installer and updater projects.
This would be work for the future.


Bug: 989772
Change-Id: I9b84e8d1f4475b9977d5b7fd78f37272dd7c6212
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1730309
Commit-Queue: Sorin Jianu <sorin@chromium.org>
Reviewed-by: default avatarJoshua Pawlicki <waffles@chromium.org>
Cr-Commit-Position: refs/heads/master@{#684210}
parent 5d40ba71
......@@ -5,9 +5,11 @@
import("//chrome/process_version_rc_template.gni")
import("//testing/test.gni")
# This target build the updater executable and its installer.
group("win") {
deps = [
":updater",
"//chrome/updater/win/installer:installer",
]
}
......@@ -26,6 +28,10 @@ executable("updater") {
"//build/win:default_exe_manifest",
"//chrome/updater:common",
]
data_deps = [
":uninstall.cmd",
]
}
copy("uninstall.cmd") {
......@@ -67,14 +73,6 @@ source_set("code") {
"//chrome/installer/util:with_no_strings",
"//components/update_client",
]
data = [
"setup/uninstall.cmd",
]
data_deps = [
":uninstall.cmd",
]
}
source_set("unittest") {
......@@ -90,4 +88,8 @@ source_set("unittest") {
"//base/test:test_support",
"//testing/gtest",
]
data_deps = [
"//chrome/updater/win/installer:installer_unittest",
]
}
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//chrome/process_version_rc_template.gni")
import("//testing/test.gni")
source_set("lib") {
sources = [
"configuration.cc",
"configuration.h",
"exit_code.h",
"installer.cc",
"installer.h",
"installer.rc",
"installer_constants.cc",
"installer_constants.h",
"installer_resource.h",
"pe_resource.cc",
"pe_resource.h",
"regkey.cc",
"regkey.h",
"string.cc",
"string.h",
]
}
process_version_rc_template("version") {
template_file = "installer_version.rc.version"
output = "$root_out_dir/installer_version.rc"
}
# This target creats a list of runtime dependencies for the component
# builds. This list is parsed by the |create_installer_archive| script, the
# DLL paths extracted out from the list, and included in the archive.
updater_runtime_deps = "$root_gen_dir/updater.runtime_deps"
group("updater_runtime_deps") {
write_runtime_deps = updater_runtime_deps
data_deps = [
"//chrome/updater/win:updater",
]
}
template("generate_installer") {
output_dir = invoker.out_dir
packed_files_rc_file = "$target_gen_dir/$target_name/packed_files.rc"
archive_name = target_name + "_archive"
staging_dir = "$target_gen_dir/$target_name"
action(archive_name) {
script = "create_installer_archive.py"
release_file = "updater.release"
inputs = [
release_file,
]
outputs = [
"$output_dir/updater.packed.7z",
packed_files_rc_file,
]
args = [
"--build_dir",
rebase_path(root_out_dir, root_build_dir),
"--staging_dir",
rebase_path(staging_dir, root_build_dir),
"--input_file",
rebase_path(release_file, root_build_dir),
"--resource_file_path",
rebase_path(packed_files_rc_file, root_build_dir),
"--output_dir",
rebase_path(output_dir, root_build_dir),
"--setup_runtime_deps",
rebase_path(updater_runtime_deps, root_build_dir),
"--output_name=updater",
"--verbose",
]
deps = [
":updater_runtime_deps",
"//chrome/updater/win:updater",
]
if (is_component_build) {
args += [ "--component_build=1" ]
}
}
executable(target_name) {
output_name = invoker.output_name
sources = [
"installer_main.cc",
packed_files_rc_file,
]
configs += [ "//build/config/win:windowed" ]
libs = [ "setupapi.lib" ]
deps = [
":$archive_name",
":lib",
":version",
"//build/win:default_exe_manifest",
"//chrome/installer/util:with_no_strings",
]
}
}
generate_installer("installer") {
out_dir = root_out_dir
output_name = "UpdaterSetup"
}
test("installer_unittest") {
testonly = true
output_name = "updater_installer_unittest"
sources = [
"configuration_unittest.cc",
"run_all_unittests.cc",
"string_unittest.cc",
]
public_deps = [
":lib",
]
deps = [
"//base",
"//base/test:test_support",
"//chrome/installer/util:with_no_strings",
"//testing/gtest",
]
}
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/updater/win/installer/configuration.h"
#include <shellapi.h>
#include "chrome/updater/win/installer/string.h"
namespace updater {
namespace {
// Returns true if GoogleUpdateIsMachine=1 is present in the environment.
bool GetGoogleUpdateIsMachineEnvVar() {
constexpr DWORD kBufferSize = 2;
StackString<kBufferSize> value;
const auto length = ::GetEnvironmentVariableW(L"GoogleUpdateIsMachine",
value.get(), kBufferSize);
return length == 1 && *value.get() == L'1';
}
} // namespace
Configuration::Configuration() {
Clear();
}
Configuration::~Configuration() {
Clear();
}
bool Configuration::Initialize(HMODULE module) {
Clear();
return ParseCommandLine(::GetCommandLine());
}
void Configuration::Clear() {
if (args_ != nullptr) {
::LocalFree(args_);
args_ = nullptr;
}
command_line_ = nullptr;
operation_ = INSTALL_PRODUCT;
argument_count_ = 0;
is_system_level_ = false;
has_invalid_switch_ = false;
}
// |command_line| is shared with this instance in the sense that this
// instance may refer to it at will throughout its lifetime, yet it will
// not release it.
bool Configuration::ParseCommandLine(const wchar_t* command_line) {
command_line_ = command_line;
args_ = ::CommandLineToArgvW(command_line_, &argument_count_);
if (!args_)
return false;
for (int i = 1; i < argument_count_; ++i) {
if (0 == ::lstrcmpi(args_[i], L"--system-level"))
is_system_level_ = true;
else if (0 == ::lstrcmpi(args_[i], L"--cleanup"))
operation_ = CLEANUP;
}
if (!is_system_level_)
is_system_level_ = GetGoogleUpdateIsMachineEnvVar();
return true;
}
} // namespace updater
// Copyright (c) 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_UPDATER_WIN_INSTALLER_CONFIGURATION_H_
#define CHROME_UPDATER_WIN_INSTALLER_CONFIGURATION_H_
#include <windows.h>
namespace updater {
// A simple container of the updater's configuration, as defined by the
// command line used to invoke it.
class Configuration {
public:
enum Operation {
INSTALL_PRODUCT,
CLEANUP,
};
Configuration();
~Configuration();
// Initializes this instance on the basis of the process's command line.
bool Initialize(HMODULE module);
// Returns the desired operation dictated by the command line options.
Operation operation() const { return operation_; }
// Returns true if --system-level is on the command line or if
// GoogleUpdateIsMachine=1 is set in the process's environment.
bool is_system_level() const { return is_system_level_; }
// Returns true if any invalid switch is found on the command line.
bool has_invalid_switch() const { return has_invalid_switch_; }
protected:
void Clear();
bool ParseCommandLine(const wchar_t* command_line);
wchar_t** args_ = nullptr;
const wchar_t* command_line_ = nullptr;
int argument_count_ = 0;
Operation operation_ = INSTALL_PRODUCT;
bool is_system_level_ = false;
bool has_invalid_switch_ = false;
private:
Configuration(const Configuration&) = delete;
Configuration& operator=(const Configuration&) = delete;
};
} // namespace updater
#endif // CHROME_UPDATER_WIN_INSTALLER_CONFIGURATION_H_
// Copyright (c) 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/updater/win/installer/configuration.h"
#include <stddef.h>
#include <stdlib.h>
#include <memory>
#include "base/environment.h"
#include "base/macros.h"
#include "base/test/test_reg_util_win.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace updater {
namespace {
// A helper class to set the "GoogleUpdateIsMachine" environment variable.
class ScopedGoogleUpdateIsMachine {
public:
explicit ScopedGoogleUpdateIsMachine(bool value)
: env_(base::Environment::Create()) {
env_->SetVar("GoogleUpdateIsMachine", value ? "1" : "0");
}
~ScopedGoogleUpdateIsMachine() { env_->UnSetVar("GoogleUpdateIsMachine"); }
private:
std::unique_ptr<base::Environment> env_;
};
class TestConfiguration : public Configuration {
public:
explicit TestConfiguration(const wchar_t* command_line) {
EXPECT_TRUE(ParseCommandLine(command_line));
}
private:
DISALLOW_COPY_AND_ASSIGN(TestConfiguration);
};
} // namespace
class UpdaterInstallerConfigurationTest : public ::testing::Test {
protected:
UpdaterInstallerConfigurationTest() = default;
private:
DISALLOW_COPY_AND_ASSIGN(UpdaterInstallerConfigurationTest);
};
// Test that the operation type is CLEANUP iff --cleanup is on the cmdline.
TEST_F(UpdaterInstallerConfigurationTest, Operation) {
EXPECT_EQ(Configuration::INSTALL_PRODUCT,
TestConfiguration(L"spam.exe").operation());
EXPECT_EQ(Configuration::INSTALL_PRODUCT,
TestConfiguration(L"spam.exe --clean").operation());
EXPECT_EQ(Configuration::INSTALL_PRODUCT,
TestConfiguration(L"spam.exe --cleanupthis").operation());
EXPECT_EQ(Configuration::CLEANUP,
TestConfiguration(L"spam.exe --cleanup").operation());
EXPECT_EQ(Configuration::CLEANUP,
TestConfiguration(L"spam.exe --cleanup now").operation());
}
TEST_F(UpdaterInstallerConfigurationTest, IsSystemLevel) {
EXPECT_FALSE(TestConfiguration(L"spam.exe").is_system_level());
EXPECT_FALSE(TestConfiguration(L"spam.exe --chrome").is_system_level());
EXPECT_TRUE(TestConfiguration(L"spam.exe --system-level").is_system_level());
{
ScopedGoogleUpdateIsMachine env_setter(false);
EXPECT_FALSE(TestConfiguration(L"spam.exe").is_system_level());
}
{
ScopedGoogleUpdateIsMachine env_setter(true);
EXPECT_TRUE(TestConfiguration(L"spam.exe").is_system_level());
}
}
TEST_F(UpdaterInstallerConfigurationTest, HasInvalidSwitch) {
EXPECT_FALSE(TestConfiguration(L"spam.exe").has_invalid_switch());
EXPECT_TRUE(
TestConfiguration(L"spam.exe --chrome-frame").has_invalid_switch());
}
} // namespace updater
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Script to create the Chrome Updater Installer archive.
This script is used to create an archive of all the files required for a
Chrome Updater install in appropriate directory structure. It reads
updater.release file as input, creates updater.7z ucompressed archive, and
generates the updater.packed.7z compressed archive.
"""
import ConfigParser
import glob
import optparse
import os
import shutil
import subprocess
import sys
# Directory name inside the uncompressed archive where all the files are.
UPDATER_DIR = "bin"
# Suffix to uncompressed full archive file, appended to options.output_name.
ARCHIVE_SUFFIX = ".7z"
# compressed full archive suffix, will be prefixed by options.output_name.
COMPRESSED_ARCHIVE_SUFFIX = ".packed.7z"
TEMP_ARCHIVE_DIR = "temp_installer_archive"
g_archive_inputs = []
def CompressUsingLZMA(build_dir, compressed_file, input_file, verbose):
lzma_exec = GetLZMAExec(build_dir)
cmd = [lzma_exec,
'a', '-t7z',
# Flags equivalent to -mx9 (ultra) but with the bcj2 turned on (exe
# pre-filter). These arguments are the similar to what the Chrome mini
# installer is using.
'-m0=BCJ2',
'-m1=LZMA:d27:fb128',
'-m2=LZMA:d22:fb128:mf=bt2',
'-m3=LZMA:d22:fb128:mf=bt2',
'-mb0:1',
'-mb0s1:2',
'-mb0s2:3',
os.path.abspath(compressed_file),
os.path.abspath(input_file),]
if os.path.exists(compressed_file):
os.remove(compressed_file)
RunSystemCommand(cmd, verbose)
def CopyAllFilesToStagingDir(config, staging_dir, build_dir):
"""Copies the files required for installer archive.
"""
CopySectionFilesToStagingDir(config, 'GENERAL', staging_dir, build_dir)
def CopySectionFilesToStagingDir(config, section, staging_dir, src_dir):
"""Copies installer archive files specified in section from src_dir to
staging_dir. This method reads section from config and copies all the
files specified from src_dir to staging dir.
"""
for option in config.options(section):
src_subdir = option.replace('\\', os.sep)
dst_dir = os.path.join(staging_dir, config.get(section, option))
dst_dir = dst_dir.replace('\\', os.sep)
src_paths = glob.glob(os.path.join(src_dir, src_subdir))
if src_paths and not os.path.exists(dst_dir):
os.makedirs(dst_dir)
for src_path in src_paths:
print(src_path)
dst_path = os.path.join(dst_dir, os.path.basename(src_path))
if not os.path.exists(dst_path):
g_archive_inputs.append(src_path)
print('paths src_path={0}, dest_dir={1}'.format(src_path, dst_dir))
shutil.copy(src_path, dst_dir)
def GetLZMAExec(build_dir):
if sys.platform == 'win32':
lzma_exec = os.path.join(build_dir, "..", "..", "third_party",
"lzma_sdk", "Executable", "7za.exe")
else:
lzma_exec = '7zr' # Use system 7zr.
return lzma_exec
def MakeStagingDirectory(staging_dir):
"""Creates a staging path for installer archive. If directory exists already,
deletes the existing directory.
"""
file_path = os.path.join(staging_dir, TEMP_ARCHIVE_DIR)
if os.path.exists(file_path):
shutil.rmtree(file_path)
os.makedirs(file_path)
return file_path
def Readconfig(input_file):
"""Reads config information from input file after setting default value of
global variables.
"""
variables = {}
variables['UpdaterDir'] = UPDATER_DIR
config = ConfigParser.SafeConfigParser(variables)
config.read(input_file)
return config
def RunSystemCommand(cmd, verbose):
"""Runs |cmd|, prints the |cmd| and its output if |verbose|; otherwise
captures its output and only emits it on failure.
"""
if verbose:
print 'Running', cmd
try:
# Run |cmd|, redirecting stderr to stdout in order for captured errors to be
# inline with corresponding stdout.
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
if verbose:
print output
except subprocess.CalledProcessError as e:
raise Exception("Error while running cmd: %s\n"
"Exit code: %s\n"
"Command output:\n%s" %
(e.cmd, e.returncode, e.output))
def CreateArchiveFile(options, staging_dir):
"""Creates a new installer archive file after deleting any existing old file.
"""
# First create an uncompressed archive file for the current build (updater.7z)
lzma_exec = GetLZMAExec(options.build_dir)
archive_file = os.path.join(options.output_dir,
options.output_name + ARCHIVE_SUFFIX)
if options.depfile:
# If a depfile was requested, do the glob of the staging dir and generate
# a list of dependencies in .d format. We list the files that were copied
# into the staging dir, not the files that are actually in the staging dir
# because the ones in the staging dir will never be edited, and we want
# to have the build be triggered when the thing-that-was-copied-there
# changes.
def PathFixup(path):
"""Fixes path for depfile format: backslash to forward slash, and
backslash escaping for spaces."""
return path.replace('\\', '/').replace(' ', '\\ ')
# Gather the list of files in the staging dir that will be zipped up. We
# only gather this list to make sure that g_archive_inputs is complete (i.e.
# that there's not file copies that got missed).
staging_contents = []
for root, files in os.walk(os.path.join(staging_dir, UPDATER_DIR)):
for filename in files:
staging_contents.append(PathFixup(os.path.join(root, filename)))
# Make sure there's an archive_input for each staging dir file.
for staging_file in staging_contents:
for archive_input in g_archive_inputs:
archive_rel = PathFixup(archive_input)
if (os.path.basename(staging_file).lower() ==
os.path.basename(archive_rel).lower()):
break
else:
raise Exception('Did not find an archive input file for "%s"' %
staging_file)
# Finally, write the depfile referencing the inputs.
with open(options.depfile, 'wb') as f:
f.write(PathFixup(os.path.relpath(archive_file, options.build_dir)) +
': \\\n')
f.write(' ' + ' \\\n '.join(PathFixup(x) for x in g_archive_inputs))
# It is important to use abspath to create the path to the directory because
# if you use a relative path without any .. sequences then 7za.exe uses the
# entire relative path as part of the file paths in the archive. If you have
# a .. sequence or an absolute path then only the last directory is stored as
# part of the file paths in the archive, which is what we want.
cmd = [lzma_exec,
'a',
'-t7z',
archive_file,
os.path.abspath(os.path.join(staging_dir, UPDATER_DIR)),
'-mx0',]
# There does not seem to be any way in 7za.exe to override existing file so
# we always delete before creating a new one.
if not os.path.exists(archive_file):
RunSystemCommand(cmd, options.verbose)
elif options.skip_rebuild_archive != "true":
os.remove(archive_file)
RunSystemCommand(cmd, options.verbose)
# Do not compress the archive when skip_archive_compression is specified.
if options.skip_archive_compression:
compressed_file = os.path.join(
options.output_dir, options.output_name + COMPRESSED_ARCHIVE_SUFFIX)
if os.path.exists(compressed_file):
os.remove(compressed_file)
return os.path.basename(archive_file)
compressed_archive_file = options.output_name + COMPRESSED_ARCHIVE_SUFFIX
compressed_archive_file_path = os.path.join(options.output_dir,
compressed_archive_file)
CompressUsingLZMA(options.build_dir, compressed_archive_file_path,
archive_file, options.verbose)
return compressed_archive_file
_RESOURCE_FILE_HEADER = """\
// This file is automatically generated by create_installer_archive.py.
// It contains the resource entries that are going to be linked inside the exe.
// For each file to be linked there should be two lines:
// - The first line contains the output filename (without path) and the
// type of the resource ('BN' - not compressed , 'BL' - LZ compressed,
// 'B7' - LZMA compressed)
// - The second line contains the path to the input file. Uses '/' to
// separate path components.
"""
def CreateResourceInputFile(
output_dir, archive_file, resource_file_path,
component_build, staging_dir):
"""Creates resource input file for installer target."""
# An array of (file, type, path) tuples of the files to be included.
resources = [(archive_file, 'B7',
os.path.join(output_dir, archive_file))]
with open(resource_file_path, 'w') as f:
f.write(_RESOURCE_FILE_HEADER)
for (file, type, path) in resources:
f.write('\n%s %s\n "%s"\n' % (file, type, path.replace("\\","/")))
def ParseDLLsFromDeps(build_dir, runtime_deps_file):
"""Parses the runtime_deps file and returns the set of DLLs in it, relative
to build_dir."""
build_dlls = set()
args = open(runtime_deps_file).read()
for l in args.splitlines():
if os.path.splitext(l)[1] == ".dll":
build_dlls.add(os.path.join(build_dir, l))
return build_dlls
# Copies component build DLLs for the setup to be able to find those DLLs at
# run-time.
# This is meant for developer builds only and should never be used to package
# an official build.
def DoComponentBuildTasks(staging_dir, build_dir, setup_runtime_deps):
installer_dir = os.path.join(staging_dir, UPDATER_DIR)
if not os.path.exists(installer_dir):
os.mkdir(installer_dir)
setup_component_dlls = ParseDLLsFromDeps(build_dir, setup_runtime_deps)
for setup_component_dll in setup_component_dlls:
g_archive_inputs.append(setup_component_dll)
shutil.copy(setup_component_dll, installer_dir)
def main(options):
"""Main method that reads input file, creates archive file and writes
resource input file.
"""
config = Readconfig(options.input_file)
staging_dir = MakeStagingDirectory(options.staging_dir)
# Copy the files from the build dir.
CopyAllFilesToStagingDir(config, staging_dir, options.build_dir)
if options.component_build == '1':
DoComponentBuildTasks(staging_dir, options.build_dir,
options.setup_runtime_deps)
# Name of the archive file built (for example - updater.7z)
archive_file = CreateArchiveFile(options, staging_dir)
CreateResourceInputFile(options.output_dir,
archive_file, options.resource_file_path,
options.component_build == '1', staging_dir)
def _ParseOptions():
parser = optparse.OptionParser()
parser.add_option('-i', '--input_file',
help='Input file describing which files to archive.')
parser.add_option('-b', '--build_dir',
help='Build directory. The paths in input_file are relative to this.')
parser.add_option('--staging_dir',
help='Staging directory where intermediate files and directories '
'will be created')
parser.add_option('-o', '--output_dir',
help='The output directory where the archives will be written. '
'Defaults to the build_dir.')
parser.add_option('--resource_file_path',
help='The path where the resource file will be output. ')
parser.add_option('-s', '--skip_rebuild_archive',
default="False", help='Skip re-building updater.7z archive if it exists.')
parser.add_option('-n', '--output_name', default='updater',
help='Name used to prefix names of generated archives.')
parser.add_option('--component_build', default='0',
help='Whether this archive is packaging a component build.')
parser.add_option('--skip_archive_compression',
action='store_true', default=False,
help='Turn off compression of updater.7z into updater.packed.7z and '
'helpfully delete any old updater.packed.7z in |output_dir|.')
parser.add_option('--depfile',
help='Generate a depfile with the given name listing the implicit inputs '
'to the archive process that can be used with a build system.')
parser.add_option('--setup_runtime_deps',
help='A file listing runtime dependencies for setup.exe. This will be '
'used to get a list of DLLs to archive in a component build.')
parser.add_option('-v', '--verbose', action='store_true', dest='verbose',
default=False)
options, _ = parser.parse_args()
if not options.build_dir:
parser.error('You must provide a build dir.')
options.build_dir = os.path.normpath(options.build_dir)
if not options.staging_dir:
parser.error('You must provide a staging dir.')
if not options.input_file:
parser.error('You must provide an input file')
is_component_build = options.component_build == '1'
if is_component_build and not options.setup_runtime_deps:
parser.error("updater_runtime_deps must be specified for a component build")
if not options.output_dir:
options.output_dir = options.build_dir
return options
if '__main__' == __name__:
options = _ParseOptions()
if options.verbose:
print sys.argv
sys.exit(main(options))
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_UPDATER_WIN_INSTALLER_EXIT_CODE_H_
#define CHROME_UPDATER_WIN_INSTALLER_EXIT_CODE_H_
namespace updater {
// Installer process exit codes (the underlying type is uint32_t).
enum ExitCode {
SUCCESS_EXIT_CODE = 0,
GENERIC_INITIALIZATION_FAILURE = 101,
COMMAND_STRING_OVERFLOW = 105,
WAIT_FOR_PROCESS_FAILED = 107,
PATH_STRING_OVERFLOW = 108,
UNABLE_TO_GET_WORK_DIRECTORY = 109,
UNABLE_TO_EXTRACT_ARCHIVE = 112,
UNABLE_TO_SET_DIRECTORY_ACL = 117,
INVALID_OPTION = 118,
RUN_SETUP_FAILED_FILE_NOT_FOUND = 122, // ERROR_FILE_NOT_FOUND.
RUN_SETUP_FAILED_PATH_NOT_FOUND = 123, // ERROR_PATH_NOT_FOUND.
RUN_SETUP_FAILED_COULD_NOT_CREATE_PROCESS = 124, // All other errors.
};
} // namespace updater
#endif // CHROME_UPDATER_WIN_INSTALLER_EXIT_CODE_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GoogleUpdateSetup.exe is the first exe that is run when chrome is being
// installed. It has two main jobs:
// 1) unpack the resources (possibly decompressing some)
// 2) run the real installer (updater.exe) with appropriate flags (--install).
//
// All files needed by the updater are archived together as an uncompressed
// LZMA file, which is further compressed as one file, and inserted as a
// binary resource in the resource section of the setup program.
#include "chrome/updater/win/installer/installer.h"
// #define needed to link in RtlGenRandom(), a.k.a. SystemFunction036. See the
// "Community Additions" comment on MSDN here:
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx
#define SystemFunction036 NTAPI SystemFunction036
#include <NTSecAPI.h>
#undef SystemFunction036
#include <sddl.h>
#include <shellapi.h>
#include <stddef.h>
#include <stdlib.h>
#include <initializer_list>
// TODO(sorin): remove the dependecies on //base/ to reduce the code size.
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "chrome/installer/util/lzma_util.h"
#include "chrome/installer/util/self_cleaning_temp_dir.h"
#include "chrome/installer/util/util_constants.h"
#include "chrome/updater/win/installer/configuration.h"
#include "chrome/updater/win/installer/installer_constants.h"
#include "chrome/updater/win/installer/pe_resource.h"
#include "chrome/updater/win/installer/regkey.h"
namespace updater {
namespace {
// Initializes |temp_path| to "Temp" within the target directory, and
// |unpack_path| to a random directory beginning with "source" within
// |temp_path|. Returns false on error.
bool CreateTemporaryAndUnpackDirectories(
installer::SelfCleaningTempDir* temp_path,
base::FilePath* unpack_path) {
DCHECK(temp_path && unpack_path);
base::FilePath temp_dir;
if (!base::PathService::Get(base::DIR_TEMP, &temp_dir))
return false;
if (!temp_path->Initialize(temp_dir, kTempPrefix)) {
PLOG(ERROR) << "Could not create temporary path.";
return false;
}
VLOG(1) << "Created path " << temp_path->path().value();
if (!base::CreateTemporaryDirInDir(temp_path->path(), L"source",
unpack_path)) {
PLOG(ERROR) << "Could not create temporary path for unpacked archive.";
return false;
}
return true;
}
} // namespace
using PathString = StackString<MAX_PATH>;
// This structure passes data back and forth for the processing
// of resource callbacks.
struct Context {
// Input to the call back method. Specifies the dir to save resources into.
const wchar_t* base_path = nullptr;
// First output from call back method. Specifies the path of resource archive.
PathString* updater_resource_path = nullptr;
};
// Calls CreateProcess with good default parameters and waits for the process to
// terminate returning the process exit code. In case of CreateProcess failure,
// returns a results object with the provided codes as follows:
// - ERROR_FILE_NOT_FOUND: (file_not_found_code, attributes of setup.exe).
// - ERROR_PATH_NOT_FOUND: (path_not_found_code, attributes of setup.exe).
// - Otherwise: (generic_failure_code, CreateProcess error code).
// In case of error waiting for the process to exit, returns a results object
// with (WAIT_FOR_PROCESS_FAILED, last error code). Otherwise, returns a results
// object with the subprocess's exit code.
ProcessExitResult RunProcessAndWait(const wchar_t* exe_path, wchar_t* cmdline) {
STARTUPINFOW si = {sizeof(si)};
PROCESS_INFORMATION pi = {0};
if (!::CreateProcess(exe_path, cmdline, nullptr, nullptr, FALSE,
CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi)) {
// Split specific failure modes. If the process couldn't be launched because
// its file/path couldn't be found, report its attributes in ExtraCode1.
// This will help diagnose the prevalence of launch failures due to Image
// File Execution Options tampering. See https://crbug.com/672813 for more
// details.
const DWORD last_error = ::GetLastError();
const DWORD attributes = ::GetFileAttributes(exe_path);
switch (last_error) {
case ERROR_FILE_NOT_FOUND:
return ProcessExitResult(RUN_SETUP_FAILED_FILE_NOT_FOUND, attributes);
case ERROR_PATH_NOT_FOUND:
return ProcessExitResult(RUN_SETUP_FAILED_PATH_NOT_FOUND, attributes);
default:
break;
}
// Lump all other errors into a distinct failure bucket.
return ProcessExitResult(RUN_SETUP_FAILED_COULD_NOT_CREATE_PROCESS,
last_error);
}
::CloseHandle(pi.hThread);
DWORD exit_code = SUCCESS_EXIT_CODE;
DWORD wr = ::WaitForSingleObject(pi.hProcess, INFINITE);
if (WAIT_OBJECT_0 != wr || !::GetExitCodeProcess(pi.hProcess, &exit_code)) {
// Note: We've assumed that WAIT_OBJCT_0 != wr means a failure. The call
// could return a different object but since we never spawn more than one
// sub-process at a time that case should never happen.
return ProcessExitResult(WAIT_FOR_PROCESS_FAILED, ::GetLastError());
}
::CloseHandle(pi.hProcess);
return ProcessExitResult(exit_code);
}
// Windows defined callback used in the EnumResourceNames call. For each
// matching resource found, the callback is invoked and at this point we write
// it to disk. We expect resource names to start with the 'updater' prefix.
// Any other name is treated as an error.
BOOL CALLBACK OnResourceFound(HMODULE module,
const wchar_t* type,
wchar_t* name,
LONG_PTR context) {
Context* ctx = reinterpret_cast<Context*>(context);
if (!ctx)
return FALSE;
if (!StrStartsWith(name, kUpdaterArchivePrefix))
return FALSE;
PEResource resource(name, type, module);
if (!resource.IsValid() || resource.Size() < 1)
return FALSE;
PathString full_path;
if (!full_path.assign(ctx->base_path) || !full_path.append(name) ||
!resource.WriteToDisk(full_path.get())) {
return FALSE;
}
if (!ctx->updater_resource_path->assign(full_path.get()))
return FALSE;
return TRUE;
}
// Finds and writes to disk resources of type 'B7' (7zip archive). Returns false
// if there is a problem in writing any resource to disk.
ProcessExitResult UnpackBinaryResources(const Configuration& configuration,
HMODULE module,
const wchar_t* base_path,
PathString* archive_path) {
// Prepare the input to OnResourceFound method that needs a location where
// it will write all the resources.
Context context = {base_path, archive_path};
// Get the resources of type 'B7' (7zip archive).
if (!::EnumResourceNames(module, kLZMAResourceType, OnResourceFound,
reinterpret_cast<LONG_PTR>(&context))) {
return ProcessExitResult(UNABLE_TO_EXTRACT_ARCHIVE, ::GetLastError());
}
if (archive_path->length() == 0)
return ProcessExitResult(UNABLE_TO_EXTRACT_ARCHIVE);
ProcessExitResult exit_code = ProcessExitResult(SUCCESS_EXIT_CODE);
return exit_code;
}
// Executes updater.exe, waits for it to finish and returns the exit code.
ProcessExitResult RunSetup(const Configuration& configuration,
const wchar_t* setup_path) {
PathString setup_exe;
if (*setup_path != L'\0') {
if (!setup_exe.assign(setup_path))
return ProcessExitResult(COMMAND_STRING_OVERFLOW);
}
CommandString cmd_line;
// Put the quoted path to setup.exe in cmd_line first.
if (!cmd_line.assign(L"\"") || !cmd_line.append(setup_exe.get()) ||
!cmd_line.append(L"\"")) {
return ProcessExitResult(COMMAND_STRING_OVERFLOW);
}
if (!cmd_line.append(L" --install --enable-logging --v=1"))
return ProcessExitResult(COMMAND_STRING_OVERFLOW);
return RunProcessAndWait(setup_exe.get(), cmd_line.get());
}
// Returns true if the supplied path supports ACLs.
bool IsAclSupportedForPath(const wchar_t* path) {
PathString volume;
DWORD flags = 0;
return ::GetVolumePathName(path, volume.get(),
static_cast<DWORD>(volume.capacity())) &&
::GetVolumeInformation(volume.get(), nullptr, 0, nullptr, nullptr,
&flags, nullptr, 0) &&
(flags & FILE_PERSISTENT_ACLS);
}
// Retrieves the SID of the default owner for objects created by this user
// token (accounting for different behavior under UAC elevation, etc.).
// NOTE: On success the |sid| parameter must be freed with LocalFree().
bool GetCurrentOwnerSid(wchar_t** sid) {
HANDLE token;
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token))
return false;
DWORD size = 0;
bool result = false;
// We get the TokenOwner rather than the TokenUser because e.g. under UAC
// elevation we want the admin to own the directory rather than the user.
::GetTokenInformation(token, TokenOwner, nullptr, 0, &size);
if (size && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
if (TOKEN_OWNER* owner =
reinterpret_cast<TOKEN_OWNER*>(::LocalAlloc(LPTR, size))) {
if (::GetTokenInformation(token, TokenOwner, owner, size, &size))
result = !!::ConvertSidToStringSid(owner->Owner, sid);
::LocalFree(owner);
}
}
::CloseHandle(token);
return result;
}
// Populates |sd| suitable for use when creating directories within |path| with
// ACLs allowing access to only the current owner, admin, and system.
// NOTE: On success the |sd| parameter must be freed with LocalFree().
bool SetSecurityDescriptor(const wchar_t* path, PSECURITY_DESCRIPTOR* sd) {
*sd = nullptr;
// We succeed without doing anything if ACLs aren't supported.
if (!IsAclSupportedForPath(path))
return true;
wchar_t* sid = nullptr;
if (!GetCurrentOwnerSid(&sid))
return false;
// The largest SID is under 200 characters, so 300 should give enough slack.
StackString<300> sddl;
bool result = sddl.append(
L"D:PAI" // Protected, auto-inherited DACL.
L"(A;;FA;;;BA)" // Admin: Full control.
L"(A;OIIOCI;GA;;;BA)"
L"(A;;FA;;;SY)" // System: Full control.
L"(A;OIIOCI;GA;;;SY)"
L"(A;OIIOCI;GA;;;CO)" // Owner: Full control.
L"(A;;FA;;;") &&
sddl.append(sid) && sddl.append(L")");
if (result) {
result = !!::ConvertStringSecurityDescriptorToSecurityDescriptor(
sddl.get(), SDDL_REVISION_1, sd, nullptr);
}
::LocalFree(sid);
return result;
}
// Creates a temporary directory under |base_path| and returns the full path
// of created directory in |work_dir|. If successful return true, otherwise
// false. When successful, the returned |work_dir| will always have a trailing
// backslash and this function requires that |base_path| always includes a
// trailing backslash as well.
// We do not use GetTempFileName here to avoid running into AV software that
// might hold on to the temp file as soon as we create it and then we can't
// delete it and create a directory in its place. So, we use our own mechanism
// for creating a directory with a hopefully-unique name. In the case of a
// collision, we retry a few times with a new name before failing.
bool CreateWorkDir(const wchar_t* base_path,
PathString* work_dir,
ProcessExitResult* exit_code) {
*exit_code = ProcessExitResult(PATH_STRING_OVERFLOW);
if (!work_dir->assign(base_path) || !work_dir->append(kTempPrefix))
return false;
// Store the location where we'll append the id.
size_t end = work_dir->length();
// Check if we'll have enough buffer space to continue.
// The name of the directory will use up 11 chars and then we need to append
// the trailing backslash and a terminator. We've already added the prefix
// to the buffer, so let's just make sure we've got enough space for the rest.
if ((work_dir->capacity() - end) < (_countof("fffff.tmp") + 1))
return false;
// Add an ACL if supported by the filesystem. Otherwise system-level installs
// are potentially vulnerable to file squatting attacks.
SECURITY_ATTRIBUTES sa = {};
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
if (!SetSecurityDescriptor(base_path, &sa.lpSecurityDescriptor)) {
*exit_code =
ProcessExitResult(UNABLE_TO_SET_DIRECTORY_ACL, ::GetLastError());
return false;
}
unsigned int id;
*exit_code = ProcessExitResult(UNABLE_TO_GET_WORK_DIRECTORY);
for (int max_attempts = 10; max_attempts; --max_attempts) {
::RtlGenRandom(&id, sizeof(id)); // Try a different name.
// This converts 'id' to a string in the format "78563412" on windows
// because of little endianness, but we don't care since it's just
// a name. Since we checked capaity at the front end, we don't need to
// duplicate it here.
HexEncode(&id, sizeof(id), work_dir->get() + end,
work_dir->capacity() - end);
// We only want the first 5 digits to remain within the 8.3 file name
// format (compliant with previous implementation).
work_dir->truncate_at(end + 5);
// for consistency with the previous implementation which relied on
// GetTempFileName, we append the .tmp extension.
work_dir->append(L".tmp");
if (::CreateDirectory(work_dir->get(),
sa.lpSecurityDescriptor ? &sa : nullptr)) {
// Yay! Now let's just append the backslash and we're done.
work_dir->append(L"\\");
*exit_code = ProcessExitResult(SUCCESS_EXIT_CODE);
break;
}
}
if (sa.lpSecurityDescriptor)
LocalFree(sa.lpSecurityDescriptor);
return exit_code->IsSuccess();
}
// Creates and returns a temporary directory in |work_dir| that can be used to
// extract updater payload. |work_dir| ends with a path separator.
bool GetWorkDir(HMODULE module,
PathString* work_dir,
ProcessExitResult* exit_code) {
PathString base_path;
DWORD len =
::GetTempPath(static_cast<DWORD>(base_path.capacity()), base_path.get());
if (!len || len >= base_path.capacity() ||
!CreateWorkDir(base_path.get(), work_dir, exit_code)) {
// Problem creating the work dir under TEMP path, so try using the
// current directory as the base path.
len = ::GetModuleFileName(module, base_path.get(),
static_cast<DWORD>(base_path.capacity()));
if (len >= base_path.capacity() || !len)
return false; // Can't even get current directory? Return an error.
wchar_t* name = GetNameFromPathExt(base_path.get(), len);
if (name == base_path.get())
return false; // There was no directory in the string! Bail out.
*name = L'\0';
*exit_code = ProcessExitResult(SUCCESS_EXIT_CODE);
return CreateWorkDir(base_path.get(), work_dir, exit_code);
}
return true;
}
// Returns true for ".." and "." directories.
bool IsCurrentOrParentDirectory(const wchar_t* dir) {
return dir && dir[0] == L'.' &&
(dir[1] == L'\0' || (dir[1] == L'.' && dir[2] == L'\0'));
}
ProcessExitResult WMain(HMODULE module) {
ProcessExitResult exit_code = ProcessExitResult(SUCCESS_EXIT_CODE);
// Parse configuration from the command line and resources.
Configuration configuration;
if (!configuration.Initialize(module))
return ProcessExitResult(GENERIC_INITIALIZATION_FAILURE, ::GetLastError());
// Exit early if an invalid switch was found on the command line.
if (configuration.has_invalid_switch())
return ProcessExitResult(INVALID_OPTION);
// First get a path where we can extract the resource payload, which is
// a compressed LZMA archive of a single file.
base::ScopedTempDir base_path_owner;
PathString base_path;
if (!GetWorkDir(module, &base_path, &exit_code))
return exit_code;
if (!base_path_owner.Set(base::FilePath(base_path.get()))) {
::DeleteFile(base_path.get());
return ProcessExitResult(static_cast<DWORD>(installer::TEMP_DIR_FAILED));
}
PathString compressed_archive;
exit_code = UnpackBinaryResources(configuration, module, base_path.get(),
&compressed_archive);
// Create a temp folder where the archives are unpacked.
base::FilePath unpack_path;
installer::SelfCleaningTempDir temp_path;
if (!CreateTemporaryAndUnpackDirectories(&temp_path, &unpack_path))
return ProcessExitResult(static_cast<DWORD>(installer::TEMP_DIR_FAILED));
// Unpack the compressed archive to extract the uncompressed archive file.
UnPackStatus unpack_status = UNPACK_NO_ERROR;
int32_t ntstatus = 0;
auto lzma_result =
UnPackArchive(base::FilePath(compressed_archive.get()), unpack_path,
nullptr, &unpack_status, &ntstatus);
if (lzma_result)
return ProcessExitResult(static_cast<DWORD>(installer::UNPACKING_FAILED));
// Unpack the uncompressed archive to extract the updater files.
base::FilePath uncompressed_archive =
unpack_path.Append(FILE_PATH_LITERAL("updater.7z"));
lzma_result = UnPackArchive(uncompressed_archive, unpack_path, nullptr,
&unpack_status, &ntstatus);
if (lzma_result)
return ProcessExitResult(static_cast<DWORD>(installer::UNPACKING_FAILED));
// While unpacking the binaries, we paged in a whole bunch of memory that
// we don't need anymore. Let's give it back to the pool before running
// setup.
::SetProcessWorkingSetSize(::GetCurrentProcess(), static_cast<SIZE_T>(-1),
static_cast<SIZE_T>(-1));
PathString setup_path;
if (!setup_path.assign(unpack_path.value().c_str()) ||
!setup_path.append(L"\\bin\\updater.exe")) {
exit_code = ProcessExitResult(PATH_STRING_OVERFLOW);
}
if (exit_code.IsSuccess())
exit_code = RunSetup(configuration, setup_path.get());
return exit_code;
}
} // namespace updater
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<!--
Have compatibility section here instead of using
build/win/compatibility.manifest
to work around crbug.com/272660.
TODO(yukawa): Use build/win/compatibility.manifest again.
-->
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below indicates application support for Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!--The ID below indicates application support for Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!--The ID below indicates application support for Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!--The ID below indicates application support for Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!--The ID below indicates application support for Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<ms_asmv2:trustInfo xmlns:ms_asmv2="urn:schemas-microsoft-com:asm.v2">
<ms_asmv2:security>
<ms_asmv2:requestedPrivileges>
<ms_asmv2:requestedExecutionLevel level="asInvoker" />
</ms_asmv2:requestedPrivileges>
</ms_asmv2:security>
</ms_asmv2:trustInfo>
</assembly>
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_UPDATER_WIN_INSTALLER_INSTALLER_H_
#define CHROME_UPDATER_WIN_INSTALLER_INSTALLER_H_
#include <windows.h>
#include "chrome/updater/win/installer/exit_code.h"
#include "chrome/updater/win/installer/string.h"
namespace updater {
// A container of a process exit code (eventually passed to ExitProcess) and
// a Windows error code for cases where the exit code is non-zero.
struct ProcessExitResult {
DWORD exit_code;
DWORD windows_error;
explicit ProcessExitResult(DWORD exit) : exit_code(exit), windows_error(0) {}
ProcessExitResult(DWORD exit, DWORD win)
: exit_code(exit), windows_error(win) {}
bool IsSuccess() const { return exit_code == SUCCESS_EXIT_CODE; }
};
// A stack-based string large enough to hold an executable to run
// (which is a path), plus a few extra arguments.
using CommandString = StackString<MAX_PATH * 4>;
// Main function for the installer.
ProcessExitResult WMain(HMODULE module);
} // namespace updater
#endif // CHROME_UPDATER_WIN_INSTALLER_INSTALLER_H_
// Microsoft Visual C++ generated resource script.
//
#include "installer_resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#define APSTUDIO_HIDDEN_SYMBOLS
#include "windows.h"
#undef APSTUDIO_HIDDEN_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_MINI_INSTALLER ICON "installer.ico"
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"installer_resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
"#include ""windows.h""\r\n"
"#undef APSTUDIO_HIDDEN_SYMBOL\0"
END
#endif // APSTUDIO_INVOKED
#endif // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/updater/win/installer/installer_constants.h"
namespace updater {
// The prefix of the updater archive resource.
const wchar_t kUpdaterArchivePrefix[] = L"updater";
// Temp directory prefix that this process creates.
const wchar_t kTempPrefix[] = L"UPDATER";
// 7zip archive.
const wchar_t kLZMAResourceType[] = L"B7";
} // namespace updater
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_UPDATER_WIN_INSTALLER_INSTALLER_CONSTANTS_H_
#define CHROME_UPDATER_WIN_INSTALLER_INSTALLER_CONSTANTS_H_
namespace updater {
// Various filenames and prefixes.
extern const wchar_t kUpdaterArchivePrefix[];
extern const wchar_t kTempPrefix[];
// The resource types that would be unpacked from the mini installer.
extern const wchar_t kLZMAResourceType[];
} // namespace updater
#endif // CHROME_UPDATER_WIN_INSTALLER_INSTALLER_CONSTANTS_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <windows.h>
#include "chrome/updater/win/installer/installer.h"
// http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx
extern "C" IMAGE_DOS_HEADER __ImageBase;
int WINAPI wWinMain(HINSTANCE /* instance */,
HINSTANCE /* previous_instance */,
LPWSTR /* command_line */,
int /* command_show */) {
updater::ProcessExitResult result =
updater::WMain(reinterpret_cast<HMODULE>(&__ImageBase));
return result.exit_code;
}
// Copyright (c) 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_UPDATER_WIN_INSTALLER_INSTALLER_RESOURCE_H_
#define CHROME_UPDATER_WIN_INSTALLER_INSTALLER_RESOURCE_H_
#define IDC_STATIC -1
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC 1
#define _APS_NEXT_RESOURCE_VALUE 129
#define _APS_NEXT_COMMAND_VALUE 32771
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 110
#endif
#endif
#endif // CHROME_UPDATER_WIN_INSTALLER_INSTALLER_RESOURCE_H_
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
// Use the ordinal 1 here, to avoid needing to #include a header file
// to use the VS_VERSION_INFO macro. This header file changes with different
// SDK versions which causes headaches building in some environments. The
// VERSIONINFO resource will always be at index 1.
1 VERSIONINFO
FILEVERSION @MAJOR@,@MINOR@,@BUILD@,@PATCH@
PRODUCTVERSION @MAJOR@,@MINOR@,@BUILD@,@PATCH@
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "@COMPANY_FULLNAME@"
VALUE "FileDescription", "@PRODUCT_INSTALLER_FULLNAME@"
VALUE "FileVersion", "@MAJOR@.@MINOR@.@BUILD@.@PATCH@"
VALUE "InternalName", "Chrome Updater"
VALUE "LegalCopyright", "@COPYRIGHT@"
VALUE "ProductName", "@PRODUCT_INSTALLER_FULLNAME@"
VALUE "ProductVersion", "@MAJOR@.@MINOR@.@BUILD@.@PATCH@"
VALUE "CompanyShortName", "@COMPANY_SHORTNAME@"
VALUE "ProductShortName", "@PRODUCT_INSTALLER_SHORTNAME@"
VALUE "LastChange", "@LASTCHANGE@"
VALUE "Official Build", "@OFFICIAL_BUILD@"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/updater/win/installer/pe_resource.h"
namespace updater {
PEResource::PEResource(const wchar_t* name, const wchar_t* type, HMODULE module)
: resource_(nullptr), module_(module) {
resource_ = ::FindResource(module, name, type);
}
bool PEResource::IsValid() {
return nullptr != resource_;
}
size_t PEResource::Size() {
return ::SizeofResource(module_, resource_);
}
bool PEResource::WriteToDisk(const wchar_t* full_path) {
// Resource handles are not real HGLOBALs so do not attempt to close them.
// Windows frees them whenever there is memory pressure.
HGLOBAL data_handle = ::LoadResource(module_, resource_);
if (nullptr == data_handle)
return false;
void* data = ::LockResource(data_handle);
if (nullptr == data)
return false;
size_t resource_size = Size();
HANDLE out_file = ::CreateFile(full_path, GENERIC_WRITE, 0, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (INVALID_HANDLE_VALUE == out_file)
return false;
DWORD written = 0;
if (!::WriteFile(out_file, data, static_cast<DWORD>(resource_size), &written,
nullptr)) {
::CloseHandle(out_file);
return false;
}
return ::CloseHandle(out_file) ? true : false;
}
} // namespace updater
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_UPDATER_WIN_INSTALLER_PE_RESOURCE_H_
#define CHROME_UPDATER_WIN_INSTALLER_PE_RESOURCE_H_
#include <stddef.h>
#include <windows.h>
namespace updater {
// This class models a windows PE resource. It does not pretend to be a full
// API wrapper and it is just concerned with loading it to memory and writing
// it to disk. Each resource is unique only in the context of a loaded module,
// that is why you need to specify one on each constructor.
class PEResource {
public:
// Takes the resource name, the resource type, and the module where
// to look for the resource. If the resource is found IsValid() returns true.
PEResource(const wchar_t* name, const wchar_t* type, HMODULE module);
// Returns true if the resource is valid.
bool IsValid();
// Returns the size in bytes of the resource. Returns zero if the resource is
// not valid.
size_t Size();
// Creates a file in |path| with a copy of the resource. If the resource can
// not be loaded into memory or if it cannot be written to disk it returns
// false.
bool WriteToDisk(const wchar_t* path);
private:
HRSRC resource_ = nullptr;
HMODULE module_ = nullptr;
};
} // namespace updater
#endif // CHROME_UPDATER_WIN_INSTALLER_PE_RESOURCE_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/updater/win/installer/regkey.h"
#include "chrome/updater/win/installer/installer_constants.h"
#include "chrome/updater/win/installer/string.h"
namespace updater {
LONG RegKey::Open(HKEY key, const wchar_t* sub_key, REGSAM access) {
Close();
return ::RegOpenKeyEx(key, sub_key, 0, access, &key_);
}
LONG RegKey::ReadSZValue(const wchar_t* value_name,
wchar_t* value,
size_t value_size) const {
DWORD type = 0;
DWORD byte_length = static_cast<DWORD>(value_size * sizeof(wchar_t));
LONG result = ::RegQueryValueEx(key_, value_name, nullptr, &type,
reinterpret_cast<BYTE*>(value), &byte_length);
if (result == ERROR_SUCCESS) {
if (type != REG_SZ) {
result = ERROR_NOT_SUPPORTED;
} else if (byte_length < 2) {
*value = L'\0';
} else if (value[byte_length / sizeof(wchar_t) - 1] != L'\0') {
if ((byte_length / sizeof(wchar_t)) < value_size)
value[byte_length / sizeof(wchar_t)] = L'\0';
else
result = ERROR_MORE_DATA;
}
}
return result;
}
LONG RegKey::ReadDWValue(const wchar_t* value_name, DWORD* value) const {
DWORD type = 0;
DWORD byte_length = sizeof(*value);
LONG result = ::RegQueryValueEx(key_, value_name, nullptr, &type,
reinterpret_cast<BYTE*>(value), &byte_length);
if (result == ERROR_SUCCESS) {
if (type != REG_DWORD) {
result = ERROR_NOT_SUPPORTED;
} else if (byte_length != sizeof(*value)) {
result = ERROR_NO_DATA;
}
}
return result;
}
LONG RegKey::WriteSZValue(const wchar_t* value_name, const wchar_t* value) {
return ::RegSetValueEx(key_, value_name, 0, REG_SZ,
reinterpret_cast<const BYTE*>(value),
(lstrlen(value) + 1) * sizeof(wchar_t));
}
LONG RegKey::WriteDWValue(const wchar_t* value_name, DWORD value) {
return ::RegSetValueEx(key_, value_name, 0, REG_DWORD,
reinterpret_cast<const BYTE*>(&value), sizeof(value));
}
void RegKey::Close() {
if (key_ != nullptr) {
::RegCloseKey(key_);
key_ = nullptr;
}
}
// static
bool RegKey::ReadSZValue(HKEY root_key,
const wchar_t* sub_key,
const wchar_t* value_name,
wchar_t* value,
size_t size) {
RegKey key;
return (key.Open(root_key, sub_key, KEY_QUERY_VALUE) == ERROR_SUCCESS &&
key.ReadSZValue(value_name, value, size) == ERROR_SUCCESS);
}
} // namespace updater
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_UPDATER_WIN_INSTALLER_REGKEY_H_
#define CHROME_UPDATER_WIN_INSTALLER_REGKEY_H_
#include <stddef.h>
#include <windows.h>
namespace updater {
// A helper class used to manipulate the Windows registry.
class RegKey {
public:
RegKey() : key_(nullptr) {}
~RegKey() { Close(); }
// Opens the key named |sub_key| with given |access| rights. Returns
// ERROR_SUCCESS or some other error.
LONG Open(HKEY key, const wchar_t* sub_key, REGSAM access);
// Returns true if a key is open.
bool is_valid() const { return key_ != nullptr; }
// Read a value from the registry into the memory indicated by |value|
// (of |value_size| wchar_t units). Returns ERROR_SUCCESS,
// ERROR_FILE_NOT_FOUND, ERROR_MORE_DATA, or some other error. |value| is
// guaranteed to be null-terminated on success.
LONG ReadSZValue(const wchar_t* value_name,
wchar_t* value,
size_t value_size) const;
LONG ReadDWValue(const wchar_t* value_name, DWORD* value) const;
// Write a value to the registry. SZ |value| must be null-terminated.
// Returns ERROR_SUCCESS or an error code.
LONG WriteSZValue(const wchar_t* value_name, const wchar_t* value);
LONG WriteDWValue(const wchar_t* value_name, DWORD value);
// Closes the key if it was open.
void Close();
// Helper function to read a value from registry. Returns true if value
// is read successfully and stored in parameter value. Returns false
// otherwise. |size| is measured in wchar_t units.
static bool ReadSZValue(HKEY root_key,
const wchar_t* sub_key,
const wchar_t* value_name,
wchar_t* value,
size_t value_size);
private:
RegKey(const RegKey&);
RegKey& operator=(const RegKey&);
HKEY key_;
};
} // namespace updater
#endif // CHROME_UPDATER_WIN_INSTALLER_REGKEY_H_
// Copyright (c) 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/bind.h"
#include "base/test/launcher/unit_test_launcher.h"
#include "base/test/test_suite.h"
int main(int argc, char** argv) {
base::TestSuite test_suite(argc, argv);
return base::LaunchUnitTestsSerially(
argc, argv,
base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/updater/win/installer/string.h"
#include <windows.h>
namespace {
// Returns true if the given two ASCII characters are same (ignoring case).
bool EqualASCIICharI(wchar_t a, wchar_t b) {
if (a >= L'A' && a <= L'Z')
a += (L'a' - L'A');
if (b >= L'A' && b <= L'Z')
b += (L'a' - L'A');
return (a == b);
}
} // namespace
namespace updater {
// Formats a sequence of |bytes| as hex. The |str| buffer must have room for
// at least 2*|size| + 1.
bool HexEncode(const void* bytes, size_t size, wchar_t* str, size_t str_size) {
if (str_size <= (size * 2))
return false;
static const wchar_t kHexChars[] = L"0123456789ABCDEF";
str[size * 2] = L'\0';
for (size_t i = 0; i < size; ++i) {
char b = reinterpret_cast<const char*>(bytes)[i];
str[(i * 2)] = kHexChars[(b >> 4) & 0xf];
str[(i * 2) + 1] = kHexChars[b & 0xf];
}
return true;
}
size_t SafeStrLen(const wchar_t* str, size_t alloc_size) {
if (!str || !alloc_size)
return 0;
size_t len = 0;
while (--alloc_size && str[len] != L'\0')
++len;
return len;
}
bool SafeStrCopy(wchar_t* dest, size_t dest_size, const wchar_t* src) {
if (!dest || !dest_size)
return false;
wchar_t* write = dest;
for (size_t remaining = dest_size; remaining != 0; --remaining) {
if ((*write++ = *src++) == L'\0')
return true;
}
// If we fail, we do not want to leave the string with partially copied
// contents. The reason for this is that we use these strings mostly for
// named objects such as files. If we copy a partial name, then that could
// match with something we do not want it to match with.
// Furthermore, since SafeStrCopy is called from SafeStrCat, we do not
// want to mutate the string in case the caller handles the error of a
// failed concatenation. For example:
//
// wchar_t buf[5] = {0};
// if (!SafeStrCat(buf, _countof(buf), kLongName))
// SafeStrCat(buf, _countof(buf), kShortName);
//
// If we were to return false in the first call to SafeStrCat but still
// mutate the buffer, the buffer will be in an unexpected state.
*dest = L'\0';
return false;
}
// Safer replacement for lstrcat function.
bool SafeStrCat(wchar_t* dest, size_t dest_size, const wchar_t* src) {
// Use SafeStrLen instead of lstrlen just in case the |dest| buffer isn't
// terminated.
size_t str_len = SafeStrLen(dest, dest_size);
return SafeStrCopy(dest + str_len, dest_size - str_len, src);
}
bool StrStartsWith(const wchar_t* str, const wchar_t* start_str) {
if (str == nullptr || start_str == nullptr)
return false;
for (int i = 0; start_str[i] != L'\0'; ++i) {
if (!EqualASCIICharI(str[i], start_str[i]))
return false;
}
return true;
}
const wchar_t* GetNameFromPathExt(const wchar_t* path, size_t size) {
if (!size)
return path;
const wchar_t* current = &path[size - 1];
while (current != path && L'\\' != *current)
--current;
// If no path separator found, just return |path|.
// Otherwise, return a pointer right after the separator.
return ((current == path) && (L'\\' != *current)) ? current : (current + 1);
}
wchar_t* GetNameFromPathExt(wchar_t* path, size_t size) {
return const_cast<wchar_t*>(
GetNameFromPathExt(const_cast<const wchar_t*>(path), size));
}
} // namespace updater
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_UPDATER_WIN_INSTALLER_STRING_H_
#define CHROME_UPDATER_WIN_INSTALLER_STRING_H_
#include <stddef.h>
namespace updater {
// NOTE: Do not assume that these string functions support UTF encoding.
// This is fine for the purposes of the mini_installer, but you have
// been warned!
// Formats a sequence of |bytes| as hex. The |str| buffer must have room for
// at least 2*|size| + 1.
bool HexEncode(const void* bytes, size_t size, wchar_t* str, size_t str_size);
// Counts the number of characters in the string up to a maximum of
// alloc_size. The highest return value from this function can therefore be
// alloc_size - 1 since |alloc_size| includes the \0 terminator.
size_t SafeStrLen(const wchar_t* str, size_t alloc_size);
// Simple replacement for CRT string copy method that does not overflow.
// Returns true if the source was copied successfully otherwise returns false.
// Parameter src is assumed to be nullptr terminated and the nullptr character
// is copied over to string dest.
bool SafeStrCopy(wchar_t* dest, size_t dest_size, const wchar_t* src);
// Simple replacement for CRT string copy method that does not overflow.
// Returns true if the source was copied successfully otherwise returns false.
// Parameter src is assumed to be nullptr terminated and the nullptr character
// is copied over to string dest. If the return value is false, the |dest|
// string should be the same as it was before.
bool SafeStrCat(wchar_t* dest, size_t dest_size, const wchar_t* src);
// Function to check if a string (specified by str) starts with another string
// (specified by start_str). The comparison is string insensitive.
bool StrStartsWith(const wchar_t* str, const wchar_t* start_str);
// Takes the path to file and returns a pointer to the basename component.
// Example input -> output:
// c:\full\path\to\file.ext -> file.ext
// file.ext -> file.ext
// Note: |size| is the number of characters in |path| not including the string
// terminator.
const wchar_t* GetNameFromPathExt(const wchar_t* path, size_t size);
wchar_t* GetNameFromPathExt(wchar_t* path, size_t size);
// A string class that manages a fixed size buffer on the stack.
// The methods in the class are based on the above string methods and the
// class additionally is careful about proper buffer termination.
template <size_t kCapacity>
class StackString {
public:
StackString() {
static_assert(kCapacity != 0, "invalid buffer size");
buffer_[kCapacity] = L'\0'; // We always reserve 1 more than asked for.
clear();
}
// We do not expose a constructor that accepts a string pointer on purpose.
// We expect the caller to call assign() and handle failures.
// Returns the number of reserved characters in this buffer, _including_
// the reserved char for the terminator.
size_t capacity() const { return kCapacity; }
wchar_t* get() { return buffer_; }
bool assign(const wchar_t* str) {
return SafeStrCopy(buffer_, kCapacity, str);
}
bool append(const wchar_t* str) {
return SafeStrCat(buffer_, kCapacity, str);
}
void clear() { buffer_[0] = L'\0'; }
size_t length() const { return SafeStrLen(buffer_, kCapacity); }
// Does a case insensitive search for a substring.
const wchar_t* findi(const wchar_t* find) const {
return SearchStringI(buffer_, find);
}
// Case insensitive string compare.
int comparei(const wchar_t* str) const { return lstrcmpiW(buffer_, str); }
// Case sensitive string compare.
int compare(const wchar_t* str) const { return lstrcmpW(buffer_, str); }
// Terminates the string at the specified location.
// Note: this method has no effect if this object's length is less than
// |location|.
bool truncate_at(size_t location) {
if (location >= kCapacity)
return false;
buffer_[location] = L'\0';
return true;
}
protected:
// We reserve 1 more than what is asked for as a safeguard against
// off-by-one errors.
wchar_t buffer_[kCapacity + 1];
private:
StackString(const StackString&);
StackString& operator=(const StackString&);
};
} // namespace updater
#endif // CHROME_UPDATER_WIN_INSTALLER_STRING_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <stdlib.h>
#include <windows.h>
#include <string>
#include "chrome/updater/win/installer/string.h"
#include "testing/gtest/include/gtest/gtest.h"
using updater::StackString;
namespace {
class InstallerStringTest : public testing::Test {
protected:
void SetUp() override {}
void TearDown() override {}
};
} // namespace
// Tests the strcat/strcpy/length support of the StackString class.
TEST_F(InstallerStringTest, StackStringOverflow) {
static const wchar_t kTestString[] = L"1234567890";
StackString<MAX_PATH> str;
EXPECT_EQ(static_cast<size_t>(MAX_PATH), str.capacity());
std::wstring compare_str;
EXPECT_EQ(str.length(), compare_str.length());
EXPECT_EQ(0, compare_str.compare(str.get()));
size_t max_chars = str.capacity() - 1;
while ((str.length() + (_countof(kTestString) - 1)) <= max_chars) {
EXPECT_TRUE(str.append(kTestString));
compare_str.append(kTestString);
EXPECT_EQ(str.length(), compare_str.length());
EXPECT_EQ(0, compare_str.compare(str.get()));
}
EXPECT_GT(static_cast<size_t>(MAX_PATH), str.length());
// Now we've exhausted the space we allocated for the string,
// so append should fail.
EXPECT_FALSE(str.append(kTestString));
// ...and remain unchanged.
EXPECT_EQ(0, compare_str.compare(str.get()));
EXPECT_EQ(str.length(), compare_str.length());
// Last test for fun.
str.clear();
compare_str.clear();
EXPECT_EQ(0, compare_str.compare(str.get()));
EXPECT_EQ(str.length(), compare_str.length());
}
[GENERAL]
updater.exe: %(UpdaterDir)s\
gen\chrome\updater\win\uninstall.cmd: %(UpdaterDir)s\
......@@ -21,14 +21,21 @@ namespace updater {
namespace {
constexpr char kBuildGenWinDir[] = "gen\\chrome\\updater\\win";
const base::char16* kUpdaterFiles[] = {
L"updater.exe",
L"uninstall.cmd",
#if defined(COMPONENT_BUILD)
L"base.dll", L"boringssl.dll", L"crcrypto.dll", L"icuuc.dll",
L"libc++.dll", L"prefs.dll", L"protobuf_lite.dll", L"updater.exe",
L"url_lib.dll", L"zlib.dll",
// TODO(sorin): get the list of component dependencies from a build-time
// file instead of hardcoding the names of the components here.
L"base.dll",
L"boringssl.dll",
L"crcrypto.dll",
L"icuuc.dll",
L"libc++.dll",
L"prefs.dll",
L"protobuf_lite.dll",
L"url_lib.dll",
L"zlib.dll",
#endif
};
......@@ -69,10 +76,6 @@ int Setup() {
WorkItem::CreateCopyTreeWorkItem(source_path, target_path, temp_dir,
WorkItem::ALWAYS, base::FilePath()));
}
install_list->AddWorkItem(WorkItem::CreateCopyTreeWorkItem(
source_dir.AppendASCII(kBuildGenWinDir).AppendASCII(kUninstallScript),
product_dir.AppendASCII(kUninstallScript), temp_dir, WorkItem::ALWAYS,
base::FilePath()));
if (!install_list->Do()) {
LOG(ERROR) << "Install failed, rolling back...";
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment