1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 //     * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //     * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //     * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 #include "base/win_util.h"
31 
32 // skip all unless OS_WIN
33 #ifdef OS_WIN
34 
35 #include <Aux_ulib.h>
36 #include <Psapi.h>
37 #include <Stringapiset.h>
38 #include <Winternl.h>
39 #include <shellapi.h>
40 
41 #define _ATL_NO_AUTOMATIC_NAMESPACE
42 #define _WTL_NO_AUTOMATIC_NAMESPACE
43 #include <atlbase.h>
44 
45 #include <clocale>
46 #include <memory>
47 
48 #include "base/logging.h"
49 #include "base/mutex.h"
50 #include "base/scoped_handle.h"
51 #include "base/system_util.h"
52 #include "base/util.h"
53 
54 using std::unique_ptr;
55 
56 namespace mozc {
57 namespace {
58 
59 once_t g_aux_lib_initialized = MOZC_ONCE_INIT;
60 
CallAuxUlibInitialize()61 void CallAuxUlibInitialize() {
62   ::AuxUlibInitialize();
63 }
64 
EqualLuid(const LUID & L1,const LUID & L2)65 bool EqualLuid(const LUID &L1, const LUID &L2) {
66   return (L1.LowPart == L2.LowPart && L1.HighPart == L2.HighPart);
67 }
68 
IsProcessSandboxedImpl()69 bool IsProcessSandboxedImpl() {
70   bool is_restricted = false;
71   if (!WinUtil::IsProcessRestricted(::GetCurrentProcess(), &is_restricted)) {
72     return true;
73   }
74   if (is_restricted) {
75     return true;
76   }
77 
78   bool in_appcontainer = false;
79   if (!WinUtil::IsProcessInAppContainer(::GetCurrentProcess(),
80                                         &in_appcontainer)) {
81     return true;
82   }
83 
84   return in_appcontainer;
85 }
86 
87 }  // namespace
88 
LoadSystemLibrary(const std::wstring & base_filename)89 HMODULE WinUtil::LoadSystemLibrary(const std::wstring &base_filename) {
90   std::wstring fullpath = SystemUtil::GetSystemDir();
91   fullpath += L"\\";
92   fullpath += base_filename;
93 
94   const HMODULE module = ::LoadLibraryExW(fullpath.c_str(),
95                                           nullptr,
96                                           LOAD_WITH_ALTERED_SEARCH_PATH);
97   if (nullptr == module) {
98     const int last_error = ::GetLastError();
99     DLOG(WARNING) << "LoadLibraryEx failed."
100                   << " fullpath = " << fullpath.c_str()
101                   << " error = " << last_error;
102   }
103   return module;
104 }
105 
LoadMozcLibrary(const std::wstring & base_filename)106 HMODULE WinUtil::LoadMozcLibrary(const std::wstring &base_filename) {
107   std::wstring fullpath;
108   Util::UTF8ToWide(SystemUtil::GetServerDirectory(), &fullpath);
109   fullpath += L"\\";
110   fullpath += base_filename;
111 
112   const HMODULE module = ::LoadLibraryExW(fullpath.c_str(),
113                                           nullptr,
114                                           LOAD_WITH_ALTERED_SEARCH_PATH);
115   if (nullptr == module) {
116     const int last_error = ::GetLastError();
117     DLOG(WARNING) << "LoadLibraryEx failed."
118                   << " fullpath = " << fullpath.c_str()
119                   << " error = " << last_error;
120   }
121   return module;
122 }
123 
GetSystemModuleHandle(const std::wstring & base_filename)124 HMODULE WinUtil::GetSystemModuleHandle(const std::wstring &base_filename) {
125   std::wstring fullpath = SystemUtil::GetSystemDir();
126   fullpath += L"\\";
127   fullpath += base_filename;
128 
129   HMODULE module = nullptr;
130   if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
131                          fullpath.c_str(), &module) == FALSE) {
132     const int last_error = ::GetLastError();
133     DLOG(WARNING) << "GetModuleHandleExW failed."
134                   << " fullpath = " << fullpath.c_str()
135                   << " error = " << last_error;
136   }
137   return module;
138 }
139 
GetSystemModuleHandleAndIncrementRefCount(const std::wstring & base_filename)140 HMODULE WinUtil::GetSystemModuleHandleAndIncrementRefCount(
141     const std::wstring &base_filename) {
142   std::wstring fullpath = SystemUtil::GetSystemDir();
143   fullpath += L"\\";
144   fullpath += base_filename;
145 
146   HMODULE module = nullptr;
147   if (GetModuleHandleExW(0, fullpath.c_str(), &module) == FALSE) {
148     const int last_error = ::GetLastError();
149     DLOG(WARNING) << "GetModuleHandleExW failed."
150                   << " fullpath = " << fullpath.c_str()
151                   << " error = " << last_error;
152   }
153   return module;
154 }
155 
IsDLLSynchronizationHeld(bool * lock_status)156 bool WinUtil::IsDLLSynchronizationHeld(bool *lock_status) {
157   mozc::CallOnce(&g_aux_lib_initialized, &CallAuxUlibInitialize);
158 
159   if (lock_status == nullptr) {
160     return false;
161   }
162 
163   BOOL synchronization_held = FALSE;
164   const BOOL result =
165       ::AuxUlibIsDLLSynchronizationHeld(&synchronization_held);
166   if (!result) {
167     const int error = ::GetLastError();
168     DLOG(ERROR) << "AuxUlibIsDLLSynchronizationHeld failed. error = "
169                 << error;
170     return false;
171   }
172   *lock_status = (synchronization_held != FALSE);
173   return true;
174 }
175 
EncodeWindowHandle(HWND window_handle)176 uint32 WinUtil::EncodeWindowHandle(HWND window_handle) {
177   return static_cast<uint32>(reinterpret_cast<uintptr_t>(window_handle));
178 }
179 
DecodeWindowHandle(uint32 window_handle_value)180 HWND WinUtil::DecodeWindowHandle(uint32 window_handle_value) {
181   return reinterpret_cast<HWND>(static_cast<uintptr_t>(window_handle_value));
182 }
183 
SystemEqualString(const std::wstring & lhs,const std::wstring & rhs,bool ignore_case)184 bool WinUtil::SystemEqualString(
185       const std::wstring &lhs, const std::wstring &rhs, bool ignore_case) {
186   // We assume a string instance never contains NUL character in principle.
187   // So we will raise an error to notify the unexpected situation in debug
188   // builds.  In production, however, we will admit such an instance and
189   // silently trim it at the first NUL character.
190   const std::wstring::size_type lhs_null_pos = lhs.find_first_of(L'\0');
191   const std::wstring::size_type rhs_null_pos = rhs.find_first_of(L'\0');
192   DCHECK_EQ(lhs.npos, lhs_null_pos)
193       << "|lhs| should not contain NUL character.";
194   DCHECK_EQ(rhs.npos, rhs_null_pos)
195       << "|rhs| should not contain NUL character.";
196   const std::wstring &lhs_null_trimmed = lhs.substr(0, lhs_null_pos);
197   const std::wstring &rhs_null_trimmed = rhs.substr(0, rhs_null_pos);
198 
199   const int compare_result = ::CompareStringOrdinal(
200       lhs_null_trimmed.data(), lhs_null_trimmed.size(),
201       rhs_null_trimmed.data(), rhs_null_trimmed.size(),
202       (ignore_case ? TRUE : FALSE));
203 
204   return compare_result == CSTR_EQUAL;
205 }
206 
IsServiceUser(HANDLE hToken,bool * is_service)207 bool WinUtil::IsServiceUser(HANDLE hToken, bool *is_service) {
208   if (is_service == nullptr) {
209     return false;
210   }
211 
212   TOKEN_STATISTICS ts;
213   DWORD dwSize = 0;
214   // Use token logon LUID instead of user SID, for brevity and safety
215   if (!::GetTokenInformation(hToken, TokenStatistics,
216                              (LPVOID)&ts, sizeof(ts), &dwSize)) {
217     return false;
218   }
219 
220   // Compare LUID
221   const LUID SystemLuid = SYSTEM_LUID;
222   const LUID LocalServiceLuid = LOCALSERVICE_LUID;
223   const LUID NetworkServiceLuid = NETWORKSERVICE_LUID;
224   if (EqualLuid(SystemLuid, ts.AuthenticationId) ||
225       EqualLuid(LocalServiceLuid, ts.AuthenticationId) ||
226       EqualLuid(NetworkServiceLuid, ts.AuthenticationId)) {
227     *is_service = true;
228     return true;
229   }
230 
231   // Not a service account
232   *is_service = false;
233   return true;
234 }
235 
IsServiceProcess(bool * is_service)236 bool WinUtil::IsServiceProcess(bool *is_service) {
237   if (is_service == nullptr) {
238     return false;
239   }
240 
241   // Session 0 is dedicated to services
242   DWORD dwSessionId = 0;
243   if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &dwSessionId) ||
244       (dwSessionId == 0)) {
245     *is_service = true;
246     return true;
247   }
248 
249   // Get process token
250   HANDLE hProcessToken = nullptr;
251   if (!::OpenProcessToken(::GetCurrentProcess(),
252                           TOKEN_QUERY | TOKEN_QUERY_SOURCE,
253                           &hProcessToken)) {
254     return false;
255   }
256 
257   ScopedHandle process_token(hProcessToken);
258 
259   // Process token is one for a service account.
260   if (!IsServiceUser(process_token.get(), is_service)) {
261     return false;
262   }
263 
264   return true;
265 }
266 
IsServiceThread(bool * is_service)267 bool WinUtil::IsServiceThread(bool *is_service) {
268   if (is_service == nullptr) {
269     return false;
270   }
271 
272   // Get thread token (if any)
273   HANDLE hThreadToken = nullptr;
274   if (!::OpenThreadToken(::GetCurrentThread(),
275                         TOKEN_QUERY, TRUE, &hThreadToken) &&
276       ERROR_NO_TOKEN != ::GetLastError()) {
277     return false;
278   }
279 
280   if (hThreadToken == nullptr) {
281     // No thread token.
282     *is_service = false;
283     return true;
284   }
285 
286   ScopedHandle thread_token(hThreadToken);
287 
288   // Check if the thread token (if any) is one for a service account.
289   if (!IsServiceUser(thread_token.get(), is_service)) {
290     return false;
291   }
292   return true;
293 }
294 
IsServiceAccount(bool * is_service)295 bool WinUtil::IsServiceAccount(bool *is_service) {
296   if (is_service == nullptr) {
297     return false;
298   }
299 
300   bool is_service_process = false;
301   if (!WinUtil::IsServiceProcess(&is_service_process)) {
302     DLOG(ERROR) << "WinUtil::IsServiceProcess failed.";
303     return false;
304   }
305 
306   if (is_service_process) {
307     *is_service = true;
308     return true;
309   }
310 
311   // Process token is not one for service.
312   // Check thread token just in case.
313   bool is_service_thread = false;
314   if (!WinUtil::IsServiceThread(&is_service_thread)) {
315     DLOG(ERROR) << "WinUtil::IsServiceThread failed.";
316     return false;
317   }
318 
319   if (is_service_thread) {
320     *is_service = true;
321     return true;
322   }
323 
324   *is_service = false;
325   return true;
326 }
327 
IsProcessImmersive(HANDLE process_handle,bool * is_immersive)328 bool WinUtil::IsProcessImmersive(HANDLE process_handle,
329                                  bool *is_immersive) {
330   if (is_immersive == nullptr) {
331     return false;
332   }
333   *is_immersive = false;
334   // ImmersiveMode is supported only in Windows8 and later.
335   if (!SystemUtil::IsWindows8OrLater()) {
336     return true;
337   }
338 
339   const HMODULE module = WinUtil::GetSystemModuleHandle(L"user32.dll");
340   if (module == nullptr) {
341     return false;
342   }
343 
344   typedef BOOL (WINAPI* IsImmersiveProcessFunc)(HANDLE process);
345   IsImmersiveProcessFunc is_immersive_process =
346       reinterpret_cast<IsImmersiveProcessFunc>(
347           ::GetProcAddress(module, "IsImmersiveProcess"));
348   if (is_immersive_process == nullptr) {
349     return false;
350   }
351 
352   *is_immersive = !!is_immersive_process(process_handle);
353   return true;
354 }
355 
IsProcessRestricted(HANDLE process_handle,bool * is_restricted)356 bool WinUtil::IsProcessRestricted(HANDLE process_handle, bool *is_restricted) {
357   if (is_restricted == nullptr) {
358     return false;
359   }
360   *is_restricted = false;
361 
362   HANDLE token = nullptr;
363   if (!::OpenProcessToken(process_handle, TOKEN_QUERY, &token)) {
364     return false;
365   }
366 
367   ScopedHandle process_token(token);
368   ::SetLastError(NOERROR);
369   if (::IsTokenRestricted(process_token.get()) == FALSE) {
370     const DWORD error = ::GetLastError();
371     if (error != NOERROR) {
372       return false;
373     }
374   } else {
375     *is_restricted = true;
376   }
377   return true;
378 }
379 
IsProcessInAppContainer(HANDLE process_handle,bool * in_appcontainer)380 bool WinUtil::IsProcessInAppContainer(HANDLE process_handle,
381                                       bool *in_appcontainer) {
382   if (in_appcontainer == nullptr) {
383     return false;
384   }
385   *in_appcontainer = false;
386 
387   // AppContainer is supported only in Windows8 and later.
388   if (!SystemUtil::IsWindows8OrLater()) {
389     return true;
390   }
391 
392   HANDLE token = nullptr;
393   if (!::OpenProcessToken(process_handle, TOKEN_QUERY | TOKEN_QUERY_SOURCE,
394                           &token)) {
395     return false;
396   }
397 
398   // TokenIsAppContainer is defined only in Windows SDK 8.0 and later.
399   ScopedHandle process_token(token);
400   const TOKEN_INFORMATION_CLASS kTokenIsAppContainer =
401       static_cast<TOKEN_INFORMATION_CLASS>(29);  // TokenIsAppContainer
402 #if defined(_WIN32_WINNT_WIN8)
403   static_assert(kTokenIsAppContainer == TokenIsAppContainer,
404                 "Checking |kTokenIsAppContainer| has correct value.");
405 #endif  // _WIN32_WINNT_WIN8
406   DWORD returned_size = 0;
407   DWORD retval = 0;
408   if (!GetTokenInformation(process_token.get(), kTokenIsAppContainer,
409                            &retval, sizeof(retval), &returned_size)) {
410     return false;
411   }
412   if (returned_size != sizeof(retval)) {
413     return false;
414   }
415 
416   *in_appcontainer = (retval != 0);
417   return true;
418 }
419 
GetFileSystemInfoFromPath(const std::wstring & path,BY_HANDLE_FILE_INFORMATION * info)420 bool WinUtil::GetFileSystemInfoFromPath(
421     const std::wstring &path, BY_HANDLE_FILE_INFORMATION *info) {
422   // no read access is required.
423   ScopedHandle handle(::CreateFileW(
424       path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
425       nullptr, OPEN_EXISTING,
426       FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL, nullptr));
427 
428   // Caveats: handle.get() returns nullptr when it is initialized with
429   //     INVALID_HANDLE_VALUE.
430   if (handle.get() == nullptr) {
431     return false;
432   }
433   return !!::GetFileInformationByHandle(handle.get(), info);
434 }
435 
AreEqualFileSystemObject(const std::wstring & left_path,const std::wstring & right_path)436 bool WinUtil::AreEqualFileSystemObject(const std::wstring &left_path,
437                                        const std::wstring &right_path) {
438   BY_HANDLE_FILE_INFORMATION left_info = {};
439   if (!GetFileSystemInfoFromPath(left_path, &left_info)) {
440     return false;
441   }
442   BY_HANDLE_FILE_INFORMATION right_info = {};
443   if (!GetFileSystemInfoFromPath(right_path, &right_info)) {
444     return false;
445   }
446   return (left_info.nFileIndexLow == right_info.nFileIndexLow) &&
447          (left_info.nFileIndexHigh == right_info.nFileIndexHigh);
448 }
449 
GetNtPath(const std::wstring & dos_path,std::wstring * nt_path)450 bool WinUtil::GetNtPath(const std::wstring &dos_path, std::wstring *nt_path) {
451   if (nt_path == nullptr) {
452     return false;
453   }
454 
455   nt_path->clear();
456 
457   ScopedHandle file_handle(::CreateFileW(
458       dos_path.c_str(), 0,
459       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
460       nullptr, OPEN_EXISTING,
461       FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL, nullptr));
462   if (file_handle.get() == nullptr) {
463     // Caveats: |file_handle.get()| becomes nullptr instead of
464     // INVALID_HANDLE_VALUE when failure.
465     return false;
466   }
467 
468   const size_t kMaxPath = 4096;
469   unique_ptr<wchar_t[]> ntpath_buffer(
470       new wchar_t[kMaxPath]);
471   const DWORD copied_len_without_null = ::GetFinalPathNameByHandleW(
472       file_handle.get(),
473       ntpath_buffer.get(),
474       kMaxPath,
475       FILE_NAME_NORMALIZED | VOLUME_NAME_NT);
476   if (copied_len_without_null == 0 ||
477       copied_len_without_null > kMaxPath) {
478     const DWORD error = ::GetLastError();
479     VLOG(1) << "GetFinalPathNameByHandleW() failed: " << error;
480     return false;
481   }
482 
483   nt_path->assign(ntpath_buffer.get(), copied_len_without_null);
484   return true;
485 }
486 
GetProcessInitialNtPath(DWORD pid,std::wstring * nt_path)487 bool WinUtil::GetProcessInitialNtPath(DWORD pid, std::wstring *nt_path) {
488   if (nt_path == nullptr) {
489     return false;
490   }
491   nt_path->clear();
492 
493   ScopedHandle process_handle(::OpenProcess(
494       PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
495 
496   if (process_handle.get() == nullptr) {
497     VLOG(1) << "OpenProcess() failed: " << ::GetLastError();
498     return false;
499   }
500 
501   const size_t kMaxPath = 4096;
502   unique_ptr<wchar_t[]> ntpath_buffer(new wchar_t[kMaxPath]);
503   const DWORD copied_len_without_null =
504       ::GetProcessImageFileNameW(process_handle.get(),
505                                  ntpath_buffer.get(),
506                                  kMaxPath);
507   if (copied_len_without_null == 0 || copied_len_without_null > kMaxPath) {
508     const DWORD error = ::GetLastError();
509     VLOG(1) << "GetProcessImageFileNameW() failed: " << error;
510     return false;
511   }
512 
513   nt_path->assign(ntpath_buffer.get(), copied_len_without_null);
514   return true;
515 }
516 
517 // SPI_GETTHREADLOCALINPUTSETTINGS is available on Windows 8 SDK and later.
518 #ifndef SPI_GETTHREADLOCALINPUTSETTINGS
519 #define SPI_GETTHREADLOCALINPUTSETTINGS 0x104E
520 #endif  // SPI_GETTHREADLOCALINPUTSETTINGS
521 
IsPerUserInputSettingsEnabled()522 bool WinUtil::IsPerUserInputSettingsEnabled() {
523   if (!SystemUtil::IsWindows8OrLater()) {
524     // Windows 7 and below does not support per-user input mode.
525     return false;
526   }
527   BOOL is_thread_local = FALSE;
528   if (::SystemParametersInfo(SPI_GETTHREADLOCALINPUTSETTINGS,
529                              0,
530                              reinterpret_cast<void *>(&is_thread_local),
531                              0) == FALSE) {
532     return false;
533   }
534   return !is_thread_local;
535 }
536 
IsProcessSandboxed()537 bool WinUtil::IsProcessSandboxed() {
538   // Thread safety is not required.
539   static bool sandboxed = IsProcessSandboxedImpl();
540   return sandboxed;
541 }
542 
ShellExecuteInSystemDir(const wchar_t * verb,const wchar_t * file,const wchar_t * parameters)543 bool WinUtil::ShellExecuteInSystemDir(const wchar_t *verb,
544                                       const wchar_t *file,
545                                       const wchar_t *parameters) {
546   const auto result = static_cast<uint32>(reinterpret_cast<uintptr_t>(
547       ::ShellExecuteW(0, verb, file, parameters, SystemUtil::GetSystemDir(),
548                       SW_SHOW)));
549   LOG_IF(ERROR, result <= 32)
550       << "ShellExecute failed."
551       << ", error:" << result
552       << ", verb: " << verb
553       << ", file: " << file
554       << ", parameters: " << parameters;
555   return result > 32;
556 }
557 
ScopedCOMInitializer()558 ScopedCOMInitializer::ScopedCOMInitializer()
559     : hr_(::CoInitialize(nullptr)) {
560 }
561 
~ScopedCOMInitializer()562 ScopedCOMInitializer::~ScopedCOMInitializer() {
563   if (SUCCEEDED(hr_)) {
564     ::CoUninitialize();
565   }
566 }
567 
568 }  // namespace mozc
569 
570 #endif  // OS_WIN
571