Home | History | Annotate | Download | only in nacl_host
      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