summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrobertshield@chromium.org <robertshield@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-18 12:37:03 +0000
committerrobertshield@chromium.org <robertshield@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-18 12:37:03 +0000
commitff2cd6177c0a16648e2a3fa7c6151af140f30a82 (patch)
tree9cb93870a3a21ee866b329d55242d1a509c0eb51
parentf6b257352113d093138f129ba4c80cfae6bc6ea0 (diff)
downloadchromium_src-ff2cd6177c0a16648e2a3fa7c6151af140f30a82.zip
chromium_src-ff2cd6177c0a16648e2a3fa7c6151af140f30a82.tar.gz
chromium_src-ff2cd6177c0a16648e2a3fa7c6151af140f30a82.tar.bz2
Chrome browser process DLL blacklist.
This patch allows for blocking of module loading in the browser process. It does not actually prevent any modules from loading. BUG=329023 TEST=chrome_elf_unittests Review URL: https://codereview.chromium.org/107663008 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@241548 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/app/chrome_exe_main_win.cc5
-rw-r--r--chrome/chrome_exe.gypi3
-rw-r--r--chrome_elf/DEPS1
-rw-r--r--chrome_elf/blacklist.gypi60
-rw-r--r--chrome_elf/blacklist/blacklist.cc274
-rw-r--r--chrome_elf/blacklist/blacklist.h52
-rw-r--r--chrome_elf/blacklist/blacklist_interceptions.cc226
-rw-r--r--chrome_elf/blacklist/blacklist_interceptions.h33
-rw-r--r--chrome_elf/blacklist/test/blacklist_test.cc124
-rw-r--r--chrome_elf/blacklist/test/blacklist_test_dll_1.cc9
-rw-r--r--chrome_elf/blacklist/test/blacklist_test_dll_1.def5
-rw-r--r--chrome_elf/blacklist/test/blacklist_test_dll_2.cc19
-rw-r--r--chrome_elf/blacklist/test/blacklist_test_dll_2.def8
-rw-r--r--chrome_elf/blacklist/test/blacklist_test_dll_3.cc14
-rw-r--r--chrome_elf/blacklist/test/blacklist_test_dll_3.lib1
-rw-r--r--chrome_elf/blacklist/test/blacklist_test_main.cc17
-rw-r--r--chrome_elf/blacklist/test/blacklist_test_main_dll.cc17
-rw-r--r--chrome_elf/blacklist/test/blacklist_test_main_dll.def10
-rw-r--r--chrome_elf/blacklist/test/blacklist_test_main_dll.h10
-rw-r--r--chrome_elf/chrome_elf.def2
-rw-r--r--chrome_elf/chrome_elf.gyp23
-rw-r--r--chrome_elf/chrome_elf_main.cc14
-rw-r--r--chrome_elf/chrome_elf_main.h2
-rw-r--r--chrome_elf/version_assembly_manifest_action.gypi2
24 files changed, 918 insertions, 13 deletions
diff --git a/chrome/app/chrome_exe_main_win.cc b/chrome/app/chrome_exe_main_win.cc
index 68a2519..2dc2c94 100644
--- a/chrome/app/chrome_exe_main_win.cc
+++ b/chrome/app/chrome_exe_main_win.cc
@@ -128,9 +128,8 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prev, wchar_t*, int) {
if (AttemptFastNotify(*CommandLine::ForCurrentProcess()))
return 0;
- // The purpose of this call is to force the addition of an entry in the IAT
- // for chrome_elf.dll to force a load time dependency.
- InitChromeElf();
+ // Signal Chrome Elf that Chrome has begun to start.
+ SignalChromeElf();
MetroDriver metro_driver;
if (metro_driver.in_metro_mode())
diff --git a/chrome/chrome_exe.gypi b/chrome/chrome_exe.gypi
index abd63a3..c8f9cef 100644
--- a/chrome/chrome_exe.gypi
+++ b/chrome/chrome_exe.gypi
@@ -465,6 +465,8 @@
}],
['OS=="win"', {
'dependencies': [
+ # Note that chrome_elf must be listed first. Do not reorder it.
+ '../chrome_elf/chrome_elf.gyp:chrome_elf',
'chrome_dll',
'chrome_nacl_win64',
'chrome_process_finder',
@@ -474,7 +476,6 @@
'../base/base.gyp:base',
'../breakpad/breakpad.gyp:breakpad_handler',
'../breakpad/breakpad.gyp:breakpad_sender',
- '../chrome_elf/chrome_elf.gyp:chrome_elf',
'../components/components.gyp:breakpad_component',
'../components/components.gyp:policy',
'../sandbox/sandbox.gyp:sandbox',
diff --git a/chrome_elf/DEPS b/chrome_elf/DEPS
index 48e8875..ec69c8f 100644
--- a/chrome_elf/DEPS
+++ b/chrome_elf/DEPS
@@ -1,2 +1,3 @@
include_rules = [
+ "+sandbox",
]
diff --git a/chrome_elf/blacklist.gypi b/chrome_elf/blacklist.gypi
new file mode 100644
index 0000000..ab5597d
--- /dev/null
+++ b/chrome_elf/blacklist.gypi
@@ -0,0 +1,60 @@
+# Copyright 2013 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.
+{
+ 'targets': [
+ {
+ 'target_name': 'blacklist',
+ 'type': 'static_library',
+ 'sources': [
+ 'blacklist/blacklist.cc',
+ 'blacklist/blacklist.h',
+ 'blacklist/blacklist_interceptions.cc',
+ 'blacklist/blacklist_interceptions.h',
+ ],
+ 'dependencies': [
+ # Depend on base_static, but do NOT take a dependency on base.gyp:base
+ # as that would risk pulling in base's link-time dependencies which
+ # chrome_elf cannot do.
+ '../base/base.gyp:base_static',
+ '../sandbox/sandbox.gyp:sandbox',
+ ],
+ },
+ {
+ 'target_name': 'blacklist_test_main_dll',
+ 'type': 'shared_library',
+ 'sources': [
+ 'blacklist/test/blacklist_test_main_dll.cc',
+ 'blacklist/test/blacklist_test_main_dll.def',
+ ],
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'blacklist',
+ ],
+ },
+ {
+ 'target_name': 'blacklist_test_dll_1',
+ 'type': 'loadable_module',
+ 'sources': [
+ 'blacklist/test/blacklist_test_dll_1.cc',
+ 'blacklist/test/blacklist_test_dll_1.def',
+ ],
+ },
+ {
+ 'target_name': 'blacklist_test_dll_2',
+ 'type': 'loadable_module',
+ 'sources': [
+ 'blacklist/test/blacklist_test_dll_2.cc',
+ 'blacklist/test/blacklist_test_dll_2.def',
+ ],
+ },
+ {
+ 'target_name': 'blacklist_test_dll_3',
+ 'type': 'loadable_module',
+ 'sources': [
+ 'blacklist/test/blacklist_test_dll_3.cc',
+ ],
+ },
+ ],
+}
+
diff --git a/chrome_elf/blacklist/blacklist.cc b/chrome_elf/blacklist/blacklist.cc
new file mode 100644
index 0000000..f87c41a
--- /dev/null
+++ b/chrome_elf/blacklist/blacklist.cc
@@ -0,0 +1,274 @@
+// Copyright 2013 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_elf/blacklist/blacklist.h"
+
+#include <string.h>
+
+#include "base/basictypes.h"
+#include "chrome_elf/blacklist/blacklist_interceptions.h"
+#include "sandbox/win/src/interception_internal.h"
+#include "sandbox/win/src/internal_types.h"
+#include "sandbox/win/src/sandbox_utils.h"
+#include "sandbox/win/src/service_resolver.h"
+
+// http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+
+namespace blacklist{
+
+const wchar_t* g_troublesome_dlls[kTroublesomeDllsMaxCount] = {};
+int g_troublesome_dlls_cur_index = 0;
+
+const wchar_t kRegistryBeaconPath[] = L"SOFTWARE\\Google\\Chrome\\BLBeacon";
+
+} // namespace blacklist
+
+// Allocate storage for thunks in a RWX page of this module to save on doing
+// an extra allocation at run time.
+#if !defined(_WIN64)
+// 64-bit images appear to not support writeable and executable pages.
+// This would yield compile warning C4330.
+// TODO(robertshield): Add 64 bit support.
+#pragma section(".crthunk",read,write,execute)
+__declspec(allocate(".crthunk")) sandbox::ThunkData g_thunk_storage;
+#endif
+
+namespace {
+
+enum Version {
+ VERSION_PRE_XP_SP2 = 0, // Not supported.
+ VERSION_XP_SP2,
+ VERSION_SERVER_2003, // Also includes XP Pro x64 and Server 2003 R2.
+ VERSION_VISTA, // Also includes Windows Server 2008.
+ VERSION_WIN7, // Also includes Windows Server 2008 R2.
+ VERSION_WIN8, // Also includes Windows Server 2012.
+ VERSION_WIN8_1,
+ VERSION_WIN_LAST, // Indicates error condition.
+};
+
+// Whether a process is running under WOW64 (the wrapper that allows 32-bit
+// processes to run on 64-bit versions of Windows). This will return
+// WOW64_DISABLED for both "32-bit Chrome on 32-bit Windows" and "64-bit
+// Chrome on 64-bit Windows". WOW64_UNKNOWN means "an error occurred", e.g.
+// the process does not have sufficient access rights to determine this.
+enum WOW64Status {
+ WOW64_DISABLED,
+ WOW64_ENABLED,
+ WOW64_UNKNOWN,
+};
+
+WOW64Status GetWOW64StatusForCurrentProcess() {
+ typedef BOOL (WINAPI* IsWow64ProcessFunc)(HANDLE, PBOOL);
+ IsWow64ProcessFunc is_wow64_process = reinterpret_cast<IsWow64ProcessFunc>(
+ GetProcAddress(GetModuleHandle(L"kernel32.dll"), "IsWow64Process"));
+ if (!is_wow64_process)
+ return WOW64_DISABLED;
+ BOOL is_wow64 = FALSE;
+ if (!(*is_wow64_process)(GetCurrentProcess(), &is_wow64))
+ return WOW64_UNKNOWN;
+ return is_wow64 ? WOW64_ENABLED : WOW64_DISABLED;
+}
+
+class OSInfo {
+ public:
+ struct VersionNumber {
+ int major;
+ int minor;
+ int build;
+ };
+
+ struct ServicePack {
+ int major;
+ int minor;
+ };
+
+ OSInfo() {
+ OSVERSIONINFOEX version_info = { sizeof(version_info) };
+ GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info));
+ version_number_.major = version_info.dwMajorVersion;
+ version_number_.minor = version_info.dwMinorVersion;
+ version_number_.build = version_info.dwBuildNumber;
+ if ((version_number_.major == 5) && (version_number_.minor > 0)) {
+ // Treat XP Pro x64, Home Server, and Server 2003 R2 as Server 2003.
+ version_ = (version_number_.minor == 1) ? VERSION_XP_SP2 :
+ VERSION_SERVER_2003;
+ if (version_ == VERSION_XP_SP2 && version_info.wServicePackMajor < 2)
+ version_ = VERSION_PRE_XP_SP2;
+ } else if (version_number_.major == 6) {
+ switch (version_number_.minor) {
+ case 0:
+ // Treat Windows Server 2008 the same as Windows Vista.
+ version_ = VERSION_VISTA;
+ break;
+ case 1:
+ // Treat Windows Server 2008 R2 the same as Windows 7.
+ version_ = VERSION_WIN7;
+ break;
+ case 2:
+ // Treat Windows Server 2012 the same as Windows 8.
+ version_ = VERSION_WIN8;
+ break;
+ default:
+ version_ = VERSION_WIN8_1;
+ break;
+ }
+ } else if (version_number_.major > 6) {
+ version_ = VERSION_WIN_LAST;
+ } else {
+ version_ = VERSION_PRE_XP_SP2;
+ }
+
+ service_pack_.major = version_info.wServicePackMajor;
+ service_pack_.minor = version_info.wServicePackMinor;
+ }
+
+ Version version() const { return version_; }
+ VersionNumber version_number() const { return version_number_; }
+ ServicePack service_pack() const { return service_pack_; }
+
+ private:
+ Version version_;
+ VersionNumber version_number_;
+ ServicePack service_pack_;
+
+ DISALLOW_COPY_AND_ASSIGN(OSInfo);
+};
+
+bool IsNonBrowserProcess() {
+ wchar_t* command_line = GetCommandLine();
+ return (command_line && wcsstr(command_line, L"--type"));
+}
+
+} // namespace
+
+namespace blacklist {
+
+bool CreateBeacon() {
+ HKEY beacon_key = NULL;
+ DWORD disposition = 0;
+ LONG result = ::RegCreateKeyEx(HKEY_CURRENT_USER,
+ kRegistryBeaconPath,
+ 0,
+ NULL,
+ 0,
+ KEY_WRITE,
+ NULL,
+ &beacon_key,
+ &disposition);
+ bool success = (result == ERROR_SUCCESS &&
+ disposition != REG_OPENED_EXISTING_KEY);
+ if (result == ERROR_SUCCESS)
+ ::RegCloseKey(beacon_key);
+ return success;
+}
+
+bool ClearBeacon() {
+ LONG result = ::RegDeleteKey(HKEY_CURRENT_USER, kRegistryBeaconPath);
+ return (result == ERROR_SUCCESS);
+}
+
+bool AddDllToBlacklist(const wchar_t* dll_name) {
+ if (g_troublesome_dlls_cur_index >= kTroublesomeDllsMaxCount)
+ return false;
+ for (int i = 0; i < g_troublesome_dlls_cur_index; ++i) {
+ if (!wcscmp(g_troublesome_dlls[i], dll_name))
+ return true;
+ }
+
+ // Copy string to blacklist.
+ wchar_t* str_buffer = new wchar_t[wcslen(dll_name) + 1];
+ wcscpy(str_buffer, dll_name);
+
+ g_troublesome_dlls[g_troublesome_dlls_cur_index] = str_buffer;
+ g_troublesome_dlls_cur_index++;
+ return true;
+}
+
+bool RemoveDllFromBlacklist(const wchar_t* dll_name) {
+ for (int i = 0; i < g_troublesome_dlls_cur_index; ++i) {
+ if (!wcscmp(g_troublesome_dlls[i], dll_name)) {
+ // Found the thing to remove. Delete it then replace it with the last
+ // element.
+ g_troublesome_dlls_cur_index--;
+ delete[] g_troublesome_dlls[i];
+ g_troublesome_dlls[i] = g_troublesome_dlls[g_troublesome_dlls_cur_index];
+ g_troublesome_dlls[g_troublesome_dlls_cur_index] = NULL;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Initialize(bool force) {
+#if defined(_WIN64)
+ // TODO(robertshield): Implement 64-bit support by providing 64-bit
+ // interceptors.
+ return false;
+#endif
+
+ // Check to see that we found the functions we need in ntdll.
+ if (!InitializeInterceptImports())
+ return false;
+
+ // Check to see if this is a non-browser process, abort if so.
+ if (IsNonBrowserProcess())
+ return false;
+
+ // Check to see if a beacon is present, abort if so.
+ if (!force && !CreateBeacon())
+ return false;
+
+ // Don't try blacklisting on unsupported OS versions.
+ OSInfo os_info;
+ if (os_info.version() <= VERSION_PRE_XP_SP2)
+ return false;
+
+ // Pseudo-handle, no need to close.
+ HANDLE current_process = ::GetCurrentProcess();
+
+ // Tells the resolver to patch already patched functions.
+ const bool kRelaxed = true;
+
+ // Create a thunk via the appropriate ServiceResolver instance.
+ sandbox::ServiceResolverThunk* thunk;
+#if defined(_WIN64)
+ // TODO(robertshield): Use the appropriate thunk for 64-bit support
+ // when said support is implemented.
+#else
+ if (GetWOW64StatusForCurrentProcess() == WOW64_ENABLED) {
+ if (os_info.version() >= VERSION_WIN8)
+ thunk = new sandbox::Wow64W8ResolverThunk(current_process, kRelaxed);
+ else
+ thunk = new sandbox::Wow64ResolverThunk(current_process, kRelaxed);
+ } else if (os_info.version() >= VERSION_WIN8) {
+ thunk = new sandbox::Win8ResolverThunk(current_process, kRelaxed);
+ } else {
+ thunk = new sandbox::ServiceResolverThunk(current_process, kRelaxed);
+ }
+#endif
+
+#if defined(_WIN64)
+ BYTE* thunk_storage = new BYTE[sizeof(sandbox::ThunkData)];
+#else
+ BYTE* thunk_storage = reinterpret_cast<BYTE*>(&g_thunk_storage);
+#endif
+
+ thunk->AllowLocalPatches();
+
+ // Get ntdll base, target name, interceptor address,
+ NTSTATUS ret = thunk->Setup(::GetModuleHandle(sandbox::kNtdllName),
+ reinterpret_cast<void*>(&__ImageBase),
+ "NtMapViewOfSection",
+ NULL,
+ &blacklist::BlNtMapViewOfSection,
+ thunk_storage,
+ sizeof(sandbox::ThunkData),
+ NULL);
+
+ delete thunk;
+ return NT_SUCCESS(ret);
+}
+
+} // namespace blacklist
diff --git a/chrome_elf/blacklist/blacklist.h b/chrome_elf/blacklist/blacklist.h
new file mode 100644
index 0000000..5787ddd
--- /dev/null
+++ b/chrome_elf/blacklist/blacklist.h
@@ -0,0 +1,52 @@
+// Copyright 2013 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_ELF_BLACKLIST_BLACKLIST_H_
+#define CHROME_ELF_BLACKLIST_BLACKLIST_H_
+
+namespace blacklist {
+
+// Max size of the DLL blacklist.
+const int kTroublesomeDllsMaxCount = 64;
+
+// The DLL blacklist.
+extern const wchar_t* g_troublesome_dlls[kTroublesomeDllsMaxCount];
+
+// Cursor to the current last element in the blacklist.
+extern int g_troublesome_dlls_cur_index;
+
+// The registry path of the blacklist beacon. Exposed here for testing.
+extern const wchar_t kRegistryBeaconPath[];
+
+// Attempts to create a beacon in the current user's registry hive.
+// If the beacon already exists or any other error occurs when creating the
+// beacon, returns false. Otherwise returns true.
+// The intent of the beacon is to act as an extra failure mode protection
+// whereby if Chrome for some reason fails to start during blacklist setup,
+// it will skip blacklisting on the subsequent run.
+bool CreateBeacon();
+
+// Looks for the beacon that CreateBeacon() creates and attempts to delete it.
+// Returns true if the beacon was found and deleted.
+bool ClearBeacon();
+
+// Adds the given dll name to the blacklist. Returns true if the dll name is in
+// the blacklist when this returns, false on error. Note that this will copy
+// |dll_name| and will leak it on exit if the string is not subsequently removed
+// using RemoveDllFromBlacklist.
+extern "C" bool AddDllToBlacklist(const wchar_t* dll_name);
+
+// Removes the given dll name from the blacklist. Returns true if it was
+// removed, false on error.
+extern "C" bool RemoveDllFromBlacklist(const wchar_t* dll_name);
+
+// Initializes the DLL blacklist in the current process. This should be called
+// before any undesirable DLLs might be loaded. If |force| is set to true, then
+// initialization will take place even if a beacon is present. This is useful
+// for tests.
+bool Initialize(bool force);
+
+} // namespace blacklist
+
+#endif // CHROME_ELF_BLACKLIST_BLACKLIST_H_
diff --git a/chrome_elf/blacklist/blacklist_interceptions.cc b/chrome_elf/blacklist/blacklist_interceptions.cc
new file mode 100644
index 0000000..477fc1d
--- /dev/null
+++ b/chrome_elf/blacklist/blacklist_interceptions.cc
@@ -0,0 +1,226 @@
+// Copyright 2013 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.
+//
+// Implementation of NtMapViewOfSection intercept for 32 bit builds.
+//
+// TODO(robertshield): Implement the 64 bit intercept.
+
+#include "chrome_elf/blacklist/blacklist_interceptions.h"
+
+#include <string>
+#include <vector>
+
+// Note that only #includes from base that are either header-only or built into
+// base_static (see base/base.gyp) are allowed here.
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "base/win/pe_image.h"
+#include "chrome_elf/blacklist/blacklist.h"
+#include "sandbox/win/src/internal_types.h"
+#include "sandbox/win/src/nt_internals.h"
+#include "sandbox/win/src/sandbox_nt_util.h"
+#include "sandbox/win/src/sandbox_types.h"
+
+namespace {
+
+NtQuerySectionFunction g_nt_query_section_func = NULL;
+NtQueryVirtualMemoryFunction g_nt_query_virtual_memory_func = NULL;
+NtUnmapViewOfSectionFunction g_nt_unmap_view_of_section_func = NULL;
+
+// TODO(robertshield): Merge with ntdll exports cache.
+FARPROC GetNtDllExportByName(const char* export_name) {
+ HMODULE ntdll = ::GetModuleHandle(sandbox::kNtdllName);
+ return ::GetProcAddress(ntdll, export_name);
+}
+
+bool DllMatch(const string16& module_name) {
+ for (int i = 0; i < blacklist::g_troublesome_dlls_cur_index; ++i) {
+ if (module_name == blacklist::g_troublesome_dlls[i])
+ return true;
+ }
+ return false;
+}
+
+// TODO(robertshield): Some of the helper functions below overlap somewhat with
+// code in sandbox_nt_util.cc. See if they can be unified.
+
+// Native reimplementation of PSAPIs GetMappedFileName.
+string16 GetBackingModuleFilePath(PVOID address) {
+ DCHECK_NT(g_nt_query_virtual_memory_func);
+
+ // We'll start with something close to max_path characters for the name.
+ ULONG buffer_bytes = MAX_PATH * 2;
+ std::vector<BYTE> buffer_data(buffer_bytes);
+
+ for (;;) {
+ MEMORY_SECTION_NAME* section_name =
+ reinterpret_cast<MEMORY_SECTION_NAME*>(&buffer_data[0]);
+
+ if (!section_name)
+ break;
+
+ ULONG returned_bytes;
+ NTSTATUS ret = g_nt_query_virtual_memory_func(
+ NtCurrentProcess, address, MemorySectionName, section_name,
+ buffer_bytes, &returned_bytes);
+
+ if (STATUS_BUFFER_OVERFLOW == ret) {
+ // Retry the call with the given buffer size.
+ buffer_bytes = returned_bytes + 1;
+ buffer_data.resize(buffer_bytes);
+ section_name = NULL;
+ continue;
+ }
+ if (!NT_SUCCESS(ret))
+ break;
+
+ UNICODE_STRING* section_string =
+ reinterpret_cast<UNICODE_STRING*>(section_name);
+ return string16(section_string->Buffer,
+ section_string->Length / sizeof(wchar_t));
+ }
+
+ return string16();
+}
+
+bool IsModuleValidImageSection(HANDLE section,
+ PVOID *base,
+ PLARGE_INTEGER offset,
+ PSIZE_T view_size) {
+ DCHECK_NT(g_nt_query_section_func);
+
+ if (!section || !base || !view_size || offset)
+ return false;
+
+ SECTION_BASIC_INFORMATION basic_info;
+ SIZE_T bytes_returned;
+ NTSTATUS ret = g_nt_query_section_func(section, SectionBasicInformation,
+ &basic_info, sizeof(basic_info),
+ &bytes_returned);
+
+ if (!NT_SUCCESS(ret) || sizeof(basic_info) != bytes_returned)
+ return false;
+
+ if (!(basic_info.Attributes & SEC_IMAGE))
+ return false;
+
+ return true;
+}
+
+string16 ExtractLoadedModuleName(const string16& module_path) {
+ if (module_path.empty() || module_path[module_path.size() - 1] == L'\\')
+ return string16();
+
+ size_t sep = module_path.find_last_of(L'\\');
+ if (sep == string16::npos)
+ return module_path;
+ else
+ return module_path.substr(sep+1);
+}
+
+// Fills |out_name| with the image name from the given |pe| image and |flags|
+// with additional info about the image.
+void SafeGetImageInfo(const base::win::PEImage& pe,
+ std::string* out_name,
+ uint32* flags) {
+ out_name->clear();
+ out_name->reserve(MAX_PATH);
+ *flags = 0;
+ __try {
+ if (pe.VerifyMagic()) {
+ *flags |= sandbox::MODULE_IS_PE_IMAGE;
+
+ PIMAGE_EXPORT_DIRECTORY exports = pe.GetExportDirectory();
+ if (exports) {
+ char* image_name = reinterpret_cast<char*>(pe.RVAToAddr(exports->Name));
+ size_t i = 0;
+ for (; i < MAX_PATH && *image_name; ++i, ++image_name)
+ out_name->push_back(*image_name);
+ }
+
+ PIMAGE_NT_HEADERS headers = pe.GetNTHeaders();
+ if (headers) {
+ if (headers->OptionalHeader.AddressOfEntryPoint)
+ *flags |= sandbox::MODULE_HAS_ENTRY_POINT;
+ if (headers->OptionalHeader.SizeOfCode)
+ *flags |= sandbox::MODULE_HAS_CODE;
+ }
+ }
+ } __except(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?
+ EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
+ out_name->clear();
+ }
+}
+
+string16 GetImageInfoFromLoadedModule(HMODULE module, uint32* flags) {
+ std::string out_name;
+ base::win::PEImage pe(module);
+ SafeGetImageInfo(pe, &out_name, flags);
+ return string16(out_name.begin(), out_name.end());
+}
+
+} // namespace
+
+namespace blacklist {
+
+bool InitializeInterceptImports() {
+ g_nt_query_section_func = reinterpret_cast<NtQuerySectionFunction>(
+ GetNtDllExportByName("NtQuerySection"));
+ g_nt_query_virtual_memory_func =
+ reinterpret_cast<NtQueryVirtualMemoryFunction>(
+ GetNtDllExportByName("NtQueryVirtualMemory"));
+ g_nt_unmap_view_of_section_func =
+ reinterpret_cast<NtUnmapViewOfSectionFunction>(
+ GetNtDllExportByName("NtUnmapViewOfSection"));
+
+ return g_nt_query_section_func && g_nt_query_virtual_memory_func &&
+ g_nt_unmap_view_of_section_func;
+}
+
+SANDBOX_INTERCEPT NTSTATUS WINAPI BlNtMapViewOfSection(
+ NtMapViewOfSectionFunction orig_MapViewOfSection,
+ HANDLE section,
+ HANDLE process,
+ PVOID *base,
+ ULONG_PTR zero_bits,
+ SIZE_T commit_size,
+ PLARGE_INTEGER offset,
+ PSIZE_T view_size,
+ SECTION_INHERIT inherit,
+ ULONG allocation_type,
+ ULONG protect) {
+ NTSTATUS ret = orig_MapViewOfSection(section, process, base, zero_bits,
+ commit_size, offset, view_size, inherit,
+ allocation_type, protect);
+
+ if (!NT_SUCCESS(ret) || !sandbox::IsSameProcess(process) ||
+ !IsModuleValidImageSection(section, base, offset, view_size)) {
+ return ret;
+ }
+
+ HMODULE module = reinterpret_cast<HMODULE>(*base);
+ if (module) {
+ UINT image_flags;
+
+ string16 module_name(GetImageInfoFromLoadedModule(
+ reinterpret_cast<HMODULE>(*base), &image_flags));
+ string16 file_name(GetBackingModuleFilePath(*base));
+
+ if (module_name.empty() && (image_flags & sandbox::MODULE_HAS_CODE)) {
+ // If the module has no exports we retrieve the module name from the
+ // full path of the mapped section.
+ module_name = ExtractLoadedModuleName(file_name);
+ }
+
+ if (!module_name.empty() && DllMatch(module_name)) {
+ DCHECK_NT(g_nt_unmap_view_of_section_func);
+ g_nt_unmap_view_of_section_func(process, *base);
+ ret = STATUS_UNSUCCESSFUL;
+ }
+
+ }
+ return ret;
+}
+
+} // namespace blacklist
diff --git a/chrome_elf/blacklist/blacklist_interceptions.h b/chrome_elf/blacklist/blacklist_interceptions.h
new file mode 100644
index 0000000..dfb4495
--- /dev/null
+++ b/chrome_elf/blacklist/blacklist_interceptions.h
@@ -0,0 +1,33 @@
+// Copyright 2013 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_ELF_BLACKLIST_BLACKLIST_INTERCEPTIONS_H_
+#define CHROME_ELF_BLACKLIST_BLACKLIST_INTERCEPTIONS_H_
+
+#include "sandbox/win/src/nt_internals.h"
+#include "sandbox/win/src/sandbox_types.h"
+
+namespace blacklist {
+
+bool InitializeInterceptImports();
+
+// Interception of NtMapViewOfSection within the current process.
+// It should never be called directly. This function provides the means to
+// detect dlls being loaded, so we can patch them if needed.
+SANDBOX_INTERCEPT NTSTATUS WINAPI BlNtMapViewOfSection(
+ NtMapViewOfSectionFunction orig_MapViewOfSection,
+ HANDLE section,
+ HANDLE process,
+ PVOID *base,
+ ULONG_PTR zero_bits,
+ SIZE_T commit_size,
+ PLARGE_INTEGER offset,
+ PSIZE_T view_size,
+ SECTION_INHERIT inherit,
+ ULONG allocation_type,
+ ULONG protect);
+
+} // namespace blacklist
+
+#endif // CHROME_ELF_BLACKLIST_BLACKLIST_INTERCEPTIONS_H_
diff --git a/chrome_elf/blacklist/test/blacklist_test.cc b/chrome_elf/blacklist/test/blacklist_test.cc
new file mode 100644
index 0000000..b0b428b
--- /dev/null
+++ b/chrome_elf/blacklist/test/blacklist_test.cc
@@ -0,0 +1,124 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/environment.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/scoped_native_library.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/test_reg_util_win.h"
+#include "chrome_elf/blacklist/blacklist.h"
+#include "chrome_elf/blacklist/test/blacklist_test_main_dll.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+const wchar_t kTestDllName1[] = L"blacklist_test_dll_1.dll";
+const wchar_t kTestDllName2[] = L"blacklist_test_dll_2.dll";
+const wchar_t kTestDllName3[] = L"blacklist_test_dll_3.dll";
+
+const wchar_t kDll2Beacon[] = L"{F70A0100-2889-4629-9B44-610FE5C73231}";
+const wchar_t kDll3Beacon[] = L"{9E056AEC-169E-400c-B2D0-5A07E3ACE2EB}";
+
+extern const wchar_t* kEnvVars[];
+
+extern "C" {
+// When modifying the blacklist in the test process, use the exported test dll
+// functions on the test blacklist dll, not the ones linked into the test
+// executable itself.
+__declspec(dllimport) void TestDll_AddDllToBlacklist(const wchar_t* dll_name);
+__declspec(dllimport) void TestDll_RemoveDllFromBlacklist(
+ const wchar_t* dll_name);
+}
+
+class BlacklistTest : public testing::Test {
+ virtual void SetUp() {
+ // Force an import from blacklist_test_main_dll.
+ InitBlacklistTestDll();
+
+ // Ensure that the beacon state starts off cleared.
+ blacklist::ClearBeacon();
+ }
+
+ virtual void TearDown() {
+ TestDll_RemoveDllFromBlacklist(kTestDllName1);
+ TestDll_RemoveDllFromBlacklist(kTestDllName2);
+ }
+};
+
+TEST_F(BlacklistTest, Beacon) {
+ registry_util::RegistryOverrideManager override_manager;
+ override_manager.OverrideRegistry(HKEY_CURRENT_USER, L"beacon_test");
+
+ // First call should succeed as the beacon is newly created.
+ EXPECT_TRUE(blacklist::CreateBeacon());
+
+ // Second call should fail indicating the beacon already existed.
+ EXPECT_FALSE(blacklist::CreateBeacon());
+
+ // First call should find the beacon and delete it.
+ EXPECT_TRUE(blacklist::ClearBeacon());
+
+ // Second call should fail to find the beacon and delete it.
+ EXPECT_FALSE(blacklist::ClearBeacon());
+}
+
+TEST_F(BlacklistTest, AddAndRemoveModules) {
+ EXPECT_TRUE(blacklist::AddDllToBlacklist(L"foo.dll"));
+ // Adding the same item twice should be idempotent.
+ EXPECT_TRUE(blacklist::AddDllToBlacklist(L"foo.dll"));
+ EXPECT_TRUE(blacklist::RemoveDllFromBlacklist(L"foo.dll"));
+ EXPECT_FALSE(blacklist::RemoveDllFromBlacklist(L"foo.dll"));
+
+ std::vector<string16> added_dlls;
+ added_dlls.reserve(blacklist::kTroublesomeDllsMaxCount);
+ for (int i = 0; i < blacklist::kTroublesomeDllsMaxCount; ++i) {
+ added_dlls.push_back(base::IntToString16(i) + L".dll");
+ EXPECT_TRUE(blacklist::AddDllToBlacklist(added_dlls[i].c_str())) << i;
+ }
+ EXPECT_FALSE(blacklist::AddDllToBlacklist(L"overflow.dll"));
+ for (int i = 0; i < blacklist::kTroublesomeDllsMaxCount; ++i) {
+ EXPECT_TRUE(blacklist::RemoveDllFromBlacklist(added_dlls[i].c_str())) << i;
+ }
+ EXPECT_FALSE(blacklist::RemoveDllFromBlacklist(L"0.dll"));
+ EXPECT_FALSE(blacklist::RemoveDllFromBlacklist(L"63.dll"));
+}
+
+TEST_F(BlacklistTest, LoadBlacklistedLibrary) {
+ base::FilePath current_dir;
+ ASSERT_TRUE(PathService::Get(base::DIR_EXE, &current_dir));
+
+ // Test that an un-blacklisted DLL can load correctly.
+ base::ScopedNativeLibrary dll1(current_dir.Append(kTestDllName1));
+ EXPECT_TRUE(dll1.is_valid());
+ dll1.Reset(NULL);
+
+ struct TestData {
+ const wchar_t* dll_name;
+ const wchar_t* dll_beacon;
+ } test_data[] = {
+ { kTestDllName2, kDll2Beacon },
+ { kTestDllName3, kDll3Beacon }
+ };
+ for (int i = 0 ; i < arraysize(test_data); ++i) {
+ // Add the DLL to the blacklist, ensure that it is not loaded both by
+ // inspecting the handle returned by LoadLibrary and by looking for an
+ // environment variable that is set when the DLL's entry point is called.
+ TestDll_AddDllToBlacklist(test_data[i].dll_name);
+ base::ScopedNativeLibrary dll_blacklisted(
+ current_dir.Append(test_data[i].dll_name));
+ EXPECT_FALSE(dll_blacklisted.is_valid());
+ EXPECT_EQ(0u, ::GetEnvironmentVariable(test_data[i].dll_beacon, NULL, 0));
+ dll_blacklisted.Reset(NULL);
+
+ // Remove the DLL from the blacklist. Ensure that it loads and that its
+ // entry point was called.
+ TestDll_RemoveDllFromBlacklist(test_data[i].dll_name);
+ base::ScopedNativeLibrary dll(current_dir.Append(test_data[i].dll_name));
+ EXPECT_TRUE(dll.is_valid());
+ EXPECT_NE(0u, ::GetEnvironmentVariable(test_data[i].dll_beacon, NULL, 0));
+ dll.Reset(NULL);
+ }
+}
diff --git a/chrome_elf/blacklist/test/blacklist_test_dll_1.cc b/chrome_elf/blacklist/test/blacklist_test_dll_1.cc
new file mode 100644
index 0000000..a4e414d
--- /dev/null
+++ b/chrome_elf/blacklist/test/blacklist_test_dll_1.cc
@@ -0,0 +1,9 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <windows.h>
+
+BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved) {
+ return TRUE;
+}
diff --git a/chrome_elf/blacklist/test/blacklist_test_dll_1.def b/chrome_elf/blacklist/test/blacklist_test_dll_1.def
new file mode 100644
index 0000000..fc7b7be
--- /dev/null
+++ b/chrome_elf/blacklist/test/blacklist_test_dll_1.def
@@ -0,0 +1,5 @@
+; Copyright 2013 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.
+
+LIBRARY "blacklist_test_dll_1.dll"
diff --git a/chrome_elf/blacklist/test/blacklist_test_dll_2.cc b/chrome_elf/blacklist/test/blacklist_test_dll_2.cc
new file mode 100644
index 0000000..7a4a7dc
--- /dev/null
+++ b/chrome_elf/blacklist/test/blacklist_test_dll_2.cc
@@ -0,0 +1,19 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <windows.h>
+
+const wchar_t kDll2Beacon[] = L"{F70A0100-2889-4629-9B44-610FE5C73231}";
+
+extern "C" {
+// Have a dummy export so that the module gets an export table entry.
+void DummyExport() {}
+}
+
+BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved) {
+ if (reason == DLL_PROCESS_ATTACH) {
+ ::SetEnvironmentVariable(kDll2Beacon, L"1");
+ }
+ return TRUE;
+}
diff --git a/chrome_elf/blacklist/test/blacklist_test_dll_2.def b/chrome_elf/blacklist/test/blacklist_test_dll_2.def
new file mode 100644
index 0000000..2cc122f
--- /dev/null
+++ b/chrome_elf/blacklist/test/blacklist_test_dll_2.def
@@ -0,0 +1,8 @@
+; Copyright 2013 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.
+
+LIBRARY "blacklist_test_dll_2.dll"
+
+EXPORTS
+ DummyExport
diff --git a/chrome_elf/blacklist/test/blacklist_test_dll_3.cc b/chrome_elf/blacklist/test/blacklist_test_dll_3.cc
new file mode 100644
index 0000000..b4a6959
--- /dev/null
+++ b/chrome_elf/blacklist/test/blacklist_test_dll_3.cc
@@ -0,0 +1,14 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <windows.h>
+
+const wchar_t kDll3Beacon[] = L"{9E056AEC-169E-400c-B2D0-5A07E3ACE2EB}";
+
+BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved) {
+ if (reason == DLL_PROCESS_ATTACH) {
+ ::SetEnvironmentVariable(kDll3Beacon, L"1");
+ }
+ return TRUE;
+}
diff --git a/chrome_elf/blacklist/test/blacklist_test_dll_3.lib b/chrome_elf/blacklist/test/blacklist_test_dll_3.lib
new file mode 100644
index 0000000..7ab9a11
--- /dev/null
+++ b/chrome_elf/blacklist/test/blacklist_test_dll_3.lib
@@ -0,0 +1 @@
+LIBRARY "blacklist_test_dll_2.dll"
diff --git a/chrome_elf/blacklist/test/blacklist_test_main.cc b/chrome_elf/blacklist/test/blacklist_test_main.cc
new file mode 100644
index 0000000..c84b5ad
--- /dev/null
+++ b/chrome_elf/blacklist/test/blacklist_test_main.cc
@@ -0,0 +1,17 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/at_exit.h"
+#include "chrome_elf/blacklist/test/blacklist_test_main_dll.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+
+ base::AtExitManager at_exit_manager;
+
+ InitBlacklistTestDll();
+
+ RUN_ALL_TESTS();
+}
diff --git a/chrome_elf/blacklist/test/blacklist_test_main_dll.cc b/chrome_elf/blacklist/test/blacklist_test_main_dll.cc
new file mode 100644
index 0000000..54e5eb8
--- /dev/null
+++ b/chrome_elf/blacklist/test/blacklist_test_main_dll.cc
@@ -0,0 +1,17 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <windows.h>
+
+#include "chrome_elf/blacklist/blacklist.h"
+
+extern "C" void InitBlacklistTestDll() {}
+
+BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved) {
+ if (reason == DLL_PROCESS_ATTACH) {
+ blacklist::Initialize(true); // force always on, no beacon
+ }
+
+ return TRUE;
+}
diff --git a/chrome_elf/blacklist/test/blacklist_test_main_dll.def b/chrome_elf/blacklist/test/blacklist_test_main_dll.def
new file mode 100644
index 0000000..63522a0
--- /dev/null
+++ b/chrome_elf/blacklist/test/blacklist_test_main_dll.def
@@ -0,0 +1,10 @@
+; Copyright 2013 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.
+
+LIBRARY "blacklist_test_main_dll.dll"
+
+EXPORTS
+ TestDll_AddDllToBlacklist=AddDllToBlacklist
+ TestDll_RemoveDllFromBlacklist=RemoveDllFromBlacklist
+ InitBlacklistTestDll
diff --git a/chrome_elf/blacklist/test/blacklist_test_main_dll.h b/chrome_elf/blacklist/test/blacklist_test_main_dll.h
new file mode 100644
index 0000000..a004f9b
--- /dev/null
+++ b/chrome_elf/blacklist/test/blacklist_test_main_dll.h
@@ -0,0 +1,10 @@
+// Copyright 2013 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_ELF_BLACKLIST_TEST_BLACKLIST_TEST_MAIN_DLL_H_
+#define CHROME_ELF_BLACKLIST_TEST_BLACKLIST_TEST_MAIN_DLL_H_
+
+extern "C" void InitBlacklistTestDll();
+
+#endif // CHROME_ELF_BLACKLIST_TEST_BLACKLIST_TEST_MAIN_DLL_H_
diff --git a/chrome_elf/chrome_elf.def b/chrome_elf/chrome_elf.def
index d3ca82f..d7d4c9c 100644
--- a/chrome_elf/chrome_elf.def
+++ b/chrome_elf/chrome_elf.def
@@ -5,4 +5,4 @@
LIBRARY "chrome_elf.dll"
EXPORTS
- InitChromeElf
+ SignalChromeElf
diff --git a/chrome_elf/chrome_elf.gyp b/chrome_elf/chrome_elf.gyp
index cf0a0e9..b0193ba 100644
--- a/chrome_elf/chrome_elf.gyp
+++ b/chrome_elf/chrome_elf.gyp
@@ -8,6 +8,7 @@
'includes': [
'../build/win_precompile.gypi',
'../chrome/version.gypi',
+ 'blacklist.gypi',
],
'targets': [
{
@@ -22,6 +23,7 @@
'chrome_elf_main.h',
],
'dependencies': [
+ 'blacklist',
'chrome_elf_lib',
],
'msvs_settings': {
@@ -29,6 +31,14 @@
'BaseAddress': '0x01c20000',
# Set /SUBSYSTEM:WINDOWS for chrome_elf.dll (for consistency).
'SubSystem': '2',
+ # Exclude explicitly unwanted libraries from the link line.
+ 'IgnoreAllDefaultLibraries': 'true',
+ 'AdditionalDependencies!': [
+ 'user32.lib',
+ ],
+ 'IgnoreDefaultLibraryNames': [
+ 'user32.lib',
+ ],
},
},
},
@@ -36,6 +46,7 @@
'target_name': 'chrome_elf_unittests',
'type': 'executable',
'sources': [
+ 'blacklist/test/blacklist_test.cc',
'ntdll_cache_unittest.cc',
],
'include_dirs': [
@@ -43,8 +54,16 @@
],
'dependencies': [
'chrome_elf_lib',
- '<(DEPTH)/base/base.gyp:run_all_unittests',
- '<(DEPTH)/testing/gtest.gyp:gtest',
+ '../base/base.gyp:base',
+ '../base/base.gyp:run_all_unittests',
+ '../base/base.gyp:test_support_base',
+ '../sandbox/sandbox.gyp:sandbox',
+ '../testing/gtest.gyp:gtest',
+ 'blacklist',
+ 'blacklist_test_dll_1',
+ 'blacklist_test_dll_2',
+ 'blacklist_test_dll_3',
+ 'blacklist_test_main_dll',
],
},
{
diff --git a/chrome_elf/chrome_elf_main.cc b/chrome_elf/chrome_elf_main.cc
index 4291430..9ad8299 100644
--- a/chrome_elf/chrome_elf_main.cc
+++ b/chrome_elf/chrome_elf_main.cc
@@ -6,15 +6,21 @@
#include "chrome_elf/chrome_elf_main.h"
+#include "chrome_elf/blacklist/blacklist.h"
#include "chrome_elf/ntdll_cache.h"
-void InitChromeElf() {
- // This method is a no-op which may be called to force a load-time dependency
- // on chrome_elf.dll.
+void SignalChromeElf() {
+ blacklist::ClearBeacon();
}
BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved) {
- if (reason == DLL_PROCESS_ATTACH)
+ if (reason == DLL_PROCESS_ATTACH) {
InitCache();
+ blacklist::Initialize(false); // Don't force, abort if beacon is present.
+
+ // TODO(csharp): Move additions to the DLL blacklist to a sane place.
+ // blacklist::AddDllToBlacklist(L"foo.dll");
+ }
+
return TRUE;
}
diff --git a/chrome_elf/chrome_elf_main.h b/chrome_elf/chrome_elf_main.h
index 7d02ddd..52bf067 100644
--- a/chrome_elf/chrome_elf_main.h
+++ b/chrome_elf/chrome_elf_main.h
@@ -5,6 +5,6 @@
#ifndef CHROME_ELF_CHROME_ELF_MAIN_H_
#define CHROME_ELF_CHROME_ELF_MAIN_H_
-extern "C" void InitChromeElf();
+extern "C" void SignalChromeElf();
#endif // CHROME_ELF_CHROME_ELF_MAIN_H_
diff --git a/chrome_elf/version_assembly_manifest_action.gypi b/chrome_elf/version_assembly_manifest_action.gypi
index 9c44315..37c2015 100644
--- a/chrome_elf/version_assembly_manifest_action.gypi
+++ b/chrome_elf/version_assembly_manifest_action.gypi
@@ -34,4 +34,4 @@
'<@(_outputs)',
],
'message': 'Generating <@(_outputs)',
-} \ No newline at end of file
+}