blob: 9749eb94df9ff8cadc821177fc11f396a6253710 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifdef OS_WIN
#define PSAPI_VERSION 1 // for <psapi.h>
#include <windows.h>
#if !defined(NO_LOGGING)
#include <atlbase.h>
#include <atlstr.h> // for CString
#endif // !NO_LOGGING
#include <psapi.h>
#include "base/file_util.h"
#include "base/scoped_handle.h"
#include "base/singleton.h"
#include "base/system_util.h"
#include "base/util.h"
#include "base/winmain.h" // use WinMain
#include "server/cache_service_manager.h"
namespace {
HANDLE g_stop_event = NULL;
#if defined(NO_LOGGING)
#define LOG_WIN32_ERROR(message)
#else
template <size_t num_elements>
void LogMessageImpl(
const wchar_t (&file)[num_elements],
int line,
const wchar_t *message,
int error_no) {
CString buffer;
buffer.Format(_T("%s (%d): %s (error: %d)\n"), file, line, message, error_no);
::OutputDebugString(buffer);
}
#define LOG_WIN32_ERROR(message) \
LogMessageImpl(_T(__FILE__), __LINE__, message, ::GetLastError())
#endif
wstring GetMappedFileNameByAddress(LPVOID address) {
wchar_t path[MAX_PATH];
const int length = ::GetMappedFileName(::GetCurrentProcess(), address,
path, arraysize(path));
if (length == 0 || arraysize(path) <= length) {
LOG_WIN32_ERROR(L"GetMappedFileName failed.");
return L"";
}
return wstring(path, length);
}
// This function scans each section of a given mapped image section and
// changes memory protection attribute to read-only.
// Retruns true if all sections are changed to one read-only region.
// |result_info| contains the memory block information of the combined
// region if succeeds.
bool MakeReadOnlyForMappedModule(
LPVOID address,
MEMORY_BASIC_INFORMATION *result_info) {
// For an IMAGE section, the mapped size may be slightly different from the
// file size since each PE section should be aligned to the page boundary.
// To investigate the actual mapped size, we scan each block of memory pages.
// Fortunately, we can get the source filename of a given virtual address.
// We can also rely on the MEMORY_BASIC_INFORMATION::Type. If a given
// address is inside of a view of an IMAGE section,
// MEMORY_BASIC_INFORMATION::Type should be MEM_IMAGE.
// Store the source filename.
const wstring &filename = ::GetMappedFileNameByAddress(address);
if (filename.empty()) {
return false;
}
void *start_address = NULL;
DWORD total_region_size = 0;
void *current = address;
while (true) {
MEMORY_BASIC_INFORMATION mem_info;
if (::VirtualQuery(current, &mem_info, sizeof(mem_info)) == 0) {
LOG_WIN32_ERROR(L"VirtualQuery failed.");
return false;
}
if (mem_info.Type != MEM_IMAGE) {
break;
}
if (GetMappedFileNameByAddress(current) != filename) {
break;
}
// If this region is not PAGE_READONLY, change the protection to readonly.
if (mem_info.Protect != PAGE_READONLY) {
DWORD old_protect = 0;
if (::VirtualProtect(mem_info.BaseAddress, mem_info.RegionSize,
PAGE_READONLY, &old_protect) == 0) {
LOG_WIN32_ERROR(L"VirtualProtect failed.");
return false;
}
}
if (start_address == NULL) {
start_address = mem_info.BaseAddress;
}
total_region_size += mem_info.RegionSize;
current = static_cast<char *>(mem_info.BaseAddress) + mem_info.RegionSize;
}
if (result_info != NULL &&
::VirtualQuery(address, result_info,
sizeof(MEMORY_BASIC_INFORMATION)) == 0) {
LOG_WIN32_ERROR(L"VirtualQuery failed.");
return false;
}
result_info->BaseAddress = start_address;
result_info->RegionSize = total_region_size;
return true;
}
} // namespace
void WINAPI ServiceHandlerProc(DWORD control_code) {
switch (control_code) {
// ignores the following code
case SERVICE_CONTROL_PAUSE:
case SERVICE_CONTROL_CONTINUE:
case SERVICE_CONTROL_INTERROGATE:
break;
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
if (NULL != g_stop_event) {
if (!::SetEvent(g_stop_event)) { // raise event
// call ExitProcess() if SetEvent failed just in case.
::ExitProcess(0);
}
} else {
::ExitProcess(0);
}
break;
default:
break;
}
}
#define STOP_SERVICE_AND_EXIT_FUNCTION() do { \
service_status.dwCurrentState = SERVICE_STOPPED; \
::SetServiceStatus(service_status_handle, &service_status); \
return; \
} while (false)
#if defined(DEBUG)
// This function try to make a temporary file in the same directory of the
// service if the arguments contains "--verify_privilege_restriction".
// This attempt should fail by ERROR_ACCESS_DENIED since the cache service
// runs under write-restricted SID in Windows Vista or later by default.
// See http://b/2470180 for the whole story.
bool VerifyPrivilegeRestrictionIfNeeded(DWORD dwArgc, LPTSTR *lpszArgv) {
bool verify_privilege = false;
const wstring test_mode = L"--verify_privilege_restriction";
for (size_t i = 0; i < dwArgc; ++i) {
if (test_mode == lpszArgv[i]) {
verify_privilege = true;
break;
}
}
if (!verify_privilege) {
return true;
}
const string temp_path =
mozc::FileUtil::JoinPath(mozc::SystemUtil::GetServerDirectory(),
"delete_me.txt");
wstring wtemp_path;
mozc::Util::UTF8ToWide(temp_path.c_str(), &wtemp_path);
const HANDLE temp_file = ::CreateFileW(
wtemp_path.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_ATTRIBUTE_TEMPORARY
| FILE_FLAG_DELETE_ON_CLOSE,
NULL);
if (temp_file != INVALID_HANDLE_VALUE) {
::CloseHandle(temp_file);
LOG_WIN32_ERROR(L"CreateEvent should have failed but succeeded.");
return false;
}
if (ERROR_ACCESS_DENIED != ::GetLastError()) {
LOG_WIN32_ERROR(L"Unexpected error code.");
return false;
}
return true;
}
#endif // DEBUG
VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv) {
SERVICE_STATUS_HANDLE service_status_handle = ::RegisterServiceCtrlHandler(
mozc::CacheServiceManager::GetServiceName(),
ServiceHandlerProc);
if (NULL == service_status_handle) {
return;
}
SERVICE_STATUS service_status = { 0 };
service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
service_status.dwWin32ExitCode = NO_ERROR;
service_status.dwServiceSpecificExitCode = 0;
service_status.dwCheckPoint = 0;
service_status.dwWaitHint = 0;
service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
service_status.dwCurrentState = SERVICE_RUNNING;
::SetServiceStatus(service_status_handle, &service_status);
#if defined(_DEBUG)
if (!VerifyPrivilegeRestrictionIfNeeded(dwArgc, lpszArgv)) {
STOP_SERVICE_AND_EXIT_FUNCTION();
}
#endif
if (!mozc::CacheServiceManager::HasEnoughMemory()) {
STOP_SERVICE_AND_EXIT_FUNCTION();
}
mozc::ScopedHandle stop_event(::CreateEvent(NULL, TRUE, FALSE, NULL));
g_stop_event = stop_event.get();
if (NULL == g_stop_event) {
LOG_WIN32_ERROR(L"CreateEvent failed.");
STOP_SERVICE_AND_EXIT_FUNCTION();
}
// when low_memory_event is signaled, unkcok the memory image.
mozc::ScopedHandle low_memory_event(
::CreateMemoryResourceNotification(LowMemoryResourceNotification));
if (NULL == low_memory_event.get()) {
LOG_WIN32_ERROR(L"CreateMemoryResourceNotification failed.");
STOP_SERVICE_AND_EXIT_FUNCTION();
}
// when high_memory_event is signaled, lcok the memory image.
mozc::ScopedHandle high_memory_event(
::CreateMemoryResourceNotification(HighMemoryResourceNotification));
if (NULL == high_memory_event.get()) {
LOG_WIN32_ERROR(L"CreateMemoryResourceNotification failed.");
STOP_SERVICE_AND_EXIT_FUNCTION();
}
wstring server_path;
mozc::Util::UTF8ToWide(mozc::SystemUtil::GetServerPath().c_str(),
&server_path);
mozc::ScopedHandle file_handle(::CreateFile(server_path.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
0, OPEN_EXISTING,
0, 0));
if (NULL == file_handle.get()) {
LOG_WIN32_ERROR(L"CreateFile failed.");
STOP_SERVICE_AND_EXIT_FUNCTION();
}
const size_t size = ::GetFileSize(file_handle.get(), 0);
// Do not load image if the file size is too big
const DWORD kMaxImageSize = 100 * 1024 * 1024; // 100MB
if (size > kMaxImageSize) {
STOP_SERVICE_AND_EXIT_FUNCTION();
}
// In general, there are two types of section object for a given file:
// an IMAGE section and a (normal) MAPPED-FILE section. The former is used
// to load an executable file as a module (HMODULE) as opposed to the later
// which maps a file into the virtual address space as data file.
// Apparently, multiple IMAGE sections for the same executable share most of
// memory pages through their mapped view. This should be also true for
// multiple MAPPED-FILE sections. However, can IMAGE section and MAPPED-FILE
// section for the same file share their memory pages?
// Anyway we can use SEC_IMAGE to make an IMAGE section for a given file
// through which we can keep the content of the file on-memory.
mozc::ScopedHandle mmap_handle(::CreateFileMapping(file_handle.get(),
NULL,
PAGE_READONLY | SEC_IMAGE,
0, 0, NULL));
if (NULL == mmap_handle.get()) {
LOG_WIN32_ERROR(L"CreateFileMapping failed.");
STOP_SERVICE_AND_EXIT_FUNCTION();
}
LPVOID image = reinterpret_cast<LPVOID>(
::MapViewOfFile(mmap_handle.get(), FILE_MAP_READ, 0, 0, 0));
if (NULL == image) {
LOG_WIN32_ERROR(L"MapViewOfFile failed.");
STOP_SERVICE_AND_EXIT_FUNCTION();
}
// A mapped view of an IMAGE section may have multiple sections whose
// protection attributes correspond to the attributes of PE sections.
// Here is a result of a version of mapped GoogleIMEJaConverter.exe.
// Address Type Size Protection Details
// 00ED0000 Image 48,488 Execute/Copy on Write GoogleIMEJaConverter.exe
// 00ED0000 Image 4 Read Header
// 00ED1000 Image 888 Execute/Read .text
// 00FAF000 Image 47,316 Read .rdata
// 03DE4000 Image 16 Copy on write .data
// 03DE8000 Image 8 Read/Write .data
// 03DEA000 Image 4 Read .rsrc
// 03DEB000 Image 252 Read .reloc
// Although an initial protection attribute is not always read-only, you can
// change each protection to read-only by VirtualProtect API.
MEMORY_BASIC_INFORMATION mem_info;
if (!MakeReadOnlyForMappedModule(image, &mem_info)) {
STOP_SERVICE_AND_EXIT_FUNCTION();
}
const DWORD kMinAdditionalSize = 512 * 1024; // 512KB
const DWORD kMaxAdditionalSize = 2 * 1024 * 1024; // 2MB
if (!::SetProcessWorkingSetSize(::GetCurrentProcess(),
mem_info.RegionSize + kMinAdditionalSize,
mem_info.RegionSize + kMaxAdditionalSize)) {
LOG_WIN32_ERROR(L"SetProcessWorkingSetSize failed.");
STOP_SERVICE_AND_EXIT_FUNCTION();
}
DWORD lock_time = 0;
DWORD unlock_time = 0;
WAIT_HIGH:
{
const HANDLE handles[] = { g_stop_event, high_memory_event.get() };
const DWORD result = ::WaitForMultipleObjects(arraysize(handles),
handles,
FALSE, INFINITE);
switch (result) {
case WAIT_OBJECT_0 + 1: // high is signaled
goto TRY_LOCK;
break;
case WAIT_OBJECT_0: // ctrl or error
default:
STOP_SERVICE_AND_EXIT_FUNCTION();
}
}
TRY_LOCK:
{
const int kMaxTimeout = 10 * 60 * 1000; // 10 min.
const int kMinTimeout = 1 * 60 * 1000; // 1min.
// if the low event is signaled just after calling VirtualLock(),
// the VirtualLock() itself may indirectly raise the low event.
// This eventually causes an infinite loop. To prevent this, we check
// the duration between the last VirtualUnlock() and VirtualLock() and
// set longer timeout, when the duration is shorter.
const int duration = static_cast<int>(unlock_time - lock_time);
const int timeout =
unlock_time == 0 ? 0 : max(kMinTimeout, kMaxTimeout - max(0, duration));
const DWORD result = ::WaitForSingleObject(g_stop_event, timeout);
switch (result) {
case WAIT_TIMEOUT:
switch (::WaitForSingleObject(high_memory_event.get(), 0)) {
case WAIT_OBJECT_0:
if (::VirtualLock(mem_info.BaseAddress, mem_info.RegionSize) == 0) {
LOG_WIN32_ERROR(L"VirtualLock failed.");
STOP_SERVICE_AND_EXIT_FUNCTION();
}
lock_time = ::GetTickCount();
goto WAIT_LOW;
break;
case WAIT_TIMEOUT:
goto TRY_LOCK;
break;
default:
STOP_SERVICE_AND_EXIT_FUNCTION();
}
break;
case WAIT_OBJECT_0:
default:
STOP_SERVICE_AND_EXIT_FUNCTION();
}
}
WAIT_LOW:
{
const HANDLE handles[] = { g_stop_event, low_memory_event.get() };
const DWORD result = ::WaitForMultipleObjects(arraysize(handles),
handles,
FALSE, INFINITE);
switch (result) {
case WAIT_OBJECT_0 + 1: // low is signaled
if (!::VirtualUnlock(mem_info.BaseAddress, mem_info.RegionSize)) {
LOG_WIN32_ERROR(L"VirtualUnlock failed.");
STOP_SERVICE_AND_EXIT_FUNCTION();
}
unlock_time = ::GetTickCount();
goto WAIT_HIGH;
break;
case WAIT_OBJECT_0: // ctrl or error
default:
STOP_SERVICE_AND_EXIT_FUNCTION();
}
}
STOP_SERVICE_AND_EXIT_FUNCTION();
}
int main(int argc, char **argv) {
if (argc <= 1) {
SERVICE_TABLE_ENTRY kDispatchTable[] = {
{ const_cast<wchar_t *>(
mozc::CacheServiceManager::GetServiceName()), ServiceMain },
{ NULL, NULL }
};
::StartServiceCtrlDispatcher(kDispatchTable);
return 0;
}
return 0;
}
#endif