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/browser/nacl_host/nacl_file_host.h" 6 7 #include "base/bind.h" 8 #include "base/file_util.h" 9 #include "base/files/file_path.h" 10 #include "base/path_service.h" 11 #include "base/platform_file.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "base/threading/sequenced_worker_pool.h" 14 #include "chrome/browser/extensions/extension_info_map.h" 15 #include "chrome/browser/nacl_host/nacl_browser.h" 16 #include "chrome/browser/nacl_host/nacl_host_message_filter.h" 17 #include "chrome/common/extensions/manifest_handlers/shared_module_info.h" 18 #include "components/nacl/common/nacl_browser_delegate.h" 19 #include "components/nacl/common/nacl_host_messages.h" 20 #include "components/nacl/common/pnacl_types.h" 21 #include "content/public/browser/browser_thread.h" 22 #include "content/public/browser/render_view_host.h" 23 #include "content/public/browser/site_instance.h" 24 #include "ipc/ipc_platform_file.h" 25 26 using content::BrowserThread; 27 using extensions::SharedModuleInfo; 28 29 namespace { 30 31 // Force a prefix to prevent user from opening "magic" files. 32 const char* kExpectedFilePrefix = "pnacl_public_"; 33 34 // Restrict PNaCl file lengths to reduce likelyhood of hitting bugs 35 // in file name limit error-handling-code-paths, etc. 36 const size_t kMaxFileLength = 40; 37 38 void NotifyRendererOfError( 39 NaClHostMessageFilter* nacl_host_message_filter, 40 IPC::Message* reply_msg) { 41 reply_msg->set_reply_error(); 42 nacl_host_message_filter->Send(reply_msg); 43 } 44 45 void TryInstallPnacl( 46 const nacl_file_host::InstallCallback& done_callback, 47 const nacl_file_host::InstallProgressCallback& progress_callback) { 48 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 49 // TODO(jvoung): Figure out a way to get progress events and 50 // call progress_callback. 51 NaClBrowser::GetDelegate()->TryInstallPnacl(done_callback); 52 } 53 54 void DoEnsurePnaclInstalled( 55 const nacl_file_host::InstallCallback& done_callback, 56 const nacl_file_host::InstallProgressCallback& progress_callback) { 57 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 58 // If already installed, return reply w/ success immediately. 59 base::FilePath pnacl_dir; 60 if (NaClBrowser::GetDelegate()->GetPnaclDirectory(&pnacl_dir) 61 && !pnacl_dir.empty() 62 && base::PathExists(pnacl_dir)) { 63 done_callback.Run(true); 64 return; 65 } 66 67 // Otherwise, request an install (but send some "unknown" progress first). 68 progress_callback.Run(nacl::PnaclInstallProgress::Unknown()); 69 // TryInstall after sending the progress event so that they are more ordered. 70 BrowserThread::PostTask( 71 BrowserThread::UI, 72 FROM_HERE, 73 base::Bind(&TryInstallPnacl, done_callback, progress_callback)); 74 } 75 76 bool PnaclDoOpenFile(const base::FilePath& file_to_open, 77 base::PlatformFile* out_file) { 78 base::PlatformFileError error_code; 79 *out_file = base::CreatePlatformFile(file_to_open, 80 base::PLATFORM_FILE_OPEN | 81 base::PLATFORM_FILE_READ, 82 NULL, 83 &error_code); 84 if (error_code != base::PLATFORM_FILE_OK) { 85 return false; 86 } 87 return true; 88 } 89 90 void DoOpenPnaclFile( 91 scoped_refptr<NaClHostMessageFilter> nacl_host_message_filter, 92 const std::string& filename, 93 IPC::Message* reply_msg) { 94 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 95 base::FilePath full_filepath; 96 97 // PNaCl must be installed. 98 base::FilePath pnacl_dir; 99 if (!NaClBrowser::GetDelegate()->GetPnaclDirectory(&pnacl_dir) || 100 !base::PathExists(pnacl_dir)) { 101 NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); 102 return; 103 } 104 105 // Do some validation. 106 if (!nacl_file_host::PnaclCanOpenFile(filename, &full_filepath)) { 107 NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); 108 return; 109 } 110 111 base::PlatformFile file_to_open; 112 if (!PnaclDoOpenFile(full_filepath, &file_to_open)) { 113 NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); 114 return; 115 } 116 117 // Send the reply! 118 // Do any DuplicateHandle magic that is necessary first. 119 IPC::PlatformFileForTransit target_desc = 120 IPC::GetFileHandleForProcess(file_to_open, 121 nacl_host_message_filter->PeerHandle(), 122 true /* Close source */); 123 if (target_desc == IPC::InvalidPlatformFileForTransit()) { 124 NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); 125 return; 126 } 127 NaClHostMsg_GetReadonlyPnaclFD::WriteReplyParams( 128 reply_msg, target_desc); 129 nacl_host_message_filter->Send(reply_msg); 130 } 131 132 void DoRegisterOpenedNaClExecutableFile( 133 scoped_refptr<NaClHostMessageFilter> nacl_host_message_filter, 134 base::PlatformFile file, 135 base::FilePath file_path, 136 IPC::Message* reply_msg) { 137 // IO thread owns the NaClBrowser singleton. 138 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 139 140 NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); 141 uint64 file_token_lo = 0; 142 uint64 file_token_hi = 0; 143 nacl_browser->PutFilePath(file_path, &file_token_lo, &file_token_hi); 144 145 IPC::PlatformFileForTransit file_desc = IPC::GetFileHandleForProcess( 146 file, 147 nacl_host_message_filter->PeerHandle(), 148 true /* close_source */); 149 150 NaClHostMsg_OpenNaClExecutable::WriteReplyParams( 151 reply_msg, file_desc, file_token_lo, file_token_hi); 152 nacl_host_message_filter->Send(reply_msg); 153 } 154 155 // Convert the file URL into a file path in the extension directory. 156 // This function is security sensitive. Be sure to check with a security 157 // person before you modify it. 158 bool GetExtensionFilePath( 159 scoped_refptr<ExtensionInfoMap> extension_info_map, 160 const GURL& file_url, 161 base::FilePath* file_path) { 162 // Check that the URL is recognized by the extension system. 163 const extensions::Extension* extension = 164 extension_info_map->extensions().GetExtensionOrAppByURL(file_url); 165 if (!extension) 166 return false; 167 168 std::string path = file_url.path(); 169 extensions::ExtensionResource resource; 170 171 if (SharedModuleInfo::IsImportedPath(path)) { 172 // Check if this is a valid path that is imported for this extension. 173 std::string new_extension_id; 174 std::string new_relative_path; 175 SharedModuleInfo::ParseImportedPath(path, &new_extension_id, 176 &new_relative_path); 177 const extensions::Extension* new_extension = 178 extension_info_map->extensions().GetByID(new_extension_id); 179 if (!new_extension) 180 return false; 181 182 if (!SharedModuleInfo::ImportsExtensionById(extension, new_extension_id) || 183 !SharedModuleInfo::IsExportAllowed(new_extension, new_relative_path)) { 184 return false; 185 } 186 187 resource = new_extension->GetResource(new_relative_path); 188 } else { 189 // Check that the URL references a resource in the extension. 190 resource = extension->GetResource(path); 191 } 192 193 if (resource.empty()) 194 return false; 195 196 const base::FilePath resource_file_path = resource.GetFilePath(); 197 if (resource_file_path.empty()) 198 return false; 199 200 *file_path = resource_file_path; 201 return true; 202 } 203 204 // Convert the file URL into a file descriptor. 205 // This function is security sensitive. Be sure to check with a security 206 // person before you modify it. 207 void DoOpenNaClExecutableOnThreadPool( 208 scoped_refptr<NaClHostMessageFilter> nacl_host_message_filter, 209 scoped_refptr<ExtensionInfoMap> extension_info_map, 210 const GURL& file_url, 211 IPC::Message* reply_msg) { 212 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 213 214 base::FilePath file_path; 215 if (!GetExtensionFilePath(extension_info_map, file_url, &file_path)) { 216 NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); 217 return; 218 } 219 220 base::PlatformFile file; 221 nacl::OpenNaClExecutableImpl(file_path, &file); 222 if (file != base::kInvalidPlatformFileValue) { 223 // This function is running on the blocking pool, but the path needs to be 224 // registered in a structure owned by the IO thread. 225 BrowserThread::PostTask( 226 BrowserThread::IO, FROM_HERE, 227 base::Bind( 228 &DoRegisterOpenedNaClExecutableFile, 229 nacl_host_message_filter, 230 file, file_path, reply_msg)); 231 } else { 232 NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); 233 return; 234 } 235 } 236 237 } // namespace 238 239 namespace nacl_file_host { 240 241 void EnsurePnaclInstalled( 242 const InstallCallback& done_callback, 243 const InstallProgressCallback& progress_callback) { 244 if (!BrowserThread::PostBlockingPoolTask( 245 FROM_HERE, 246 base::Bind(&DoEnsurePnaclInstalled, 247 done_callback, 248 progress_callback))) { 249 done_callback.Run(false); 250 } 251 } 252 253 void GetReadonlyPnaclFd( 254 scoped_refptr<NaClHostMessageFilter> nacl_host_message_filter, 255 const std::string& filename, 256 IPC::Message* reply_msg) { 257 if (!BrowserThread::PostBlockingPoolTask( 258 FROM_HERE, 259 base::Bind(&DoOpenPnaclFile, 260 nacl_host_message_filter, 261 filename, 262 reply_msg))) { 263 NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); 264 } 265 } 266 267 // This function is security sensitive. Be sure to check with a security 268 // person before you modify it. 269 bool PnaclCanOpenFile(const std::string& filename, 270 base::FilePath* file_to_open) { 271 if (filename.length() > kMaxFileLength) 272 return false; 273 274 if (filename.empty()) 275 return false; 276 277 // Restrict character set of the file name to something really simple 278 // (a-z, 0-9, and underscores). 279 for (size_t i = 0; i < filename.length(); ++i) { 280 char charAt = filename[i]; 281 if (charAt < 'a' || charAt > 'z') 282 if (charAt < '0' || charAt > '9') 283 if (charAt != '_') 284 return false; 285 } 286 287 // PNaCl must be installed. 288 base::FilePath pnacl_dir; 289 if (!NaClBrowser::GetDelegate()->GetPnaclDirectory(&pnacl_dir) || 290 pnacl_dir.empty()) 291 return false; 292 293 // Prepend the prefix to restrict files to a whitelisted set. 294 base::FilePath full_path = pnacl_dir.AppendASCII( 295 std::string(kExpectedFilePrefix) + filename); 296 *file_to_open = full_path; 297 return true; 298 } 299 300 void OpenNaClExecutable( 301 scoped_refptr<NaClHostMessageFilter> nacl_host_message_filter, 302 scoped_refptr<ExtensionInfoMap> extension_info_map, 303 int render_view_id, 304 const GURL& file_url, 305 IPC::Message* reply_msg) { 306 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 307 BrowserThread::PostTask( 308 BrowserThread::UI, FROM_HERE, 309 base::Bind( 310 &OpenNaClExecutable, 311 nacl_host_message_filter, 312 extension_info_map, 313 render_view_id, file_url, reply_msg)); 314 return; 315 } 316 317 // Make sure render_view_id is valid and that the URL is a part of the 318 // render view's site. Without these checks, apps could probe the extension 319 // directory or run NaCl code from other extensions. 320 content::RenderViewHost* rvh = content::RenderViewHost::FromID( 321 nacl_host_message_filter->render_process_id(), render_view_id); 322 if (!rvh) { 323 nacl_host_message_filter->BadMessageReceived(); // Kill the renderer. 324 return; 325 } 326 content::SiteInstance* site_instance = rvh->GetSiteInstance(); 327 if (!content::SiteInstance::IsSameWebSite(site_instance->GetBrowserContext(), 328 site_instance->GetSiteURL(), 329 file_url)) { 330 NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); 331 return; 332 } 333 334 // The URL is part of the current app. Now query the extension system for the 335 // file path and convert that to a file descriptor. This should be done on a 336 // blocking pool thread. 337 if (!BrowserThread::PostBlockingPoolTask( 338 FROM_HERE, 339 base::Bind( 340 &DoOpenNaClExecutableOnThreadPool, 341 nacl_host_message_filter, 342 extension_info_map, 343 file_url, reply_msg))) { 344 NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); 345 } 346 } 347 348 } // namespace nacl_file_host 349