xref: /aosp_15_r20/external/cronet/components/nacl/browser/nacl_file_host.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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