| // 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. |
| |
| #include "base/run_level.h" |
| |
| #ifdef OS_WIN |
| #include <windows.h> |
| #include <aclapi.h> |
| #endif // OS_WIN |
| |
| #ifdef OS_MACOSX |
| #include <unistd.h> |
| #endif // OS_MACOSX |
| |
| #ifdef OS_LINUX |
| #include <unistd.h> |
| #include <sys/types.h> |
| #endif // OS_LINUX |
| |
| #include "base/const.h" |
| #include "base/logging.h" |
| #include "base/scoped_handle.h" |
| #include "base/system_util.h" |
| #include "base/util.h" |
| #include "base/win_sandbox.h" |
| #include "base/win_util.h" |
| |
| namespace mozc { |
| namespace { |
| |
| #ifdef OS_WIN |
| const wchar_t kElevatedProcessDisabledName[] |
| = L"elevated_process_disabled"; |
| |
| // Returns true if both array have the same content. |
| template <typename T, size_t ArraySize> |
| bool AreEqualArray(const T (&lhs)[ArraySize], const T (&rhs)[ArraySize]) { |
| for (size_t i = 0; i < ArraySize; ++i) { |
| if (lhs[i] != rhs[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // returns true if the token was created by Secondary Logon |
| // (typically via RunAs command) or UAC (w/ alternative credential provided) |
| // or if failed to determine. |
| bool IsDifferentUser(const HANDLE hToken) { |
| TOKEN_SOURCE src; |
| DWORD dwReturnedBc; |
| |
| // Get TOKEN_SOURCE |
| if (!::GetTokenInformation(hToken, TokenSource, |
| &src, sizeof(src), &dwReturnedBc)) { |
| // Most likely there was an error. |
| return true; |
| } |
| |
| // SourceName is not always null-terminated. |
| // We're looking for the cases marked '->'. |
| // Vista SP1 (Normal) "User32 \0" |
| // -> Vista SP1 (RunAs): "seclogo\0" |
| // -> Vista SP1 (Over-the-shoulder UAC): "CredPro\0" |
| |
| // Sacrifice the last character. That is practically ok for our purpose. |
| src.SourceName[TOKEN_SOURCE_LENGTH - 1] = '\0'; |
| |
| const char kSeclogo[] = "seclogo"; |
| const char kCredPro[] = "CredPro"; |
| |
| return (AreEqualArray(kSeclogo, src.SourceName) || |
| AreEqualArray(kCredPro, src.SourceName)); |
| } |
| |
| // Returns true if UAC gave the high integrity level to the token |
| // or if failed to determine. |
| // This code is written by thatanaka |
| bool IsElevatedByUAC(const HANDLE hToken) { |
| // Get TokenElevationType. |
| DWORD dwSize; |
| TOKEN_ELEVATION_TYPE ElevationType; |
| if (!::GetTokenInformation(hToken, TokenElevationType, &ElevationType, |
| sizeof(ElevationType), &dwSize )) { |
| return true; |
| } |
| |
| // Only TokenElevationTypeFull means the process token was elevated by UAC. |
| if (TokenElevationTypeFull != ElevationType) { |
| return false; |
| } |
| |
| // Although rare, it is still possible for an elevated token to have a lower |
| // integrity level. |
| // Checking to see if it is actually higher than medium. |
| |
| // Get TokenIntegrityLevel. |
| BYTE buffer[sizeof(TOKEN_MANDATORY_LABEL) + SECURITY_MAX_SID_SIZE]; |
| if (!::GetTokenInformation(hToken, TokenIntegrityLevel, buffer, |
| sizeof(buffer), &dwSize )) { |
| return true; |
| } |
| |
| PTOKEN_MANDATORY_LABEL pMandatoryLabel = |
| reinterpret_cast<PTOKEN_MANDATORY_LABEL>(buffer); |
| |
| // Since the SID was acquired from token, it should be always valid. |
| DCHECK(::IsValidSid(pMandatoryLabel->Label.Sid)); |
| |
| // Find the integrity level RID. |
| PDWORD pdwIntegrityLevelRID; |
| pdwIntegrityLevelRID = ::GetSidSubAuthority(pMandatoryLabel->Label.Sid, |
| 0 /* index 0 always exists */); |
| if (!pdwIntegrityLevelRID) { |
| return true; |
| } |
| |
| return (SECURITY_MANDATORY_MEDIUM_RID < *pdwIntegrityLevelRID); |
| } |
| #endif // OS_WIN |
| } // namespace |
| |
| RunLevel::RunLevelType RunLevel::GetRunLevel(RunLevel::RequestType type) { |
| #ifdef OS_WIN |
| bool is_service_process = false; |
| if (!WinUtil::IsServiceProcess(&is_service_process)) { |
| // Returns DENY conservatively. |
| return RunLevel::DENY; |
| } |
| if (is_service_process) { |
| return RunLevel::DENY; |
| } |
| |
| // Get process token |
| HANDLE hProcessToken = NULL; |
| if (!::OpenProcessToken(::GetCurrentProcess(), |
| TOKEN_QUERY | TOKEN_QUERY_SOURCE, |
| &hProcessToken)) { |
| return RunLevel::DENY; |
| } |
| |
| ScopedHandle process_token(hProcessToken); |
| |
| // Opt out of elevated process. |
| if (CLIENT == type && |
| GetElevatedProcessDisabled() && |
| mozc::IsElevatedByUAC(process_token.get())) { |
| return RunLevel::DENY; |
| } |
| |
| // Get thread token (if any) |
| HANDLE hThreadToken = NULL; |
| if (!::OpenThreadToken(::GetCurrentThread(), |
| TOKEN_QUERY, TRUE, &hThreadToken) && |
| ERROR_NO_TOKEN != ::GetLastError()) { |
| return RunLevel::DENY; |
| } |
| |
| ScopedHandle thread_token(hThreadToken); |
| |
| // Thread token (if any) must not a service account. |
| if (NULL != thread_token.get()) { |
| bool is_service_thread = false; |
| if (!WinUtil::IsServiceUser(thread_token.get(), &is_service_thread)) { |
| // Returns DENY conservatively. |
| return RunLevel::DENY; |
| } |
| if (is_service_thread) { |
| return RunLevel::DENY; |
| } |
| } |
| |
| // Check whether the server/renderer is running inside sandbox. |
| if (type == SERVER || type == RENDERER) { |
| // Restricted token must be created by sandbox. |
| // Server is launched with NON_ADMIN so that it can use SSL access. |
| // This is why it doesn't have restricted token. b/5502343 |
| if (type != SERVER && !::IsTokenRestricted(process_token.get())) { |
| return RunLevel::DENY; |
| } |
| |
| // Thread token must be created by sandbox. |
| if (NULL == thread_token.get()){ |
| return RunLevel::DENY; |
| } |
| |
| // Get the server path before the process is sandboxed. |
| // SHGetFolderPath may fail in a sandboxed process. |
| // See http://b/2301066 for details. |
| const volatile string sys_dir = SystemUtil::GetServerDirectory(); |
| |
| // Get the user profile path here because of the same reason. |
| // See http://b/2301066 for details. |
| const string user_dir = SystemUtil::GetUserProfileDirectory(); |
| |
| wstring dir; |
| Util::UTF8ToWide(user_dir.c_str(), &dir); |
| ScopedHandle dir_handle(::CreateFile(dir.c_str(), |
| READ_CONTROL | WRITE_DAC, |
| 0, |
| NULL, |
| OPEN_EXISTING, |
| FILE_FLAG_BACKUP_SEMANTICS, |
| 0)); |
| if (NULL != dir_handle.get()) { |
| BYTE buffer[sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE]; |
| DWORD size = 0; |
| if (::GetTokenInformation(thread_token.get(), TokenUser, |
| buffer, sizeof(buffer), &size)) { |
| PTOKEN_USER ptoken_user = reinterpret_cast<PTOKEN_USER>(buffer); |
| // Looks like in some environment, the profile foler's permission |
| // includes Administrators but does not include the user himself. |
| // In such a case, Mozc wouldn't work because Administrators identity |
| // is removed by sandboxing so we should recover the permission here. |
| // See http://b/2317718 for details. |
| WinSandbox::AddKnownSidToKernelObject( |
| dir_handle.get(), static_cast<SID *>(ptoken_user->User.Sid), |
| SUB_CONTAINERS_AND_OBJECTS_INHERIT, GENERIC_ALL); |
| } |
| } |
| |
| // Revert from the impersonation token supplied by sandbox. |
| // Note: This succeeds even when the thread is not impersonating. |
| if (!::RevertToSelf()) { |
| return RunLevel::DENY; |
| } |
| } |
| |
| // All DENY checks are passed. |
| |
| // Check whether the server/renderer is running as RunAs. |
| if (type == SERVER || type == RENDERER) { |
| // It's ok to do this check after RevertToSelf, as it's a process token |
| // and also its handle was opened before. |
| if (IsDifferentUser(process_token.get())) { |
| // Run in RESTRICTED level in order to prevent the process from running |
| // too long in another user's desktop. |
| return RunLevel::RESTRICTED; |
| } |
| } |
| |
| return RunLevel::NORMAL; |
| |
| #else |
| |
| // Linux or Mac |
| if (type == SERVER || type == RENDERER) { |
| if (::geteuid() == 0) { |
| // This process is started by root, or the executable is setuid to root. |
| |
| // TODO(yusukes): It would be better to add 'SAFE' run-level which |
| // prohibits all mutable operations to local resources and return the |
| // level after calling chroot("/somewhere/safe"), setgid("nogroup"), |
| // and setuid("nobody") here. This is because many novice Linux users |
| // tend to login to their desktop as root. |
| |
| return RunLevel::DENY; |
| } |
| if (::getuid() == 0) { |
| // The executable is setuided to non-root and is started by root user? |
| // This is unexpected. Returns DENY. |
| return RunLevel::DENY; |
| } |
| return RunLevel::NORMAL; |
| } |
| |
| // type is 'CLIENT' |
| if (::geteuid() == 0 || ::getuid() == 0) { |
| // When mozc.so is loaded into a privileged process, deny clients to use |
| // dictionary_tool and config_dialog. |
| return RunLevel::DENY; |
| } |
| |
| return RunLevel::NORMAL; |
| |
| #endif |
| } |
| |
| bool RunLevel::IsProcessInJob() { |
| #ifdef OS_WIN |
| // Check to see if we're in a job where |
| // we can't create a child in our sandbox |
| |
| JOBOBJECT_EXTENDED_LIMIT_INFORMATION JobExtLimitInfo; |
| // Get the job information of the current process |
| if (!::QueryInformationJobObject( |
| NULL, JobObjectExtendedLimitInformation, |
| &JobExtLimitInfo, sizeof(JobExtLimitInfo), NULL)) { |
| return false; |
| } |
| |
| // Check to see if we can break away from the current job |
| if (JobExtLimitInfo.BasicLimitInformation.LimitFlags & |
| (JOB_OBJECT_LIMIT_BREAKAWAY_OK | |
| JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK)) { |
| // We're in a job, but it allows to break away. |
| return false; |
| } |
| |
| return true; |
| #else |
| return false; |
| #endif |
| } |
| |
| bool RunLevel::IsElevatedByUAC() { |
| #ifdef OS_WIN |
| // Get process token |
| HANDLE hProcessToken = NULL; |
| if (!::OpenProcessToken(::GetCurrentProcess(), |
| TOKEN_QUERY | TOKEN_QUERY_SOURCE, |
| &hProcessToken)) { |
| return false; |
| } |
| |
| ScopedHandle process_token(hProcessToken); |
| return mozc::IsElevatedByUAC(process_token.get()); |
| #else |
| return false; |
| #endif |
| } |
| |
| bool RunLevel::SetElevatedProcessDisabled(bool disable) { |
| #ifdef OS_WIN |
| HKEY key = 0; |
| LONG result = ::RegCreateKeyExW(HKEY_CURRENT_USER, |
| kElevatedProcessDisabledKey, |
| 0, |
| NULL, |
| 0, |
| KEY_WRITE, |
| NULL, |
| &key, |
| NULL); |
| |
| if (ERROR_SUCCESS != result) { |
| return false; |
| } |
| |
| const DWORD value = disable ? 1 : 0; |
| result = ::RegSetValueExW(key, |
| kElevatedProcessDisabledName, |
| 0, |
| REG_DWORD, |
| reinterpret_cast<const BYTE *>(&value), |
| sizeof(value)); |
| ::RegCloseKey(key); |
| |
| return ERROR_SUCCESS == result; |
| #else |
| return false; |
| #endif |
| } |
| |
| bool RunLevel::GetElevatedProcessDisabled() { |
| #ifdef OS_WIN |
| HKEY key = 0; |
| LONG result = ::RegOpenKeyExW(HKEY_CURRENT_USER, |
| kElevatedProcessDisabledKey, |
| NULL, |
| KEY_READ, |
| &key); |
| if (ERROR_SUCCESS != result) { |
| return false; |
| } |
| |
| DWORD value = 0; |
| DWORD value_size = sizeof(value); |
| DWORD value_type = 0; |
| result = ::RegQueryValueEx(key, |
| kElevatedProcessDisabledName, |
| NULL, |
| &value_type, |
| reinterpret_cast<BYTE *>(&value), |
| &value_size); |
| ::RegCloseKey(key); |
| |
| if (ERROR_SUCCESS != result || |
| value_type != REG_DWORD || |
| value_size != sizeof(value)) { |
| return false; |
| } |
| |
| return value > 0; |
| #else |
| return false; |
| #endif |
| } |
| } // namespace mozc |