Commit 870ca29a authored by Joe Mason's avatar Joe Mason Committed by Commit Bot

Add chrome_cleaner/zip_archiver.

R=csharp

Bug: 830892
Change-Id: I821588099796a86a685bd85fa29456fd5d8bc705
Reviewed-on: https://chromium-review.googlesource.com/1197102
Commit-Queue: Joe Mason <joenotcharles@chromium.org>
Reviewed-by: default avatarTom Sepez <tsepez@chromium.org>
Reviewed-by: default avatarLeon Scroggins <scroggo@chromium.org>
Reviewed-by: default avatarChris Sharp <csharp@chromium.org>
Cr-Commit-Position: refs/heads/master@{#589235}
parent 0ed06288
......@@ -43,6 +43,7 @@ test("chrome_cleaner_unittests") {
"//chrome/chrome_cleaner/strings:unittest_sources",
"//chrome/chrome_cleaner/test:unittest_sources",
"//chrome/chrome_cleaner/ui:unittest_sources",
"//chrome/chrome_cleaner/zip_archiver:unittest_sources",
]
}
......
......@@ -198,6 +198,9 @@ bool CrashpadCrashClient::InitializeCrashReporting(Mode mode,
case SandboxType::kJsonParser:
SetCrashKey(kProcessType, "json_parser");
break;
case SandboxType::kZipArchiver:
SetCrashKey(kProcessType, "zip_archiver");
break;
default:
NOTREACHED();
}
......
......@@ -40,6 +40,12 @@ chrome_cleaner_mojom("json_parser_interface") {
]
}
chrome_cleaner_mojom("zip_archiver_interface") {
sources = [
"zip_archiver.mojom",
]
}
chrome_cleaner_mojom("engine_sandbox_test_interface") {
testonly = true
......
// Copyright 2018 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.
module chrome_cleaner.mojom;
// Represent result codes from the zip archiver.
enum ZipArchiverResultCode {
kSuccess = 0,
kErrorInvalidParameter = 1,
// Any error on file operations.
kErrorIO = 2,
kErrorMinizipInternal = 3,
kErrorCannotOpenSourceFile = 4,
kErrorCannotCreateZipFile = 5,
kZipFileExists = 6,
kIgnoredSourceFile = 7,
};
interface ZipArchiver {
// Passes |src_file_handle| which is open for reading and |zip_file_handle|
// which is open read/write from the high-privilege sandbox broker process to
// a locked down sandbox target process. The implementation will add the
// contents of the source file to the zip file, using the name
// |filename_in_zip| in the zip index, and encrypting the file with
// |password|.
Archive(handle src_file_handle, handle zip_file_handle,
string filename_in_zip, string password)
=> (ZipArchiverResultCode result_code);
};
......@@ -246,6 +246,7 @@ message ProcessInformation {
DEPRECATED_SIGNATURE_MATCHER_SANDBOX = 2;
ESET_SANDBOX = 3;
JSON_PARSER_SANDBOX = 4;
ZIP_ARCHIVER_SANDBOX = 5;
}
optional Process process = 1;
......
......@@ -253,6 +253,9 @@ ProcessInformation GetProcessInformationProtoObject(
case SandboxType::kJsonParser:
process_info.set_process(ProcessInformation::JSON_PARSER_SANDBOX);
break;
case SandboxType::kZipArchiver:
process_info.set_process(ProcessInformation::ZIP_ARCHIVER_SANDBOX);
break;
default:
NOTREACHED() << "Unknown sandbox type " << static_cast<int>(process_type);
}
......
......@@ -15,6 +15,7 @@ enum class SandboxType {
kTest,
kEset,
kJsonParser,
kZipArchiver,
kNumValues,
};
......
# Copyright 2018 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.
static_library("common") {
sources = [
"sandboxed_zip_archiver.cc",
"sandboxed_zip_archiver.h",
]
deps = [
"//chrome/chrome_cleaner/interfaces:zip_archiver_interface",
"//chrome/chrome_cleaner/ipc:mojo_task_runner",
"//chrome/chrome_cleaner/os:common_os",
"//chrome/chrome_cleaner/zip_archiver/broker:common",
"//mojo/public/cpp/system:system",
]
}
source_set("test_support") {
testonly = true
sources = [
"test_zip_archiver_util.cc",
"test_zip_archiver_util.h",
]
deps = [
"//base:base",
"//testing/gtest",
"//third_party/zlib:minizip",
]
}
source_set("unittest_sources") {
testonly = true
sources = [
"sandboxed_zip_archiver_unittest.cc",
]
deps = [
"//base/test:test_support",
"//chrome/chrome_cleaner/interfaces:zip_archiver_interface",
"//chrome/chrome_cleaner/ipc:mojo_task_runner",
"//chrome/chrome_cleaner/os:common_os",
"//chrome/chrome_cleaner/zip_archiver:common",
"//chrome/chrome_cleaner/zip_archiver:test_support",
"//chrome/chrome_cleaner/zip_archiver/broker:common",
"//chrome/chrome_cleaner/zip_archiver/target:common",
"//sandbox/win:sandbox",
"//testing/gtest",
# Tests from subdirs.
"//chrome/chrome_cleaner/zip_archiver/broker:unittest_sources",
"//chrome/chrome_cleaner/zip_archiver/target:unittest_sources",
]
}
include_rules = [
"+third_party/zlib",
]
# Copyright 2018 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.
static_library("common") {
sources = [
"sandbox_setup.cc",
"sandbox_setup.h",
]
deps = [
"//base:base",
"//chrome/chrome_cleaner/constants:common_strings",
"//chrome/chrome_cleaner/interfaces:zip_archiver_interface",
"//chrome/chrome_cleaner/ipc:mojo_task_runner",
"//chrome/chrome_cleaner/ipc:sandbox",
"//components/chrome_cleaner/public/constants:constants",
"//mojo/public/cpp/system:system",
"//sandbox/win:sandbox",
]
}
source_set("unittest_sources") {
testonly = true
sources = [
"sandbox_setup_unittest.cc",
]
deps = [
":common",
"//base:base",
"//base/test:test_support",
"//chrome/chrome_cleaner/interfaces:zip_archiver_interface",
"//chrome/chrome_cleaner/zip_archiver:test_support",
"//chrome/chrome_cleaner/zip_archiver/target:common",
"//mojo/public/cpp/system:system",
"//sandbox/win:sandbox",
"//testing/gtest",
]
}
// Copyright 2018 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/chrome_cleaner/zip_archiver/broker/sandbox_setup.h"
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "mojo/public/cpp/system/message_pipe.h"
namespace chrome_cleaner {
namespace {
void BindZipArchiverPtr(mojom::ZipArchiverPtr* zip_archiver_ptr,
mojo::ScopedMessagePipeHandle pipe_handle,
base::OnceClosure connection_error_handler) {
DCHECK(zip_archiver_ptr);
zip_archiver_ptr->Bind(mojom::ZipArchiverPtrInfo(std::move(pipe_handle), 0));
zip_archiver_ptr->set_connection_error_handler(
std::move(connection_error_handler));
}
} // namespace
ZipArchiverSandboxSetupHooks::ZipArchiverSandboxSetupHooks(
scoped_refptr<MojoTaskRunner> mojo_task_runner,
base::OnceClosure connection_error_handler)
: mojo_task_runner_(mojo_task_runner),
connection_error_handler_(std::move(connection_error_handler)),
// Manually use |new| here because |make_unique| doesn't work with
// custom deleter.
zip_archiver_ptr_(new mojom::ZipArchiverPtr(),
base::OnTaskRunnerDeleter(mojo_task_runner_)) {}
ZipArchiverSandboxSetupHooks::~ZipArchiverSandboxSetupHooks() = default;
ResultCode ZipArchiverSandboxSetupHooks::UpdateSandboxPolicy(
sandbox::TargetPolicy* policy,
base::CommandLine* command_line) {
DCHECK(policy);
DCHECK(command_line);
// Unretained pointer of |zip_archiver_ptr_| is safe because its deleter is
// run on the same task runner. So it won't be deleted before this task.
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(BindZipArchiverPtr,
base::Unretained(zip_archiver_ptr_.get()),
SetupSandboxMessagePipe(policy, command_line),
std::move(connection_error_handler_)));
return RESULT_CODE_SUCCESS;
}
UniqueZipArchiverPtr ZipArchiverSandboxSetupHooks::TakeZipArchiverPtr() {
return std::move(zip_archiver_ptr_);
}
} // namespace chrome_cleaner
// Copyright 2018 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_CHROME_CLEANER_ZIP_ARCHIVER_BROKER_SANDBOX_SETUP_H_
#define CHROME_CHROME_CLEANER_ZIP_ARCHIVER_BROKER_SANDBOX_SETUP_H_
#include <memory>
#include "base/command_line.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequenced_task_runner.h"
#include "chrome/chrome_cleaner/interfaces/zip_archiver.mojom.h"
#include "chrome/chrome_cleaner/ipc/mojo_sandbox_hooks.h"
#include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
#include "components/chrome_cleaner/public/constants/result_codes.h"
#include "sandbox/win/src/sandbox_policy.h"
namespace chrome_cleaner {
using UniqueZipArchiverPtr =
std::unique_ptr<mojom::ZipArchiverPtr, base::OnTaskRunnerDeleter>;
class ZipArchiverSandboxSetupHooks : public MojoSandboxSetupHooks {
public:
ZipArchiverSandboxSetupHooks(scoped_refptr<MojoTaskRunner> mojo_task_runner,
base::OnceClosure connection_error_handler);
~ZipArchiverSandboxSetupHooks() override;
// SandboxSetupHooks
ResultCode UpdateSandboxPolicy(sandbox::TargetPolicy* policy,
base::CommandLine* command_line) override;
UniqueZipArchiverPtr TakeZipArchiverPtr();
private:
scoped_refptr<MojoTaskRunner> mojo_task_runner_;
base::OnceClosure connection_error_handler_;
UniqueZipArchiverPtr zip_archiver_ptr_;
DISALLOW_COPY_AND_ASSIGN(ZipArchiverSandboxSetupHooks);
};
} // namespace chrome_cleaner
#endif // CHROME_CHROME_CLEANER_ZIP_ARCHIVER_BROKER_SANDBOX_SETUP_H_
// Copyright 2018 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 <string>
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequenced_task_runner.h"
#include "base/test/multiprocess_test.h"
#include "base/test/scoped_task_environment.h"
#include "base/win/scoped_handle.h"
#include "chrome/chrome_cleaner/interfaces/zip_archiver.mojom.h"
#include "chrome/chrome_cleaner/zip_archiver/broker/sandbox_setup.h"
#include "chrome/chrome_cleaner/zip_archiver/target/sandbox_setup.h"
#include "chrome/chrome_cleaner/zip_archiver/test_zip_archiver_util.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "sandbox/win/src/sandbox.h"
#include "sandbox/win/src/sandbox_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace chrome_cleaner {
namespace {
using mojom::ZipArchiverResultCode;
const char kTestFilenameInZip[] = "a.txt";
const char kTestPassword[] = "1234";
class ZipArchiverSandboxSetupTest : public base::MultiProcessTest {
public:
ZipArchiverSandboxSetupTest()
: mojo_task_runner_(MojoTaskRunner::Create()),
zip_archiver_ptr_(nullptr, base::OnTaskRunnerDeleter(nullptr)) {
ZipArchiverSandboxSetupHooks setup_hooks(
mojo_task_runner_.get(), base::BindOnce([] {
FAIL() << "ZipArchiver sandbox connection error";
}));
CHECK_EQ(
RESULT_CODE_SUCCESS,
StartSandboxTarget(MakeCmdLine("ZipArchiverSandboxSetupTargetMain"),
&setup_hooks, SandboxType::kTest));
zip_archiver_ptr_ = setup_hooks.TakeZipArchiverPtr();
}
protected:
void PostArchiveTask(base::win::ScopedHandle src_file_handle,
base::win::ScopedHandle zip_file_handle,
const std::string& filename_in_zip,
const std::string& password,
mojom::ZipArchiver::ArchiveCallback callback) {
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(RunArchive, base::Unretained(zip_archiver_ptr_.get()),
mojo::WrapPlatformFile(src_file_handle.Take()),
mojo::WrapPlatformFile(zip_file_handle.Take()),
filename_in_zip, password, std::move(callback)));
}
private:
static void RunArchive(mojom::ZipArchiverPtr* zip_archiver_ptr,
mojo::ScopedHandle mojo_src_handle,
mojo::ScopedHandle mojo_zip_handle,
const std::string& filename_in_zip,
const std::string& password,
mojom::ZipArchiver::ArchiveCallback callback) {
DCHECK(zip_archiver_ptr);
(*zip_archiver_ptr)
->Archive(std::move(mojo_src_handle), std::move(mojo_zip_handle),
filename_in_zip, password, std::move(callback));
}
scoped_refptr<MojoTaskRunner> mojo_task_runner_;
UniqueZipArchiverPtr zip_archiver_ptr_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
};
void OnArchiveDone(ZipArchiverResultCode* test_result_code,
base::OnceClosure callback,
ZipArchiverResultCode result_code) {
*test_result_code = result_code;
std::move(callback).Run();
}
} // namespace
MULTIPROCESS_TEST_MAIN(ZipArchiverSandboxSetupTargetMain) {
sandbox::TargetServices* sandbox_target_services =
sandbox::SandboxFactory::GetTargetServices();
CHECK(sandbox_target_services);
// |RunZipArchiverSandboxTarget| won't return. The mojo error handler will
// abort this process when the connection is broken.
RunZipArchiverSandboxTarget(*base::CommandLine::ForCurrentProcess(),
sandbox_target_services);
return 0;
}
TEST_F(ZipArchiverSandboxSetupTest, Archive) {
ZipArchiverTestFile test_file;
test_file.Initialize();
base::File src_file(test_file.GetSourceFilePath(),
base::File::FLAG_OPEN | base::File::FLAG_READ);
base::win::ScopedHandle src_file_handle(src_file.TakePlatformFile());
ASSERT_TRUE(src_file_handle.IsValid());
const base::FilePath zip_file_path =
test_file.GetSourceFilePath().AddExtension(L".zip");
base::File zip_file(zip_file_path,
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
base::win::ScopedHandle zip_file_handle(zip_file.TakePlatformFile());
ASSERT_TRUE(zip_file_handle.IsValid());
ZipArchiverResultCode test_result_code;
base::RunLoop loop;
PostArchiveTask(
std::move(src_file_handle), std::move(zip_file_handle),
kTestFilenameInZip, kTestPassword,
base::BindOnce(OnArchiveDone, &test_result_code, loop.QuitClosure()));
loop.Run();
EXPECT_EQ(test_result_code, ZipArchiverResultCode::kSuccess);
test_file.ExpectValidZipFile(zip_file_path, kTestFilenameInZip,
kTestPassword);
}
} // namespace chrome_cleaner
// Copyright 2018 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/chrome_cleaner/zip_archiver/sandboxed_zip_archiver.h"
#include <utility>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/win/scoped_handle.h"
#include "chrome/chrome_cleaner/os/disk_util.h"
#include "mojo/public/cpp/system/platform_handle.h"
namespace chrome_cleaner {
namespace {
using mojom::ZipArchiverResultCode;
constexpr wchar_t kDefaultFileStreamSuffix[] = L"::$DATA";
constexpr uint32_t kMinimizedReadAccess =
SYNCHRONIZE | FILE_READ_DATA | FILE_READ_ATTRIBUTES;
constexpr uint32_t kMinimizedWriteAccess =
SYNCHRONIZE | FILE_WRITE_DATA | FILE_READ_ATTRIBUTES;
// NTFS file stream can be specified by appending ":" to the filename. We remove
// the default file stream "::$DATA" so it won't break the filename in the
// following uses. For other file streams, we don't archive and ignore them.
bool GetSanitizedFileName(const base::FilePath& path,
base::string16* output_sanitized_filename) {
DCHECK(output_sanitized_filename);
base::string16 sanitized_filename = path.BaseName().AsUTF16Unsafe();
if (base::EndsWith(sanitized_filename, kDefaultFileStreamSuffix,
base::CompareCase::INSENSITIVE_ASCII)) {
// Remove the default file stream suffix.
sanitized_filename.erase(
sanitized_filename.end() - wcslen(kDefaultFileStreamSuffix),
sanitized_filename.end());
}
// If there is any ":" in |sanitized_filename|, it either points to a
// non-default file stream or is abnormal. Don't archive in this case.
if (sanitized_filename.find(L":") != base::string16::npos)
return false;
*output_sanitized_filename = sanitized_filename;
return true;
}
void RunArchiver(mojom::ZipArchiverPtr* zip_archiver_ptr,
mojo::ScopedHandle mojo_src_handle,
mojo::ScopedHandle mojo_zip_handle,
const std::string& filename,
const std::string& password,
mojom::ZipArchiver::ArchiveCallback callback) {
DCHECK(zip_archiver_ptr);
(*zip_archiver_ptr)
->Archive(std::move(mojo_src_handle), std::move(mojo_zip_handle),
filename, password, std::move(callback));
}
void OnArchiveDone(ZipArchiverResultCode* return_code,
base::WaitableEvent* waitable_event,
ZipArchiverResultCode result_code) {
*return_code = static_cast<ZipArchiverResultCode>(result_code);
waitable_event->Signal();
}
} // namespace
SandboxedZipArchiver::SandboxedZipArchiver(
scoped_refptr<MojoTaskRunner> mojo_task_runner,
UniqueZipArchiverPtr zip_archiver_ptr,
const base::FilePath& dst_archive_folder,
const std::string& zip_password)
: mojo_task_runner_(mojo_task_runner),
zip_archiver_ptr_(std::move(zip_archiver_ptr)),
dst_archive_folder_(dst_archive_folder),
zip_password_(zip_password) {
// Make sure the |zip_archiver_ptr| is bound with the |mojo_task_runner|.
DCHECK(zip_archiver_ptr_.get_deleter().task_runner_ == mojo_task_runner);
}
SandboxedZipArchiver::~SandboxedZipArchiver() = default;
// |SandboxedZipArchiver::Archive| archives the source file into a
// password-protected zip file stored in the |dst_archive_folder|. The format of
// zip file name is "|basename of the source file|_|hexdigest of the source file
// hash|.zip".
ZipArchiverResultCode SandboxedZipArchiver::Archive(
const base::FilePath& src_file_path,
base::FilePath* output_zip_file_path) {
DCHECK(output_zip_file_path);
// Open the source file with minimized rights for reading.
// Without |FILE_SHARE_WRITE| and |FILE_SHARE_DELETE|, |src_file_path| cannot
// be manipulated or replaced until |DoArchive| returns. This prevents the
// following checks from TOCTTOU. Because |base::IsLink| doesn't work on
// Windows, use |FILE_FLAG_OPEN_REPARSE_POINT| to open a symbolic link then
// check. To eliminate any TOCTTOU, use |FILE_FLAG_BACKUP_SEMANTICS| to open a
// directory then check.
base::File src_file(::CreateFile(
src_file_path.AsUTF16Unsafe().c_str(), kMinimizedReadAccess,
FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr));
if (!src_file.IsValid()) {
LOG(ERROR) << "Unable to open the source file.";
return ZipArchiverResultCode::kErrorCannotOpenSourceFile;
}
BY_HANDLE_FILE_INFORMATION src_file_info;
if (!::GetFileInformationByHandle(src_file.GetPlatformFile(),
&src_file_info)) {
LOG(ERROR) << "Unable to get the source file information.";
return ZipArchiverResultCode::kErrorIO;
}
// Don't archive symbolic links.
if (src_file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
return ZipArchiverResultCode::kIgnoredSourceFile;
// Don't archive directories. And |ZipArchiver| shouldn't get called with a
// directory path.
if (src_file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
LOG(ERROR) << "Tried to archive a directory.";
return ZipArchiverResultCode::kIgnoredSourceFile;
}
base::string16 sanitized_src_filename;
if (!GetSanitizedFileName(src_file_path, &sanitized_src_filename))
return ZipArchiverResultCode::kIgnoredSourceFile;
// TODO(veranika): Check the source file size once the limit is determined.
std::string src_file_hash;
if (!ComputeSHA256DigestOfPath(src_file_path, &src_file_hash)) {
LOG(ERROR) << "Unable to hash the source file.";
return ZipArchiverResultCode::kErrorIO;
}
// Zip file name format: "|source basename|_|src_file_hash|.zip"
const base::FilePath zip_filename(
base::StrCat({sanitized_src_filename, L"_",
base::UTF8ToUTF16(src_file_hash), L".zip"}));
const base::FilePath zip_file_path = dst_archive_folder_.Append(zip_filename);
// Fail if the zip file exists.
if (base::PathExists(zip_file_path))
return ZipArchiverResultCode::kZipFileExists;
// Create and open the zip file with minimized rights for writing.
base::File zip_file(::CreateFile(zip_file_path.AsUTF16Unsafe().c_str(),
kMinimizedWriteAccess, 0, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr));
if (!zip_file.IsValid()) {
LOG(ERROR) << "Unable to create the zip file.";
return ZipArchiverResultCode::kErrorCannotCreateZipFile;
}
const std::string filename_in_zip = base::UTF16ToUTF8(sanitized_src_filename);
ZipArchiverResultCode result_code =
DoArchive(std::move(src_file), std::move(zip_file), filename_in_zip);
if (result_code != ZipArchiverResultCode::kSuccess) {
// The |zip_file| has been closed when returned from the scope of
// |DoArchive|. Delete the incomplete zip file directly.
if (!base::DeleteFile(zip_file_path, /*recursive=*/false))
LOG(ERROR) << "Failed to delete the incomplete zip file.";
return result_code;
}
*output_zip_file_path = zip_file_path;
return ZipArchiverResultCode::kSuccess;
}
ZipArchiverResultCode SandboxedZipArchiver::DoArchive(
base::File src_file,
base::File zip_file,
const std::string& filename_in_zip) {
ZipArchiverResultCode result_code;
base::WaitableEvent waitable_event(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
// Unretained pointer of |zip_archiver_ptr_| is safe because its deleter
// is run on the same task runner. If |zip_archiver_ptr_| is destructed later,
// the deleter will be scheduled after this task.
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
RunArchiver, base::Unretained(zip_archiver_ptr_.get()),
mojo::WrapPlatformFile(src_file.TakePlatformFile()),
mojo::WrapPlatformFile(zip_file.TakePlatformFile()), filename_in_zip,
zip_password_,
base::BindOnce(OnArchiveDone, &result_code, &waitable_event)));
waitable_event.Wait();
return result_code;
}
ResultCode SpawnZipArchiverSandbox(
const base::FilePath& dst_archive_folder,
const std::string& zip_password,
scoped_refptr<MojoTaskRunner> mojo_task_runner,
base::OnceClosure connection_error_handler,
std::unique_ptr<SandboxedZipArchiver>* sandboxed_zip_archiver) {
ZipArchiverSandboxSetupHooks setup_hooks(mojo_task_runner,
std::move(connection_error_handler));
DCHECK(sandboxed_zip_archiver);
ResultCode result_code =
SpawnSandbox(&setup_hooks, SandboxType::kZipArchiver);
if (result_code == RESULT_CODE_SUCCESS) {
*sandboxed_zip_archiver = std::make_unique<SandboxedZipArchiver>(
mojo_task_runner, setup_hooks.TakeZipArchiverPtr(), dst_archive_folder,
zip_password);
}
return result_code;
}
} // namespace chrome_cleaner
// Copyright 2018 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_CHROME_CLEANER_ZIP_ARCHIVER_SANDBOXED_ZIP_ARCHIVER_H_
#define CHROME_CHROME_CLEANER_ZIP_ARCHIVER_SANDBOXED_ZIP_ARCHIVER_H_
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequenced_task_runner.h"
#include "chrome/chrome_cleaner/interfaces/zip_archiver.mojom.h"
#include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
#include "chrome/chrome_cleaner/zip_archiver/broker/sandbox_setup.h"
namespace chrome_cleaner {
class SandboxedZipArchiver {
public:
SandboxedZipArchiver(scoped_refptr<MojoTaskRunner> mojo_task_runner,
UniqueZipArchiverPtr zip_archiver_ptr,
const base::FilePath& dst_archive_folder,
const std::string& zip_password);
~SandboxedZipArchiver();
mojom::ZipArchiverResultCode Archive(const base::FilePath& src_file_path,
base::FilePath* output_zip_file_path);
private:
mojom::ZipArchiverResultCode DoArchive(base::File src_file,
base::File zip_file,
const std::string& filename_in_zip);
scoped_refptr<MojoTaskRunner> mojo_task_runner_;
UniqueZipArchiverPtr zip_archiver_ptr_;
const base::FilePath dst_archive_folder_;
const std::string zip_password_;
};
ResultCode SpawnZipArchiverSandbox(
const base::FilePath& dst_archive_folder,
const std::string& zip_password,
scoped_refptr<MojoTaskRunner> mojo_task_runner,
base::OnceClosure connection_error_handler,
std::unique_ptr<SandboxedZipArchiver>* sandboxed_zip_archiver);
} // namespace chrome_cleaner
#endif // CHROME_CHROME_CLEANER_ZIP_ARCHIVER_SANDBOXED_ZIP_ARCHIVER_H_
// Copyright 2018 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 <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/multiprocess_test.h"
#include "base/test/scoped_task_environment.h"
#include "base/win/scoped_handle.h"
#include "chrome/chrome_cleaner/interfaces/zip_archiver.mojom.h"
#include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
#include "chrome/chrome_cleaner/os/disk_util.h"
#include "chrome/chrome_cleaner/zip_archiver/broker/sandbox_setup.h"
#include "chrome/chrome_cleaner/zip_archiver/sandboxed_zip_archiver.h"
#include "chrome/chrome_cleaner/zip_archiver/target/sandbox_setup.h"
#include "chrome/chrome_cleaner/zip_archiver/test_zip_archiver_util.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "sandbox/win/src/sandbox.h"
#include "sandbox/win/src/sandbox_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace chrome_cleaner {
namespace {
using mojom::ZipArchiverResultCode;
constexpr char kTestPassword[] = "1234";
constexpr char kTestSymlink[] = "a.link";
constexpr uint32_t kProhibitedAccessPermissions[] = {
DELETE, READ_CONTROL, WRITE_DAC,
WRITE_OWNER, FILE_APPEND_DATA, FILE_EXECUTE,
FILE_READ_EA, FILE_WRITE_EA, FILE_WRITE_ATTRIBUTES};
class ZipArchiverSandboxedArchiverTest : public base::MultiProcessTest {
public:
void SetUp() override {
scoped_refptr<MojoTaskRunner> mojo_task_runner = MojoTaskRunner::Create();
ZipArchiverSandboxSetupHooks setup_hooks(
mojo_task_runner.get(), base::BindOnce([] {
FAIL() << "ZipArchiver sandbox connection error";
}));
ASSERT_EQ(RESULT_CODE_SUCCESS,
StartSandboxTarget(MakeCmdLine("SandboxedZipArchiverTargetMain"),
&setup_hooks, SandboxType::kTest));
UniqueZipArchiverPtr zip_archiver_ptr = setup_hooks.TakeZipArchiverPtr();
test_file_.Initialize();
const base::FilePath& src_file_path = test_file_.GetSourceFilePath();
std::string src_file_hash;
ComputeSHA256DigestOfPath(src_file_path, &src_file_hash);
const base::FilePath& dst_archive_folder = test_file_.GetTempDirPath();
base::FilePath zip_filename(
base::StrCat({test_file_.GetSourceFilePath().BaseName().AsUTF16Unsafe(),
L"_", base::UTF8ToUTF16(src_file_hash), L".zip"}));
expect_zip_file_path_ = dst_archive_folder.Append(zip_filename);
zip_archiver_ = std::make_unique<SandboxedZipArchiver>(
mojo_task_runner, std::move(zip_archiver_ptr), dst_archive_folder,
kTestPassword);
}
protected:
std::unique_ptr<SandboxedZipArchiver> zip_archiver_;
ZipArchiverTestFile test_file_;
base::FilePath expect_zip_file_path_;
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
};
// |ArchiverPermissionCheckerImpl| runs and handles |Archive| requests in the
// sandbox child process. It checks if the parameters passed in the sandbox are
// configured correctly. It doesn't do real archiving.
class ArchiverPermissionCheckerImpl : public mojom::ZipArchiver {
public:
explicit ArchiverPermissionCheckerImpl(mojom::ZipArchiverRequest request)
: binding_(this, std::move(request)) {
binding_.set_connection_error_handler(base::BindOnce(
[] { FAIL() << "ZipArchiver sandbox connection error"; }));
}
~ArchiverPermissionCheckerImpl() override = default;
void Archive(mojo::ScopedHandle src_file_handle,
mojo::ScopedHandle zip_file_handle,
const std::string& filename,
const std::string& password,
ArchiveCallback callback) override {
HANDLE raw_src_file_handle;
if (mojo::UnwrapPlatformFile(std::move(src_file_handle),
&raw_src_file_handle) != MOJO_RESULT_OK) {
std::move(callback).Run(ZipArchiverResultCode::kErrorInvalidParameter);
return;
}
base::File src_file(raw_src_file_handle);
if (!src_file.IsValid()) {
std::move(callback).Run(ZipArchiverResultCode::kErrorInvalidParameter);
return;
}
HANDLE raw_zip_file_handle;
if (mojo::UnwrapPlatformFile(std::move(zip_file_handle),
&raw_zip_file_handle) != MOJO_RESULT_OK) {
std::move(callback).Run(ZipArchiverResultCode::kErrorInvalidParameter);
return;
}
base::File zip_file(raw_zip_file_handle);
if (!zip_file.IsValid()) {
std::move(callback).Run(ZipArchiverResultCode::kErrorInvalidParameter);
return;
}
// Test general prohibited file access permissions.
for (uint32_t permission : kProhibitedAccessPermissions) {
if (HasPermission(src_file, permission) ||
HasPermission(zip_file, permission)) {
std::move(callback).Run(ZipArchiverResultCode::kErrorInvalidParameter);
return;
}
}
// Check if |src_file| and |zip_file| have incorrect file access
// permissions.
if (HasPermission(src_file, FILE_WRITE_DATA) ||
HasPermission(zip_file, FILE_READ_DATA)) {
std::move(callback).Run(ZipArchiverResultCode::kErrorInvalidParameter);
return;
}
std::move(callback).Run(ZipArchiverResultCode::kSuccess);
}
private:
static bool HasPermission(const base::File& file, uint32_t permission) {
HANDLE temp_handle;
if (::DuplicateHandle(::GetCurrentProcess(), file.GetPlatformFile(),
::GetCurrentProcess(), &temp_handle, permission,
false, 0)) {
CloseHandle(temp_handle);
return true;
}
return false;
}
mojo::Binding<mojom::ZipArchiver> binding_;
DISALLOW_COPY_AND_ASSIGN(ArchiverPermissionCheckerImpl);
};
} // namespace
MULTIPROCESS_TEST_MAIN(SandboxedZipArchiverTargetMain) {
sandbox::TargetServices* sandbox_target_services =
sandbox::SandboxFactory::GetTargetServices();
CHECK(sandbox_target_services);
// |RunZipArchiverSandboxTarget| won't return. The mojo error handler will
// abort this process when the connection is broken.
RunZipArchiverSandboxTarget(*base::CommandLine::ForCurrentProcess(),
sandbox_target_services);
return 0;
}
TEST_F(ZipArchiverSandboxedArchiverTest, Archive) {
base::FilePath output_zip_file_path;
EXPECT_EQ(zip_archiver_->Archive(test_file_.GetSourceFilePath(),
&output_zip_file_path),
ZipArchiverResultCode::kSuccess);
EXPECT_EQ(output_zip_file_path, expect_zip_file_path_);
test_file_.ExpectValidZipFile(
expect_zip_file_path_,
test_file_.GetSourceFilePath().BaseName().AsUTF8Unsafe(), kTestPassword);
}
TEST_F(ZipArchiverSandboxedArchiverTest, SourceFileNotFound) {
ASSERT_TRUE(base::DeleteFile(test_file_.GetSourceFilePath(), false));
base::FilePath output_zip_file_path;
EXPECT_EQ(zip_archiver_->Archive(test_file_.GetSourceFilePath(),
&output_zip_file_path),
ZipArchiverResultCode::kErrorCannotOpenSourceFile);
}
TEST_F(ZipArchiverSandboxedArchiverTest, ZipFileExists) {
base::File zip_file(expect_zip_file_path_, base::File::FLAG_CREATE);
ASSERT_TRUE(zip_file.IsValid());
base::FilePath output_zip_file_path;
EXPECT_EQ(zip_archiver_->Archive(test_file_.GetSourceFilePath(),
&output_zip_file_path),
ZipArchiverResultCode::kZipFileExists);
}
TEST_F(ZipArchiverSandboxedArchiverTest, SourceIsSymbolicLink) {
base::FilePath symlink_path =
test_file_.GetTempDirPath().AppendASCII(kTestSymlink);
ASSERT_TRUE(::CreateSymbolicLink(
symlink_path.AsUTF16Unsafe().c_str(),
test_file_.GetSourceFilePath().AsUTF16Unsafe().c_str(), 0));
base::FilePath output_zip_file_path;
EXPECT_EQ(zip_archiver_->Archive(symlink_path, &output_zip_file_path),
ZipArchiverResultCode::kIgnoredSourceFile);
}
TEST_F(ZipArchiverSandboxedArchiverTest, SourceIsDirectory) {
base::FilePath output_zip_file_path;
EXPECT_EQ(zip_archiver_->Archive(test_file_.GetTempDirPath(),
&output_zip_file_path),
ZipArchiverResultCode::kIgnoredSourceFile);
}
TEST_F(ZipArchiverSandboxedArchiverTest, SourceIsDefaultFileStream) {
base::FilePath stream_path(base::StrCat(
{test_file_.GetSourceFilePath().AsUTF16Unsafe(), L"::$data"}));
base::FilePath output_zip_file_path;
EXPECT_EQ(zip_archiver_->Archive(stream_path, &output_zip_file_path),
ZipArchiverResultCode::kSuccess);
EXPECT_EQ(output_zip_file_path, expect_zip_file_path_);
test_file_.ExpectValidZipFile(
expect_zip_file_path_,
test_file_.GetSourceFilePath().BaseName().AsUTF8Unsafe(), kTestPassword);
}
TEST_F(ZipArchiverSandboxedArchiverTest, SourceIsNonDefaultFileStream) {
base::FilePath stream_path(base::StrCat(
{test_file_.GetSourceFilePath().AsUTF16Unsafe(), L":stream:$data"}));
base::File stream_file(stream_path, base::File::FLAG_CREATE);
ASSERT_TRUE(stream_file.IsValid());
base::FilePath output_zip_file_path;
EXPECT_EQ(zip_archiver_->Archive(stream_path, &output_zip_file_path),
ZipArchiverResultCode::kIgnoredSourceFile);
}
namespace {
// |ZipArchiverIsolationTest| uses |ArchiverPermissionCheckerImpl| to check the
// sandbox configuration.
class ZipArchiverIsolationTest : public base::MultiProcessTest {
public:
ZipArchiverIsolationTest()
: mojo_task_runner_(MojoTaskRunner::Create()),
impl_ptr_(nullptr, base::OnTaskRunnerDeleter(mojo_task_runner_)) {
UniqueZipArchiverPtr zip_archiver_ptr(
new mojom::ZipArchiverPtr(),
base::OnTaskRunnerDeleter(mojo_task_runner_));
// Initialize the |impl_ptr_| in the mojo task and wait until it completed.
base::RunLoop loop;
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&ZipArchiverIsolationTest::InitializeArchiverPermissionCheckerImpl,
base::Unretained(this), zip_archiver_ptr.get(),
loop.QuitClosure()));
loop.Run();
test_file_.Initialize();
zip_archiver_ = std::make_unique<SandboxedZipArchiver>(
mojo_task_runner_, std::move(zip_archiver_ptr),
test_file_.GetTempDirPath(), kTestPassword);
}
protected:
std::unique_ptr<SandboxedZipArchiver> zip_archiver_;
ZipArchiverTestFile test_file_;
private:
void InitializeArchiverPermissionCheckerImpl(
mojom::ZipArchiverPtr* zip_archiver_ptr,
base::OnceClosure callback) {
impl_ptr_.reset(
new ArchiverPermissionCheckerImpl(mojo::MakeRequest(zip_archiver_ptr)));
std::move(callback).Run();
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
scoped_refptr<MojoTaskRunner> mojo_task_runner_;
std::unique_ptr<ArchiverPermissionCheckerImpl, base::OnTaskRunnerDeleter>
impl_ptr_;
};
} // namespace
TEST_F(ZipArchiverIsolationTest, CheckPermission) {
base::FilePath output_zip_file_path;
EXPECT_EQ(zip_archiver_->Archive(test_file_.GetSourceFilePath(),
&output_zip_file_path),
ZipArchiverResultCode::kSuccess);
}
} // namespace chrome_cleaner
# Copyright 2018 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.
static_library("common") {
sources = [
"sandbox_setup.cc",
"sandbox_setup.h",
"zip_archiver_impl.cc",
"zip_archiver_impl.h",
]
deps = [
"//base:base",
"//chrome/chrome_cleaner/constants:common_strings",
"//chrome/chrome_cleaner/interfaces:zip_archiver_interface",
"//chrome/chrome_cleaner/ipc:mojo_task_runner",
"//chrome/chrome_cleaner/ipc:sandbox",
"//chrome/chrome_cleaner/os:common_os",
"//components/chrome_cleaner/public/constants:constants",
"//mojo/public/cpp/system:system",
"//sandbox/win:sandbox",
"//third_party/zlib:minizip",
"//third_party/zlib:zlib",
]
}
source_set("unittest_sources") {
testonly = true
sources = [
"zip_archiver_impl_unittest.cc",
]
deps = [
":common",
"//base:base",
"//base/test:test_config",
"//base/test:test_support",
"//chrome/chrome_cleaner/interfaces:zip_archiver_interface",
"//chrome/chrome_cleaner/ipc:mojo_task_runner",
"//chrome/chrome_cleaner/zip_archiver:test_support",
"//mojo/public/cpp/system:system",
"//testing/gtest",
]
}
// Copyright 2018 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/chrome_cleaner/zip_archiver/target/sandbox_setup.h"
#include <memory>
#include <utility>
#include "base/memory/scoped_refptr.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
#include "chrome/chrome_cleaner/ipc/mojo_sandbox_hooks.h"
#include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
#include "chrome/chrome_cleaner/os/early_exit.h"
#include "chrome/chrome_cleaner/zip_archiver/target/zip_archiver_impl.h"
namespace chrome_cleaner {
namespace {
class ZipArchiverSandboxTargetHooks : public MojoSandboxTargetHooks {
public:
explicit ZipArchiverSandboxTargetHooks(MojoTaskRunner* mojo_task_runner);
~ZipArchiverSandboxTargetHooks() override;
// Sandbox hooks
ResultCode TargetDroppedPrivileges(
const base::CommandLine& command_line) override;
private:
void CreateZipArchiverImpl(mojom::ZipArchiverRequest request);
MojoTaskRunner* mojo_task_runner_;
base::MessageLoop message_loop_;
std::unique_ptr<ZipArchiverImpl, base::OnTaskRunnerDeleter>
zip_archiver_impl_;
};
ZipArchiverSandboxTargetHooks::ZipArchiverSandboxTargetHooks(
MojoTaskRunner* mojo_task_runner)
: mojo_task_runner_(mojo_task_runner),
zip_archiver_impl_(nullptr,
base::OnTaskRunnerDeleter(mojo_task_runner_)) {}
ZipArchiverSandboxTargetHooks::~ZipArchiverSandboxTargetHooks() = default;
ResultCode ZipArchiverSandboxTargetHooks::TargetDroppedPrivileges(
const base::CommandLine& command_line) {
mojom::ZipArchiverRequest request(ExtractSandboxMessagePipe(command_line));
// This loop will run forever. Once the communication channel with the broker
// process is broken, mojo error handler will abort this process.
base::RunLoop run_loop;
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ZipArchiverSandboxTargetHooks::CreateZipArchiverImpl,
base::Unretained(this), std::move(request)));
run_loop.Run();
return RESULT_CODE_SUCCESS;
}
void ZipArchiverSandboxTargetHooks::CreateZipArchiverImpl(
mojom::ZipArchiverRequest request) {
// Replace the null pointer by the actual |ZipArchiverImpl|.
// Manually use |new| here because |make_unique| doesn't work with
// custom deleter.
zip_archiver_impl_.reset(
new ZipArchiverImpl(std::move(request), base::BindOnce(&EarlyExit, 1)));
}
} // namespace
ResultCode RunZipArchiverSandboxTarget(
const base::CommandLine& command_line,
sandbox::TargetServices* target_services) {
DCHECK(target_services);
scoped_refptr<MojoTaskRunner> mojo_task_runner = MojoTaskRunner::Create();
ZipArchiverSandboxTargetHooks target_hooks(mojo_task_runner.get());
return RunSandboxTarget(command_line, target_services, &target_hooks);
}
} // namespace chrome_cleaner
// Copyright 2018 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_CHROME_CLEANER_ZIP_ARCHIVER_TARGET_SANDBOX_SETUP_H_
#define CHROME_CHROME_CLEANER_ZIP_ARCHIVER_TARGET_SANDBOX_SETUP_H_
#include "base/command_line.h"
#include "components/chrome_cleaner/public/constants/result_codes.h"
#include "sandbox/win/src/sandbox.h"
namespace chrome_cleaner {
ResultCode RunZipArchiverSandboxTarget(
const base::CommandLine& command_line,
sandbox::TargetServices* target_services);
} // namespace chrome_cleaner
#endif // CHROME_CHROME_CLEANER_ZIP_ARCHIVER_TARGET_SANDBOX_SETUP_H_
// Copyright 2018 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/chrome_cleaner/zip_archiver/target/zip_archiver_impl.h"
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/time.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "third_party/zlib/contrib/minizip/ioapi.h"
#include "third_party/zlib/contrib/minizip/zip.h"
#include "third_party/zlib/zlib.h"
namespace chrome_cleaner {
namespace {
using mojom::ZipArchiverResultCode;
constexpr int64_t kReadBufferSize = 4096;
// Compression method is STORE(0)
constexpr int16_t kCompressionMethod = 0;
// Section 4.4.2 http://www.pkware.com/documents/casestudies/APPNOTE.TXT
// The lower byte indicates the ZIP version(63 = 6.3) and the upper byte
// indicates the file attribute compatibility(0 = MS-DOS). We would like to
// choose the lowest version as possible to make it easier to decompress. ZIP
// 6.3 is the lowest version which supports UTF-8 filename.
constexpr uint16_t kVersionMadeBy = (0x0 << 8) | 63;
// Section 4.4.4 http://www.pkware.com/documents/casestudies/APPNOTE.TXT
// Setting the Language encoding flag so the file is told to be in UTF-8.
constexpr uint16_t kLanguageEncodingFlag = 0x1 << 11;
bool CalculateFileCrc32(base::File* file,
const int64_t length,
uint32_t* crc32_result) {
DCHECK(file);
DCHECK(crc32_result);
std::vector<char> buffer(kReadBufferSize);
int64_t offset = 0;
uint32_t crc32_value = 0;
while (offset < length) {
const int read_size = file->Read(
offset, buffer.data(), std::min(length - offset, kReadBufferSize));
if (read_size <= 0) {
LOG(ERROR) << "Unable to read the file when calculating CRC32.";
return false;
}
CHECK_LE(base::checked_cast<size_t>(read_size), buffer.size());
crc32_value = crc32(crc32_value, reinterpret_cast<uint8_t*>(buffer.data()),
read_size);
offset += read_size;
}
*crc32_result = crc32_value;
return true;
}
// Set up the minizip IO interface. The default functions of the interface are
// POSIX IO functions, which work with |FILE*| file objects. Therefore, we hook
// the open function of the interface and directly return the |FILE*| object of
// the opened zip file. So other default functions can operate correctly with
// the returned |FILE*| object, including closing the file.
bool InitializeZipIOInterface(base::File output_file,
zlib_filefunc64_def* zip_func_table) {
DCHECK(zip_func_table);
FILE* output_file_ptr = base::FileToFILE(std::move(output_file), "wb");
if (output_file_ptr == nullptr) {
LOG(ERROR) << "Unable to open FILE* from the base::File object.";
return false;
}
// Initialize with the default POSIX IO functions.
fill_fopen64_filefunc(zip_func_table);
// Now the |output_file_ptr| is owned by the minizip.
zip_func_table->opaque = output_file_ptr;
// Return the |FILE*| object of the opened zip file.
zip_func_table->zopen64_file = [](void* opaque, const void* /*filename*/,
int /*mode*/) { return opaque; };
return true;
}
zip_fileinfo TimeToZipFileInfo(const base::Time& file_time) {
base::Time::Exploded file_time_parts;
file_time.LocalExplode(&file_time_parts);
zip_fileinfo zip_info = {};
// This if check works around the handling of the year value in
// contrib/minizip/zip.c in function zip64local_TmzDateToDosDate
// It assumes that dates below 1980 are in the double digit format.
// Hence the fail safe option is to leave the date unset. Some programs
// might show the unset date as 1980-0-0 which is invalid.
if (file_time_parts.year >= 1980) {
zip_info.tmz_date.tm_year = file_time_parts.year;
zip_info.tmz_date.tm_mon = file_time_parts.month - 1;
zip_info.tmz_date.tm_mday = file_time_parts.day_of_month;
zip_info.tmz_date.tm_hour = file_time_parts.hour;
zip_info.tmz_date.tm_min = file_time_parts.minute;
zip_info.tmz_date.tm_sec = file_time_parts.second;
}
return zip_info;
}
ZipArchiverResultCode AddToArchive(zipFile zip_object,
const std::string& filename_in_zip,
const std::string& password,
base::File src_file) {
base::File::Info file_info;
if (!src_file.GetInfo(&file_info)) {
LOG(ERROR) << "Unable to get the file information.";
return ZipArchiverResultCode::kErrorIO;
}
if (file_info.is_directory || file_info.is_symbolic_link) {
LOG(ERROR) << "The source file is a directory or a symbolic link.";
return ZipArchiverResultCode::kErrorInvalidParameter;
}
const int64_t src_length = file_info.size;
// TODO(veranika): Check the source file size once the limit is determined.
uint32_t src_crc32 = 0;
if (!CalculateFileCrc32(&src_file, src_length, &src_crc32)) {
LOG(ERROR) << "Failed to calculate the CRC32 of the source file.";
return ZipArchiverResultCode::kErrorIO;
}
const zip_fileinfo zip_info = TimeToZipFileInfo(file_info.last_modified);
if (zipOpenNewFileInZip4_64(
zip_object, filename_in_zip.c_str(), &zip_info,
/*extrafield_local=*/nullptr, /*size_extrafield_local=*/0,
/*extrafield_global=*/nullptr, /*size_extrafield_global=*/0,
/*comment=*/nullptr, kCompressionMethod, /*level=*/0, /*raw=*/0,
-MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, password.c_str(),
src_crc32, kVersionMadeBy, kLanguageEncodingFlag,
/*zip64=*/1) != Z_OK) {
LOG(ERROR) << "Unable to create a file entry in the zip.";
return ZipArchiverResultCode::kErrorMinizipInternal;
}
std::vector<char> buffer(kReadBufferSize);
int64_t src_offset = 0;
while (src_offset < src_length) {
const int read_size =
src_file.Read(src_offset, buffer.data(),
std::min(src_length - src_offset, kReadBufferSize));
if (read_size <= 0) {
LOG(ERROR) << "Unable to read the source file when archiving.";
return ZipArchiverResultCode::kErrorIO;
}
CHECK_LE(base::checked_cast<size_t>(read_size), buffer.size());
if (zipWriteInFileInZip(zip_object, buffer.data(), read_size) != Z_OK) {
LOG(ERROR) << "Unable to write data into the zip.";
return ZipArchiverResultCode::kErrorMinizipInternal;
}
src_offset += read_size;
}
if (zipCloseFileInZip(zip_object) != Z_OK) {
LOG(ERROR) << "Unable to close the file entry.";
return ZipArchiverResultCode::kErrorMinizipInternal;
}
return ZipArchiverResultCode::kSuccess;
}
} // namespace
ZipArchiverImpl::ZipArchiverImpl(mojom::ZipArchiverRequest request,
base::OnceClosure connection_error_handler)
: binding_(this, std::move(request)) {
binding_.set_connection_error_handler(std::move(connection_error_handler));
}
ZipArchiverImpl::~ZipArchiverImpl() = default;
void ZipArchiverImpl::Archive(mojo::ScopedHandle src_file_handle,
mojo::ScopedHandle zip_file_handle,
const std::string& filename_in_zip,
const std::string& password,
ArchiveCallback callback) {
// Neither |filename_in_zip| nor |password| being empty will raise any error
// in the minizip internally. However, the produced zip file can't be
// decompressed by some zip tools. So these cases are rejected.
if (filename_in_zip.empty() || password.empty()) {
LOG(ERROR) << "Either filename or password is empty.";
std::move(callback).Run(ZipArchiverResultCode::kErrorInvalidParameter);
return;
}
HANDLE raw_src_file_handle;
if (mojo::UnwrapPlatformFile(std::move(src_file_handle),
&raw_src_file_handle) != MOJO_RESULT_OK) {
LOG(ERROR) << "Unable to get the source HANDLE from mojo.";
std::move(callback).Run(ZipArchiverResultCode::kErrorInvalidParameter);
return;
}
base::File src_file(raw_src_file_handle);
if (!src_file.IsValid()) {
LOG(ERROR) << "Source file is invalid.";
std::move(callback).Run(ZipArchiverResultCode::kErrorInvalidParameter);
return;
}
HANDLE raw_zip_file_handle;
if (mojo::UnwrapPlatformFile(std::move(zip_file_handle),
&raw_zip_file_handle) != MOJO_RESULT_OK) {
LOG(ERROR) << "Unable to get the destination HANDLE from mojo.";
std::move(callback).Run(ZipArchiverResultCode::kErrorInvalidParameter);
return;
}
base::File zip_file(raw_zip_file_handle);
if (!zip_file.IsValid()) {
LOG(ERROR) << "Destination file is invalid.";
std::move(callback).Run(ZipArchiverResultCode::kErrorInvalidParameter);
return;
}
zlib_filefunc64_def zip_func_table;
if (!InitializeZipIOInterface(std::move(zip_file), &zip_func_table)) {
LOG(ERROR) << "Failed to initialize the minizip IO interface.";
std::move(callback).Run(ZipArchiverResultCode::kErrorInvalidParameter);
return;
}
// Since the open function has been hooked in |InitializeZipIOInterface|,
// which doesn't need a file path, we just pass a dummy path to the |path|.
zipFile zip_object = zipOpen2_64(/*path=*/"", /*append=*/0,
/*globalcomment=*/nullptr, &zip_func_table);
if (zip_object == nullptr) {
LOG(ERROR) << "Unable to open the zip file.";
std::move(callback).Run(ZipArchiverResultCode::kErrorMinizipInternal);
return;
}
ZipArchiverResultCode result_code =
AddToArchive(zip_object, filename_in_zip, password, std::move(src_file));
if (zipClose(zip_object, /*global_comment=*/nullptr) != Z_OK) {
LOG(ERROR) << "Unable to close the zip file.";
if (result_code == ZipArchiverResultCode::kSuccess)
result_code = ZipArchiverResultCode::kErrorMinizipInternal;
}
std::move(callback).Run(result_code);
}
} // namespace chrome_cleaner
// Copyright 2018 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_CHROME_CLEANER_ZIP_ARCHIVER_TARGET_ZIP_ARCHIVER_IMPL_H_
#define CHROME_CHROME_CLEANER_ZIP_ARCHIVER_TARGET_ZIP_ARCHIVER_IMPL_H_
#include <string>
#include "chrome/chrome_cleaner/interfaces/zip_archiver.mojom.h"
#include "mojo/public/cpp/bindings/binding.h"
namespace chrome_cleaner {
class ZipArchiverImpl : public mojom::ZipArchiver {
public:
ZipArchiverImpl(mojom::ZipArchiverRequest request,
base::OnceClosure connection_error_handler);
~ZipArchiverImpl() override;
void Archive(mojo::ScopedHandle src_file_handle,
mojo::ScopedHandle zip_file_handle,
const std::string& filename,
const std::string& password,
ArchiveCallback callback) override;
private:
mojo::Binding<mojom::ZipArchiver> binding_;
DISALLOW_COPY_AND_ASSIGN(ZipArchiverImpl);
};
} // namespace chrome_cleaner
#endif // CHROME_CHROME_CLEANER_ZIP_ARCHIVER_TARGET_ZIP_ARCHIVER_IMPL_H_
// Copyright 2018 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/chrome_cleaner/zip_archiver/target/zip_archiver_impl.h"
#include <utility>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/test/scoped_task_environment.h"
#include "base/win/scoped_handle.h"
#include "chrome/chrome_cleaner/interfaces/zip_archiver.mojom.h"
#include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
#include "chrome/chrome_cleaner/zip_archiver/test_zip_archiver_util.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chrome_cleaner {
namespace {
using mojom::ZipArchiverResultCode;
const char kTestFilenameInZip[] = "a.txt";
const char kTestPassword[] = "1234";
class ZipArchiverImplTest : public testing::Test {
public:
void SetUp() override {
test_file_.Initialize();
base::File src_file(test_file_.GetSourceFilePath(),
base::File::FLAG_OPEN | base::File::FLAG_READ);
src_file_handle_ = base::win::ScopedHandle(src_file.TakePlatformFile());
ASSERT_TRUE(src_file_handle_.IsValid());
zip_file_path_ = test_file_.GetSourceFilePath().AddExtension(L".zip");
base::File zip_file(zip_file_path_,
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
zip_file_handle_ = base::win::ScopedHandle(zip_file.TakePlatformFile());
ASSERT_TRUE(zip_file_handle_.IsValid());
}
protected:
ZipArchiverTestFile test_file_;
base::win::ScopedHandle src_file_handle_;
base::win::ScopedHandle zip_file_handle_;
base::FilePath zip_file_path_;
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
};
void RunArchiver(base::win::ScopedHandle src_file_handle,
base::win::ScopedHandle zip_file_handle,
const std::string& filename,
const std::string& password,
mojom::ZipArchiver::ArchiveCallback callback) {
mojom::ZipArchiverPtr zip_archiver_ptr;
ZipArchiverImpl zip_archiver_impl(
mojo::MakeRequest(&zip_archiver_ptr),
/*connection_error_handler=*/base::DoNothing());
zip_archiver_impl.Archive(mojo::WrapPlatformFile(src_file_handle.Take()),
mojo::WrapPlatformFile(zip_file_handle.Take()),
filename, password, std::move(callback));
}
void OnArchiveDone(ZipArchiverResultCode* test_result_code,
base::OnceClosure callback,
ZipArchiverResultCode result_code) {
*test_result_code = result_code;
std::move(callback).Run();
}
void BindArchiverThenRun(base::win::ScopedHandle src_file_handle,
base::win::ScopedHandle zip_file_handle,
const std::string& filename_in_zip,
const std::string& password,
mojom::ZipArchiver::ArchiveCallback callback) {
scoped_refptr<MojoTaskRunner> task_runner = MojoTaskRunner::Create();
task_runner->PostTask(
FROM_HERE, base::BindOnce(RunArchiver, std::move(src_file_handle),
std::move(zip_file_handle), filename_in_zip,
password, std::move(callback)));
}
} // namespace
TEST_F(ZipArchiverImplTest, Archive) {
ZipArchiverResultCode test_result_code;
base::RunLoop loop;
BindArchiverThenRun(
std::move(src_file_handle_), std::move(zip_file_handle_),
kTestFilenameInZip, kTestPassword,
base::BindOnce(OnArchiveDone, &test_result_code, loop.QuitClosure()));
loop.Run();
EXPECT_EQ(test_result_code, ZipArchiverResultCode::kSuccess);
test_file_.ExpectValidZipFile(zip_file_path_, kTestFilenameInZip,
kTestPassword);
}
TEST_F(ZipArchiverImplTest, EmptyFilename) {
ZipArchiverResultCode test_result_code;
base::RunLoop loop;
BindArchiverThenRun(
std::move(src_file_handle_), std::move(zip_file_handle_), "",
kTestPassword,
base::BindOnce(OnArchiveDone, &test_result_code, loop.QuitClosure()));
loop.Run();
EXPECT_EQ(test_result_code, ZipArchiverResultCode::kErrorInvalidParameter);
}
TEST_F(ZipArchiverImplTest, EmptyPassword) {
ZipArchiverResultCode test_result_code;
base::RunLoop loop;
BindArchiverThenRun(
std::move(src_file_handle_), std::move(zip_file_handle_),
kTestFilenameInZip, "",
base::BindOnce(OnArchiveDone, &test_result_code, loop.QuitClosure()));
loop.Run();
EXPECT_EQ(test_result_code, ZipArchiverResultCode::kErrorInvalidParameter);
}
// TODO(veranika): Add a test with a too big source file once the limit is
// determined.
} // namespace chrome_cleaner
// Copyright 2018 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/chrome_cleaner/zip_archiver/test_zip_archiver_util.h"
#include <vector>
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/zlib/contrib/minizip/unzip.h"
namespace chrome_cleaner {
namespace {
const char kTestContent[] = "Hello World";
const base::Time::Exploded kTestFileTime = {
// Year
2018,
// Month
1,
// Day of week
1,
// Day of month
1,
// Hour
2,
// Minute
3,
// Second
5,
// Millisecond
0,
};
// The zip should be encrypted and use UTF-8 encoding.
const uint16_t kExpectedZipFlag = 0x1 | (0x1 << 11);
const int64_t kReadBufferSize = 4096;
// The |day_of_week| in |base::Time::Exploded| won't be set correctly.
// TODO(veranika): This is copied from
// https://cs.chromium.org/chromium/src/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive_minizip.cc.
// It would be better to move it to //base.
base::Time::Exploded ExplodeDosTime(const uint32_t dos_time) {
base::Time::Exploded time_part;
uint32_t remain_part = dos_time;
// 0-4bits, second divied by 2.
time_part.second = (remain_part & 0x1F) * 2;
remain_part >>= 5;
// 5-10bits, minute (0–59).
time_part.minute = remain_part & 0x3F;
remain_part >>= 6;
// 11-15bits, hour (0–23 on a 24-hour clock).
time_part.hour = remain_part & 0x1F;
remain_part >>= 5;
// 16-20bits, day of the month (1-31).
time_part.day_of_month = remain_part & 0x1F;
remain_part >>= 5;
// 21-24bits, month (1-23).
time_part.month = remain_part & 0xF;
remain_part >>= 4;
// 25-31bits, year offset from 1980.
time_part.year = (remain_part & 0x7F) + 1980;
return time_part;
}
} // namespace
ZipArchiverTestFile::ZipArchiverTestFile() : initialized_(false) {}
ZipArchiverTestFile::~ZipArchiverTestFile() = default;
void ZipArchiverTestFile::Initialize() {
// Can not be reinitialized.
CHECK(!initialized_);
initialized_ = true;
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(
base::CreateTemporaryFileInDir(temp_dir_.GetPath(), &src_file_path_));
ASSERT_EQ(static_cast<size_t>(base::WriteFile(src_file_path_, kTestContent,
strlen(kTestContent))),
strlen(kTestContent));
// Set a fixed timestamp, so the modified time will be identical in every
// test.
base::Time file_time;
ASSERT_TRUE(base::Time::FromLocalExploded(kTestFileTime, &file_time));
ASSERT_TRUE(base::TouchFile(src_file_path_, file_time, file_time));
}
const base::FilePath& ZipArchiverTestFile::GetSourceFilePath() const {
return src_file_path_;
}
const base::FilePath& ZipArchiverTestFile::GetTempDirPath() const {
return temp_dir_.GetPath();
}
void ZipArchiverTestFile::ExpectValidZipFile(
const base::FilePath& zip_file_path,
const std::string& filename_in_zip,
const std::string& password) {
unzFile unzip_object = unzOpen64(zip_file_path.AsUTF8Unsafe().c_str());
ASSERT_NE(unzip_object, nullptr);
EXPECT_EQ(unzLocateFile(unzip_object, filename_in_zip.c_str(),
/*iCaseSensitivity=*/1),
UNZ_OK);
unz_file_info64 file_info;
const size_t filename_length = strlen(filename_in_zip.c_str());
std::vector<char> filename(filename_length + 1);
EXPECT_EQ(unzGetCurrentFileInfo64(
unzip_object, &file_info, filename.data(), filename.size(),
/*extraField=*/nullptr, /*extraFieldBufferSize=*/0,
/*szComment=*/nullptr, /*commentBufferSize=*/0),
UNZ_OK);
EXPECT_EQ(file_info.flag & kExpectedZipFlag, kExpectedZipFlag);
// The compression method should be STORE(0).
EXPECT_EQ(file_info.compression_method, 0UL);
EXPECT_EQ(file_info.uncompressed_size, strlen(kTestContent));
EXPECT_EQ(file_info.size_filename, filename_in_zip.size());
EXPECT_STREQ(filename.data(), filename_in_zip.c_str());
base::Time::Exploded file_time = ExplodeDosTime(file_info.dosDate);
EXPECT_EQ(file_time.year, kTestFileTime.year);
EXPECT_EQ(file_time.month, kTestFileTime.month);
EXPECT_EQ(file_time.day_of_month, kTestFileTime.day_of_month);
EXPECT_EQ(file_time.hour, kTestFileTime.hour);
EXPECT_EQ(file_time.minute, kTestFileTime.minute);
// Dos time has a resolution of 2 seconds.
EXPECT_EQ(file_time.second, (kTestFileTime.second / 2) * 2);
EXPECT_EQ(unzOpenCurrentFilePassword(unzip_object, password.c_str()), UNZ_OK);
const size_t content_length = strlen(kTestContent);
std::vector<char> content;
uint8_t read_buffer[kReadBufferSize];
while (true) {
int read_size =
unzReadCurrentFile(unzip_object, read_buffer, kReadBufferSize);
EXPECT_GE(read_size, 0);
if (read_size == 0) {
break;
}
content.insert(content.end(), read_buffer, read_buffer + read_size);
if (content.size() > content_length) {
break;
}
}
EXPECT_EQ(content.size(), content_length);
EXPECT_EQ(std::string(content.begin(), content.end()), kTestContent);
EXPECT_EQ(unzCloseCurrentFile(unzip_object), UNZ_OK);
ASSERT_EQ(unzClose(unzip_object), UNZ_OK);
}
} // namespace chrome_cleaner
// Copyright 2018 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_CHROME_CLEANER_ZIP_ARCHIVER_TEST_ZIP_ARCHIVER_UTIL_H_
#define CHROME_CHROME_CLEANER_ZIP_ARCHIVER_TEST_ZIP_ARCHIVER_UTIL_H_
#include <string>
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/win/scoped_handle.h"
namespace chrome_cleaner {
class ZipArchiverTestFile {
public:
ZipArchiverTestFile();
~ZipArchiverTestFile();
void Initialize();
const base::FilePath& GetSourceFilePath() const;
const base::FilePath& GetTempDirPath() const;
void ExpectValidZipFile(const base::FilePath& zip_file_path,
const std::string& filename_in_zip,
const std::string& password);
private:
bool initialized_;
base::ScopedTempDir temp_dir_;
base::FilePath src_file_path_;
DISALLOW_COPY_AND_ASSIGN(ZipArchiverTestFile);
};
} // namespace chrome_cleaner
#endif // CHROME_CHROME_CLEANER_ZIP_ARCHIVER_TEST_ZIP_ARCHIVER_UTIL_H_
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