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 |