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") { ...@@ -43,6 +43,7 @@ test("chrome_cleaner_unittests") {
"//chrome/chrome_cleaner/strings:unittest_sources", "//chrome/chrome_cleaner/strings:unittest_sources",
"//chrome/chrome_cleaner/test:unittest_sources", "//chrome/chrome_cleaner/test:unittest_sources",
"//chrome/chrome_cleaner/ui:unittest_sources", "//chrome/chrome_cleaner/ui:unittest_sources",
"//chrome/chrome_cleaner/zip_archiver:unittest_sources",
] ]
} }
......
...@@ -198,6 +198,9 @@ bool CrashpadCrashClient::InitializeCrashReporting(Mode mode, ...@@ -198,6 +198,9 @@ bool CrashpadCrashClient::InitializeCrashReporting(Mode mode,
case SandboxType::kJsonParser: case SandboxType::kJsonParser:
SetCrashKey(kProcessType, "json_parser"); SetCrashKey(kProcessType, "json_parser");
break; break;
case SandboxType::kZipArchiver:
SetCrashKey(kProcessType, "zip_archiver");
break;
default: default:
NOTREACHED(); NOTREACHED();
} }
......
...@@ -40,6 +40,12 @@ chrome_cleaner_mojom("json_parser_interface") { ...@@ -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") { chrome_cleaner_mojom("engine_sandbox_test_interface") {
testonly = true 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 { ...@@ -246,6 +246,7 @@ message ProcessInformation {
DEPRECATED_SIGNATURE_MATCHER_SANDBOX = 2; DEPRECATED_SIGNATURE_MATCHER_SANDBOX = 2;
ESET_SANDBOX = 3; ESET_SANDBOX = 3;
JSON_PARSER_SANDBOX = 4; JSON_PARSER_SANDBOX = 4;
ZIP_ARCHIVER_SANDBOX = 5;
} }
optional Process process = 1; optional Process process = 1;
......
...@@ -253,6 +253,9 @@ ProcessInformation GetProcessInformationProtoObject( ...@@ -253,6 +253,9 @@ ProcessInformation GetProcessInformationProtoObject(
case SandboxType::kJsonParser: case SandboxType::kJsonParser:
process_info.set_process(ProcessInformation::JSON_PARSER_SANDBOX); process_info.set_process(ProcessInformation::JSON_PARSER_SANDBOX);
break; break;
case SandboxType::kZipArchiver:
process_info.set_process(ProcessInformation::ZIP_ARCHIVER_SANDBOX);
break;
default: default:
NOTREACHED() << "Unknown sandbox type " << static_cast<int>(process_type); NOTREACHED() << "Unknown sandbox type " << static_cast<int>(process_type);
} }
......
...@@ -15,6 +15,7 @@ enum class SandboxType { ...@@ -15,6 +15,7 @@ enum class SandboxType {
kTest, kTest,
kEset, kEset,
kJsonParser, kJsonParser,
kZipArchiver,
kNumValues, 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.
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_
This diff is collapsed.
// 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