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