| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome_frame/dll_redirector.h" | |
| 6 | |
| 7 #include <aclapi.h> | |
| 8 #include <atlbase.h> | |
| 9 #include <atlsecurity.h> | |
| 10 #include <sddl.h> | |
| 11 | |
| 12 #include "base/file_version_info.h" | |
| 13 #include "base/files/file_path.h" | |
| 14 #include "base/logging.h" | |
| 15 #include "base/memory/shared_memory.h" | |
| 16 #include "base/path_service.h" | |
| 17 #include "base/strings/string_util.h" | |
| 18 #include "base/strings/utf_string_conversions.h" | |
| 19 #include "base/version.h" | |
| 20 #include "base/win/windows_version.h" | |
| 21 #include "chrome_frame/utils.h" | |
| 22 | |
| 23 const wchar_t kSharedMemoryName[] = L"ChromeFrameVersionBeacon_"; | |
| 24 const uint32 kSharedMemorySize = 128; | |
| 25 const uint32 kSharedMemoryLockTimeoutMs = 1000; | |
| 26 | |
| 27 // static | |
| 28 DllRedirector::DllRedirector() : first_module_handle_(NULL) { | |
| 29 // TODO(robertshield): Allow for overrides to be taken from the environment. | |
| 30 std::wstring beacon_name(kSharedMemoryName); | |
| 31 beacon_name += GetHostProcessName(false); | |
| 32 shared_memory_.reset(new base::SharedMemory(beacon_name)); | |
| 33 shared_memory_name_ = base::WideToUTF8(beacon_name); | |
| 34 } | |
| 35 | |
| 36 DllRedirector::DllRedirector(const char* shared_memory_name) | |
| 37 : shared_memory_name_(shared_memory_name), first_module_handle_(NULL) { | |
| 38 shared_memory_.reset( | |
| 39 new base::SharedMemory(base::ASCIIToWide(shared_memory_name))); | |
| 40 } | |
| 41 | |
| 42 DllRedirector::~DllRedirector() { | |
| 43 if (first_module_handle_) { | |
| 44 if (first_module_handle_ != reinterpret_cast<HMODULE>(&__ImageBase)) { | |
| 45 FreeLibrary(first_module_handle_); | |
| 46 } else { | |
| 47 NOTREACHED() << "Error, DllRedirector attempting to free self."; | |
| 48 } | |
| 49 | |
| 50 first_module_handle_ = NULL; | |
| 51 } | |
| 52 UnregisterAsFirstCFModule(); | |
| 53 } | |
| 54 | |
| 55 // static | |
| 56 DllRedirector* DllRedirector::GetInstance() { | |
| 57 return Singleton<DllRedirector>::get(); | |
| 58 } | |
| 59 | |
| 60 bool DllRedirector::BuildSecurityAttributesForLock( | |
| 61 CSecurityAttributes* sec_attr) { | |
| 62 DCHECK(sec_attr); | |
| 63 if (base::win::GetVersion() < base::win::VERSION_VISTA) { | |
| 64 // Don't bother with changing ACLs on pre-vista. | |
| 65 return false; | |
| 66 } | |
| 67 | |
| 68 bool success = false; | |
| 69 | |
| 70 // Fill out the rest of the security descriptor from the process token. | |
| 71 CAccessToken token; | |
| 72 if (token.GetProcessToken(TOKEN_QUERY)) { | |
| 73 CSecurityDesc security_desc; | |
| 74 // Set the SACL from an SDDL string that allows access to low-integrity | |
| 75 // processes. See http://msdn.microsoft.com/en-us/library/bb625958.aspx. | |
| 76 if (security_desc.FromString(L"S:(ML;;NW;;;LW)")) { | |
| 77 CSid sid_owner; | |
| 78 if (token.GetOwner(&sid_owner)) { | |
| 79 security_desc.SetOwner(sid_owner); | |
| 80 } else { | |
| 81 NOTREACHED() << "Could not get owner."; | |
| 82 } | |
| 83 CSid sid_group; | |
| 84 if (token.GetPrimaryGroup(&sid_group)) { | |
| 85 security_desc.SetGroup(sid_group); | |
| 86 } else { | |
| 87 NOTREACHED() << "Could not get group."; | |
| 88 } | |
| 89 CDacl dacl; | |
| 90 if (token.GetDefaultDacl(&dacl)) { | |
| 91 // Add an access control entry mask for the current user. | |
| 92 // This is what grants this user access from lower integrity levels. | |
| 93 CSid sid_user; | |
| 94 if (token.GetUser(&sid_user)) { | |
| 95 success = dacl.AddAllowedAce(sid_user, MUTEX_ALL_ACCESS); | |
| 96 security_desc.SetDacl(dacl); | |
| 97 sec_attr->Set(security_desc); | |
| 98 } | |
| 99 } | |
| 100 } | |
| 101 } | |
| 102 | |
| 103 return success; | |
| 104 } | |
| 105 | |
| 106 bool DllRedirector::SetFileMappingToReadOnly(base::SharedMemoryHandle mapping) { | |
| 107 bool success = false; | |
| 108 | |
| 109 CAccessToken token; | |
| 110 if (token.GetProcessToken(TOKEN_QUERY)) { | |
| 111 CSid sid_user; | |
| 112 if (token.GetUser(&sid_user)) { | |
| 113 CDacl dacl; | |
| 114 dacl.AddAllowedAce(sid_user, STANDARD_RIGHTS_READ | FILE_MAP_READ); | |
| 115 success = AtlSetDacl(mapping, SE_KERNEL_OBJECT, dacl); | |
| 116 } | |
| 117 } | |
| 118 | |
| 119 return success; | |
| 120 } | |
| 121 | |
| 122 | |
| 123 bool DllRedirector::RegisterAsFirstCFModule() { | |
| 124 DCHECK(first_module_handle_ == NULL); | |
| 125 | |
| 126 // Build our own file version outside of the lock: | |
| 127 scoped_ptr<Version> our_version(GetCurrentModuleVersion()); | |
| 128 | |
| 129 // We sadly can't use the autolock here since we want to have a timeout. | |
| 130 // Be careful not to return while holding the lock. Also, attempt to do as | |
| 131 // little as possible while under this lock. | |
| 132 | |
| 133 bool lock_acquired = false; | |
| 134 CSecurityAttributes sec_attr; | |
| 135 if (base::win::GetVersion() >= base::win::VERSION_VISTA && | |
| 136 BuildSecurityAttributesForLock(&sec_attr)) { | |
| 137 // On vista and above, we need to explicitly allow low integrity access | |
| 138 // to our objects. On XP, we don't bother. | |
| 139 lock_acquired = shared_memory_->Lock(kSharedMemoryLockTimeoutMs, &sec_attr); | |
| 140 } else { | |
| 141 lock_acquired = shared_memory_->Lock(kSharedMemoryLockTimeoutMs, NULL); | |
| 142 } | |
| 143 | |
| 144 if (!lock_acquired) { | |
| 145 // We couldn't get the lock in a reasonable amount of time, so fall | |
| 146 // back to loading our current version. We return true to indicate that the | |
| 147 // caller should not attempt to delegate to an already loaded version. | |
| 148 dll_version_.swap(our_version); | |
| 149 return true; | |
| 150 } | |
| 151 | |
| 152 bool created_beacon = true; | |
| 153 bool result = shared_memory_->CreateNamed(shared_memory_name_.c_str(), | |
| 154 false, // open_existing | |
| 155 kSharedMemorySize); | |
| 156 | |
| 157 if (result) { | |
| 158 // We created the beacon, now we need to mutate the security attributes | |
| 159 // on the shared memory to allow read-only access and let low-integrity | |
| 160 // processes open it. This will fail on FAT32 file systems. | |
| 161 if (!SetFileMappingToReadOnly(shared_memory_->handle())) { | |
| 162 DLOG(ERROR) << "Failed to set file mapping permissions."; | |
| 163 } | |
| 164 } else { | |
| 165 created_beacon = false; | |
| 166 | |
| 167 // We failed to create the shared memory segment, suggesting it may already | |
| 168 // exist: try to create it read-only. | |
| 169 result = shared_memory_->Open(shared_memory_name_.c_str(), | |
| 170 true /* read_only */); | |
| 171 } | |
| 172 | |
| 173 if (result) { | |
| 174 // Map in the whole thing. | |
| 175 result = shared_memory_->Map(0); | |
| 176 DCHECK(shared_memory_->memory()); | |
| 177 | |
| 178 if (result) { | |
| 179 // Either write our own version number or read it in if it was already | |
| 180 // present in the shared memory section. | |
| 181 if (created_beacon) { | |
| 182 dll_version_.swap(our_version); | |
| 183 | |
| 184 lstrcpynA(reinterpret_cast<char*>(shared_memory_->memory()), | |
| 185 dll_version_->GetString().c_str(), | |
| 186 std::min(kSharedMemorySize, | |
| 187 dll_version_->GetString().length() + 1)); | |
| 188 } else { | |
| 189 char buffer[kSharedMemorySize] = {0}; | |
| 190 memcpy(buffer, shared_memory_->memory(), kSharedMemorySize - 1); | |
| 191 dll_version_.reset(new Version(buffer)); | |
| 192 | |
| 193 if (!dll_version_->IsValid() || | |
| 194 dll_version_->Equals(*our_version.get())) { | |
| 195 // If we either couldn't parse a valid version out of the shared | |
| 196 // memory or we did parse a version and it is the same as our own, | |
| 197 // then pretend we're first in to avoid trying to load any other DLLs. | |
| 198 dll_version_.reset(our_version.release()); | |
| 199 created_beacon = true; | |
| 200 } | |
| 201 } | |
| 202 } else { | |
| 203 NOTREACHED() << "Failed to map in version beacon."; | |
| 204 } | |
| 205 } else { | |
| 206 NOTREACHED() << "Could not create file mapping for version beacon, gle: " | |
| 207 << ::GetLastError(); | |
| 208 } | |
| 209 | |
| 210 // Matching Unlock. | |
| 211 shared_memory_->Unlock(); | |
| 212 | |
| 213 return created_beacon; | |
| 214 } | |
| 215 | |
| 216 void DllRedirector::UnregisterAsFirstCFModule() { | |
| 217 if (base::SharedMemory::IsHandleValid(shared_memory_->handle())) { | |
| 218 bool lock_acquired = shared_memory_->Lock(kSharedMemoryLockTimeoutMs, NULL); | |
| 219 if (lock_acquired) { | |
| 220 // Free our handles. The last closed handle SHOULD result in it being | |
| 221 // deleted. | |
| 222 shared_memory_->Close(); | |
| 223 shared_memory_->Unlock(); | |
| 224 } | |
| 225 } | |
| 226 } | |
| 227 | |
| 228 LPFNGETCLASSOBJECT DllRedirector::GetDllGetClassObjectPtr() { | |
| 229 HMODULE first_module_handle = GetFirstModule(); | |
| 230 | |
| 231 LPFNGETCLASSOBJECT proc_ptr = NULL; | |
| 232 if (first_module_handle) { | |
| 233 proc_ptr = reinterpret_cast<LPFNGETCLASSOBJECT>( | |
| 234 GetProcAddress(first_module_handle, "DllGetClassObject")); | |
| 235 DPLOG_IF(ERROR, !proc_ptr) << "DllRedirector: Could not get address of " | |
| 236 "DllGetClassObject from first loaded module."; | |
| 237 } | |
| 238 | |
| 239 return proc_ptr; | |
| 240 } | |
| 241 | |
| 242 Version* DllRedirector::GetCurrentModuleVersion() { | |
| 243 scoped_ptr<FileVersionInfo> file_version_info( | |
| 244 FileVersionInfo::CreateFileVersionInfoForCurrentModule()); | |
| 245 DCHECK(file_version_info.get()); | |
| 246 | |
| 247 scoped_ptr<Version> current_version; | |
| 248 if (file_version_info.get()) { | |
| 249 current_version.reset( | |
| 250 new Version(WideToASCII(file_version_info->file_version()))); | |
| 251 DCHECK(current_version->IsValid()); | |
| 252 } | |
| 253 | |
| 254 return current_version.release(); | |
| 255 } | |
| 256 | |
| 257 HMODULE DllRedirector::GetFirstModule() { | |
| 258 DCHECK(dll_version_.get()) | |
| 259 << "Error: Did you call RegisterAsFirstCFModule() first?"; | |
| 260 | |
| 261 if (first_module_handle_ == NULL) { | |
| 262 first_module_handle_ = LoadVersionedModule(dll_version_.get()); | |
| 263 } | |
| 264 | |
| 265 if (first_module_handle_ == reinterpret_cast<HMODULE>(&__ImageBase)) { | |
| 266 NOTREACHED() << "Should not be loading own version."; | |
| 267 first_module_handle_ = NULL; | |
| 268 } | |
| 269 | |
| 270 return first_module_handle_; | |
| 271 } | |
| 272 | |
| 273 HMODULE DllRedirector::LoadVersionedModule(Version* version) { | |
| 274 DCHECK(version); | |
| 275 | |
| 276 HMODULE hmodule = NULL; | |
| 277 wchar_t system_buffer[MAX_PATH]; | |
| 278 HMODULE this_module = reinterpret_cast<HMODULE>(&__ImageBase); | |
| 279 system_buffer[0] = 0; | |
| 280 if (GetModuleFileName(this_module, system_buffer, | |
| 281 arraysize(system_buffer)) != 0) { | |
| 282 base::FilePath module_path(system_buffer); | |
| 283 | |
| 284 // For a module located in | |
| 285 // Foo\XXXXXXXXX\<module>.dll, load | |
| 286 // Foo\<version>\<module>.dll: | |
| 287 base::FilePath module_name = module_path.BaseName(); | |
| 288 module_path = module_path.DirName() | |
| 289 .DirName() | |
| 290 .Append(base::ASCIIToWide(version->GetString())) | |
| 291 .Append(module_name); | |
| 292 | |
| 293 hmodule = LoadLibrary(module_path.value().c_str()); | |
| 294 if (hmodule == NULL) { | |
| 295 DPLOG(ERROR) << "Could not load reported module version " | |
| 296 << version->GetString(); | |
| 297 } | |
| 298 } else { | |
| 299 DPLOG(FATAL) << "Failed to get module file name"; | |
| 300 } | |
| 301 return hmodule; | |
| 302 } | |
| OLD | NEW |