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 "components/nacl/browser/nacl_file_host.h"
6
7#include "base/bind.h"
8#include "base/files/file.h"
9#include "base/files/file_path.h"
10#include "base/files/file_util.h"
11#include "base/path_service.h"
12#include "base/strings/utf_string_conversions.h"
13#include "base/threading/sequenced_worker_pool.h"
14#include "components/nacl/browser/nacl_browser.h"
15#include "components/nacl/browser/nacl_browser_delegate.h"
16#include "components/nacl/browser/nacl_host_message_filter.h"
17#include "components/nacl/common/nacl_host_messages.h"
18#include "content/public/browser/browser_thread.h"
19#include "content/public/browser/render_view_host.h"
20#include "content/public/browser/site_instance.h"
21#include "ipc/ipc_platform_file.h"
22
23using content::BrowserThread;
24
25namespace {
26
27// Force a prefix to prevent user from opening "magic" files.
28const char* kExpectedFilePrefix = "pnacl_public_";
29
30// Restrict PNaCl file lengths to reduce likelyhood of hitting bugs
31// in file name limit error-handling-code-paths, etc.
32const size_t kMaxFileLength = 40;
33
34void NotifyRendererOfError(
35    nacl::NaClHostMessageFilter* nacl_host_message_filter,
36    IPC::Message* reply_msg) {
37  reply_msg->set_reply_error();
38  nacl_host_message_filter->Send(reply_msg);
39}
40
41typedef void (*WriteFileInfoReply)(IPC::Message* reply_msg,
42                                   IPC::PlatformFileForTransit file_desc,
43                                   uint64 file_token_lo,
44                                   uint64 file_token_hi);
45
46void DoRegisterOpenedNaClExecutableFile(
47    scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter,
48    base::File file,
49    base::FilePath file_path,
50    IPC::Message* reply_msg,
51    WriteFileInfoReply write_reply_message) {
52  // IO thread owns the NaClBrowser singleton.
53  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
54
55  nacl::NaClBrowser* nacl_browser = nacl::NaClBrowser::GetInstance();
56  uint64 file_token_lo = 0;
57  uint64 file_token_hi = 0;
58  nacl_browser->PutFilePath(file_path, &file_token_lo, &file_token_hi);
59
60  IPC::PlatformFileForTransit file_desc = IPC::TakeFileHandleForProcess(
61      file.Pass(),
62      nacl_host_message_filter->PeerHandle());
63
64  write_reply_message(reply_msg, file_desc, file_token_lo, file_token_hi);
65  nacl_host_message_filter->Send(reply_msg);
66}
67
68void DoOpenPnaclFile(
69    scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter,
70    const std::string& filename,
71    bool is_executable,
72    IPC::Message* reply_msg) {
73  DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
74  base::FilePath full_filepath;
75
76  // PNaCl must be installed.
77  base::FilePath pnacl_dir;
78  if (!nacl::NaClBrowser::GetDelegate()->GetPnaclDirectory(&pnacl_dir) ||
79      !base::PathExists(pnacl_dir)) {
80    NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
81    return;
82  }
83
84  // Do some validation.
85  if (!nacl_file_host::PnaclCanOpenFile(filename, &full_filepath)) {
86    NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
87    return;
88  }
89
90  base::File file_to_open = nacl::OpenNaClReadExecImpl(full_filepath,
91                                                       is_executable);
92  if (!file_to_open.IsValid()) {
93    NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
94    return;
95  }
96
97  // This function is running on the blocking pool, but the path needs to be
98  // registered in a structure owned by the IO thread.
99  // Not all PNaCl files are executable. Only register those that are
100  // executable in the NaCl file_path cache.
101  if (is_executable) {
102    BrowserThread::PostTask(
103        BrowserThread::IO, FROM_HERE,
104        base::Bind(&DoRegisterOpenedNaClExecutableFile,
105                   nacl_host_message_filter,
106                   Passed(file_to_open.Pass()), full_filepath, reply_msg,
107                   static_cast<WriteFileInfoReply>(
108                       NaClHostMsg_GetReadonlyPnaclFD::WriteReplyParams)));
109  } else {
110    IPC::PlatformFileForTransit target_desc =
111        IPC::TakeFileHandleForProcess(file_to_open.Pass(),
112                                      nacl_host_message_filter->PeerHandle());
113    uint64_t dummy_file_token = 0;
114    NaClHostMsg_GetReadonlyPnaclFD::WriteReplyParams(
115        reply_msg, target_desc, dummy_file_token, dummy_file_token);
116    nacl_host_message_filter->Send(reply_msg);
117  }
118}
119
120// Convert the file URL into a file descriptor.
121// This function is security sensitive.  Be sure to check with a security
122// person before you modify it.
123void DoOpenNaClExecutableOnThreadPool(
124    scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter,
125    const GURL& file_url,
126    IPC::Message* reply_msg) {
127  DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
128
129  base::FilePath file_path;
130  if (!nacl::NaClBrowser::GetDelegate()->MapUrlToLocalFilePath(
131          file_url,
132          true /* use_blocking_api */,
133          nacl_host_message_filter->profile_directory(),
134          &file_path)) {
135    NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
136    return;
137  }
138
139  base::File file = nacl::OpenNaClReadExecImpl(file_path,
140                                               true /* is_executable */);
141  if (file.IsValid()) {
142    // This function is running on the blocking pool, but the path needs to be
143    // registered in a structure owned by the IO thread.
144    BrowserThread::PostTask(
145        BrowserThread::IO, FROM_HERE,
146        base::Bind(
147            &DoRegisterOpenedNaClExecutableFile,
148            nacl_host_message_filter,
149            Passed(file.Pass()), file_path, reply_msg,
150            static_cast<WriteFileInfoReply>(
151                NaClHostMsg_OpenNaClExecutable::WriteReplyParams)));
152  } else {
153    NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
154    return;
155  }
156}
157
158}  // namespace
159
160namespace nacl_file_host {
161
162void GetReadonlyPnaclFd(
163    scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter,
164    const std::string& filename,
165    bool is_executable,
166    IPC::Message* reply_msg) {
167  if (!BrowserThread::PostBlockingPoolTask(
168          FROM_HERE,
169          base::Bind(&DoOpenPnaclFile,
170                     nacl_host_message_filter,
171                     filename,
172                     is_executable,
173                     reply_msg))) {
174    NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
175  }
176}
177
178// This function is security sensitive.  Be sure to check with a security
179// person before you modify it.
180bool PnaclCanOpenFile(const std::string& filename,
181                      base::FilePath* file_to_open) {
182  if (filename.length() > kMaxFileLength)
183    return false;
184
185  if (filename.empty())
186    return false;
187
188  // Restrict character set of the file name to something really simple
189  // (a-z, 0-9, and underscores).
190  for (size_t i = 0; i < filename.length(); ++i) {
191    char charAt = filename[i];
192    if (charAt < 'a' || charAt > 'z')
193      if (charAt < '0' || charAt > '9')
194        if (charAt != '_')
195          return false;
196  }
197
198  // PNaCl must be installed.
199  base::FilePath pnacl_dir;
200  if (!nacl::NaClBrowser::GetDelegate()->GetPnaclDirectory(&pnacl_dir) ||
201      pnacl_dir.empty())
202    return false;
203
204  // Prepend the prefix to restrict files to a whitelisted set.
205  base::FilePath full_path = pnacl_dir.AppendASCII(
206      std::string(kExpectedFilePrefix) + filename);
207  *file_to_open = full_path;
208  return true;
209}
210
211void OpenNaClExecutable(
212    scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter,
213    int render_view_id,
214    const GURL& file_url,
215    IPC::Message* reply_msg) {
216  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
217    BrowserThread::PostTask(
218        BrowserThread::UI, FROM_HERE,
219        base::Bind(
220            &OpenNaClExecutable,
221            nacl_host_message_filter,
222            render_view_id, file_url, reply_msg));
223    return;
224  }
225
226  // Make sure render_view_id is valid and that the URL is a part of the
227  // render view's site. Without these checks, apps could probe the extension
228  // directory or run NaCl code from other extensions.
229  content::RenderViewHost* rvh = content::RenderViewHost::FromID(
230      nacl_host_message_filter->render_process_id(), render_view_id);
231  if (!rvh) {
232    nacl_host_message_filter->BadMessageReceived();  // Kill the renderer.
233    return;
234  }
235  content::SiteInstance* site_instance = rvh->GetSiteInstance();
236  if (!content::SiteInstance::IsSameWebSite(site_instance->GetBrowserContext(),
237                                            site_instance->GetSiteURL(),
238                                            file_url)) {
239    NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
240    return;
241  }
242
243  // The URL is part of the current app. Now query the extension system for the
244  // file path and convert that to a file descriptor. This should be done on a
245  // blocking pool thread.
246  if (!BrowserThread::PostBlockingPoolTask(
247      FROM_HERE,
248      base::Bind(
249          &DoOpenNaClExecutableOnThreadPool,
250          nacl_host_message_filter,
251          file_url, reply_msg))) {
252    NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
253  }
254}
255
256}  // namespace nacl_file_host
257