1 /* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7     http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 #include "tensorflow/c/experimental/filesystem/modular_filesystem_registration.h"
16 
17 #include "tensorflow/c/experimental/filesystem/modular_filesystem.h"
18 #include "tensorflow/c/tf_status_internal.h"
19 #include "tensorflow/core/platform/env.h"
20 #include "tensorflow/core/platform/errors.h"
21 #include "tensorflow/core/util/ptr_util.h"
22 
23 namespace tensorflow {
24 
25 // Checks that all schemes provided by a plugin are valid.
26 // TODO(b/139060984): More validation could be done here, based on supported
27 // charset, maximum length, etc. Punting it for later.
ValidateScheme(const char * scheme)28 static Status ValidateScheme(const char* scheme) {
29   if (scheme == nullptr)
30     return errors::InvalidArgument(
31         "Attempted to register filesystem with `nullptr` URI scheme");
32   return OkStatus();
33 }
34 
35 // Checks if the plugin and core ABI numbers match.
36 //
37 // If the numbers don't match, plugin cannot be loaded.
CheckABI(int pluginABI,int coreABI,StringPiece where)38 static Status CheckABI(int pluginABI, int coreABI, StringPiece where) {
39   if (pluginABI != coreABI)
40     return errors::FailedPrecondition(
41         strings::StrCat("Plugin ABI (", pluginABI, ") for ", where,
42                         " operations doesn't match expected core ABI (",
43                         coreABI, "). Plugin cannot be loaded."));
44   return OkStatus();
45 }
46 
47 // Checks if the plugin and core ABI numbers match, for all operations.
48 //
49 // If the numbers don't match, plugin cannot be loaded.
50 //
51 // Uses the simpler `CheckABI(int, int, StringPiece)`.
ValidateABI(const TF_FilesystemPluginOps * ops)52 static Status ValidateABI(const TF_FilesystemPluginOps* ops) {
53   TF_RETURN_IF_ERROR(
54       CheckABI(ops->filesystem_ops_abi, TF_FILESYSTEM_OPS_ABI, "filesystem"));
55 
56   if (ops->random_access_file_ops != nullptr)
57     TF_RETURN_IF_ERROR(CheckABI(ops->random_access_file_ops_abi,
58                                 TF_RANDOM_ACCESS_FILE_OPS_ABI,
59                                 "random access file"));
60 
61   if (ops->writable_file_ops != nullptr)
62     TF_RETURN_IF_ERROR(CheckABI(ops->writable_file_ops_abi,
63                                 TF_WRITABLE_FILE_OPS_ABI, "writable file"));
64 
65   if (ops->read_only_memory_region_ops != nullptr)
66     TF_RETURN_IF_ERROR(CheckABI(ops->read_only_memory_region_ops_abi,
67                                 TF_READ_ONLY_MEMORY_REGION_OPS_ABI,
68                                 "read only memory region"));
69 
70   return OkStatus();
71 }
72 
73 // Checks if the plugin and core API numbers match, logging mismatches.
CheckAPI(int plugin_API,int core_API,StringPiece where)74 static void CheckAPI(int plugin_API, int core_API, StringPiece where) {
75   if (plugin_API != core_API) {
76     VLOG(0) << "Plugin API (" << plugin_API << ") for " << where
77             << " operations doesn't match expected core API (" << core_API
78             << "). Plugin will be loaded but functionality might be missing.";
79   }
80 }
81 
82 // Checks if the plugin and core API numbers match, for all operations.
83 //
84 // Uses the simpler `CheckAPIHelper(int, int, StringPiece)`.
ValidateAPI(const TF_FilesystemPluginOps * ops)85 static void ValidateAPI(const TF_FilesystemPluginOps* ops) {
86   CheckAPI(ops->filesystem_ops_api, TF_FILESYSTEM_OPS_API, "filesystem");
87 
88   if (ops->random_access_file_ops != nullptr)
89     CheckAPI(ops->random_access_file_ops_api, TF_RANDOM_ACCESS_FILE_OPS_API,
90              "random access file");
91 
92   if (ops->writable_file_ops != nullptr)
93     CheckAPI(ops->writable_file_ops_api, TF_WRITABLE_FILE_OPS_API,
94              "writable file");
95 
96   if (ops->read_only_memory_region_ops != nullptr)
97     CheckAPI(ops->read_only_memory_region_ops_api,
98              TF_READ_ONLY_MEMORY_REGION_OPS_API, "read only memory region");
99 }
100 
101 // Validates the filesystem operations supplied by the plugin.
ValidateHelper(const TF_FilesystemOps * ops)102 static Status ValidateHelper(const TF_FilesystemOps* ops) {
103   if (ops == nullptr)
104     return errors::FailedPrecondition(
105         "Trying to register filesystem without operations");
106 
107   if (ops->init == nullptr)
108     return errors::FailedPrecondition(
109         "Trying to register filesystem without `init` operation");
110 
111   if (ops->cleanup == nullptr)
112     return errors::FailedPrecondition(
113         "Trying to register filesystem without `cleanup` operation");
114 
115   return OkStatus();
116 }
117 
118 // Validates the random access file operations supplied by the plugin.
ValidateHelper(const TF_RandomAccessFileOps * ops)119 static Status ValidateHelper(const TF_RandomAccessFileOps* ops) {
120   if (ops == nullptr) {
121     // We allow filesystems where files can only be written to (from TF code)
122     return OkStatus();
123   }
124 
125   if (ops->cleanup == nullptr)
126     return errors::FailedPrecondition(
127         "Trying to register filesystem without `cleanup` operation on random "
128         "access files");
129 
130   return OkStatus();
131 }
132 
133 // Validates the writable file operations supplied by the plugin.
ValidateHelper(const TF_WritableFileOps * ops)134 static Status ValidateHelper(const TF_WritableFileOps* ops) {
135   if (ops == nullptr) {
136     // We allow read-only filesystems
137     return OkStatus();
138   }
139 
140   if (ops->cleanup == nullptr)
141     return errors::FailedPrecondition(
142         "Trying to register filesystem without `cleanup` operation on writable "
143         "files");
144 
145   return OkStatus();
146 }
147 
148 // Validates the read only memory region operations given by the plugin.
ValidateHelper(const TF_ReadOnlyMemoryRegionOps * ops)149 static Status ValidateHelper(const TF_ReadOnlyMemoryRegionOps* ops) {
150   if (ops == nullptr) {
151     // read only memory region support is always optional
152     return OkStatus();
153   }
154 
155   if (ops->cleanup == nullptr)
156     return errors::FailedPrecondition(
157         "Trying to register filesystem without `cleanup` operation on read "
158         "only memory regions");
159 
160   if (ops->data == nullptr)
161     return errors::FailedPrecondition(
162         "Trying to register filesystem without `data` operation on read only "
163         "memory regions");
164 
165   if (ops->length == nullptr)
166     return errors::FailedPrecondition(
167         "Trying to register filesystem without `length` operation on read only "
168         "memory regions");
169 
170   return OkStatus();
171 }
172 
173 // Validates the operations supplied by the plugin.
174 //
175 // Uses the 4 simpler `ValidateHelper(const TF_...*)` to validate each
176 // individual function table and then checks that the function table for a
177 // specific file type exists if the plugin offers support for creating that
178 // type of files.
ValidateOperations(const TF_FilesystemPluginOps * ops)179 static Status ValidateOperations(const TF_FilesystemPluginOps* ops) {
180   TF_RETURN_IF_ERROR(ValidateHelper(ops->filesystem_ops));
181   TF_RETURN_IF_ERROR(ValidateHelper(ops->random_access_file_ops));
182   TF_RETURN_IF_ERROR(ValidateHelper(ops->writable_file_ops));
183   TF_RETURN_IF_ERROR(ValidateHelper(ops->read_only_memory_region_ops));
184 
185   if (ops->filesystem_ops->new_random_access_file != nullptr &&
186       ops->random_access_file_ops == nullptr)
187     return errors::FailedPrecondition(
188         "Filesystem allows creation of random access files but no "
189         "operations on them have been supplied.");
190 
191   if ((ops->filesystem_ops->new_writable_file != nullptr ||
192        ops->filesystem_ops->new_appendable_file != nullptr) &&
193       ops->writable_file_ops == nullptr)
194     return errors::FailedPrecondition(
195         "Filesystem allows creation of writable files but no "
196         "operations on them have been supplied.");
197 
198   if (ops->filesystem_ops->new_read_only_memory_region_from_file != nullptr &&
199       ops->read_only_memory_region_ops == nullptr)
200     return errors::FailedPrecondition(
201         "Filesystem allows creation of readonly memory regions but no "
202         "operations on them have been supplied.");
203 
204   return OkStatus();
205 }
206 
207 // Copies a function table from plugin memory space to core memory space.
208 //
209 // This has three benefits:
210 //   * allows having newer plugins than the current core TensorFlow: the
211 //     additional entries in the plugin's table are just discarded;
212 //   * allows having older plugins than the current core TensorFlow (though
213 //     we are still warning users): the entries that core TensorFlow expects
214 //     but plugins didn't provide will be set to `nullptr` values and core
215 //     TensorFlow will know to not call these on behalf of users;
216 //   * increased security as plugins will not be able to alter function table
217 //     after loading up. Thus, malicious plugins can't alter functionality to
218 //     probe for gadgets inside core TensorFlow. We can even protect the area
219 //     of memory where the copies reside to not allow any more writes to it
220 //     after all copies are created.
221 template <typename T>
CopyToCore(const T * plugin_ops,size_t plugin_size)222 static std::unique_ptr<const T> CopyToCore(const T* plugin_ops,
223                                            size_t plugin_size) {
224   if (plugin_ops == nullptr) return nullptr;
225 
226   size_t copy_size = std::min(plugin_size, sizeof(T));
227   auto core_ops = tensorflow::MakeUnique<T>();
228   memset(core_ops.get(), 0, sizeof(T));
229   memcpy(core_ops.get(), plugin_ops, copy_size);
230   return core_ops;
231 }
232 
233 // Registers one filesystem from the plugin.
234 //
235 // Must be called only with `index` a valid index in `info->ops`.
RegisterFileSystem(const TF_FilesystemPluginInfo * info,int index)236 static Status RegisterFileSystem(const TF_FilesystemPluginInfo* info,
237                                  int index) {
238   // Step 1: Copy all the function tables to core TensorFlow memory space
239   auto core_filesystem_ops = CopyToCore<TF_FilesystemOps>(
240       info->ops[index].filesystem_ops, info->ops[index].filesystem_ops_size);
241   auto core_random_access_file_ops = CopyToCore<TF_RandomAccessFileOps>(
242       info->ops[index].random_access_file_ops,
243       info->ops[index].random_access_file_ops_size);
244   auto core_writable_file_ops =
245       CopyToCore<TF_WritableFileOps>(info->ops[index].writable_file_ops,
246                                      info->ops[index].writable_file_ops_size);
247   auto core_read_only_memory_region_ops =
248       CopyToCore<TF_ReadOnlyMemoryRegionOps>(
249           info->ops[index].read_only_memory_region_ops,
250           info->ops[index].read_only_memory_region_ops_size);
251 
252   // Step 2: Initialize the opaque filesystem structure
253   auto filesystem = tensorflow::MakeUnique<TF_Filesystem>();
254   TF_Status* c_status = TF_NewStatus();
255   Status status = OkStatus();
256   core_filesystem_ops->init(filesystem.get(), c_status);
257   status = Status(c_status->status);
258   TF_DeleteStatus(c_status);
259   if (!status.ok()) return status;
260 
261   // Step 3: Actual registration
262   return Env::Default()->RegisterFileSystem(
263       info->ops[index].scheme,
264       tensorflow::MakeUnique<tensorflow::ModularFileSystem>(
265           std::move(filesystem), std::move(core_filesystem_ops),
266           std::move(core_random_access_file_ops),
267           std::move(core_writable_file_ops),
268           std::move(core_read_only_memory_region_ops),
269           info->plugin_memory_allocate, info->plugin_memory_free));
270 }
271 
272 // Registers filesystem at `index`, if plugin is providing valid information.
273 //
274 // Extracted to a separate function so that pointers inside `info` are freed
275 // by the caller regardless of whether validation/registration failed or not.
276 //
277 // Must be called only with `index` a valid index in `info->ops`.
ValidateAndRegisterFilesystems(const TF_FilesystemPluginInfo * info,int index)278 static Status ValidateAndRegisterFilesystems(
279     const TF_FilesystemPluginInfo* info, int index) {
280   TF_RETURN_IF_ERROR(ValidateScheme(info->ops[index].scheme));
281   TF_RETURN_IF_ERROR(ValidateABI(&info->ops[index]));
282   ValidateAPI(&info->ops[index]);  // we just warn on API number mismatch
283   TF_RETURN_IF_ERROR(ValidateOperations(&info->ops[index]));
284   TF_RETURN_IF_ERROR(RegisterFileSystem(info, index));
285   return OkStatus();
286 }
287 
288 // Ensures that the plugin provides the required memory management operations.
ValidatePluginMemoryRoutines(const TF_FilesystemPluginInfo * info)289 static Status ValidatePluginMemoryRoutines(
290     const TF_FilesystemPluginInfo* info) {
291   if (info->plugin_memory_allocate == nullptr)
292     return errors::FailedPrecondition(
293         "Cannot load filesystem plugin which does not provide "
294         "`plugin_memory_allocate`");
295 
296   if (info->plugin_memory_free == nullptr)
297     return errors::FailedPrecondition(
298         "Cannot load filesystem plugin which does not provide "
299         "`plugin_memory_free`");
300 
301   return OkStatus();
302 }
303 
304 namespace filesystem_registration {
305 
RegisterFilesystemPluginImpl(const TF_FilesystemPluginInfo * info)306 Status RegisterFilesystemPluginImpl(const TF_FilesystemPluginInfo* info) {
307   TF_RETURN_IF_ERROR(ValidatePluginMemoryRoutines(info));
308 
309   // Validate and register all filesystems
310   // Try to register as many filesystems as possible.
311   // Free memory once we no longer need it
312   Status status;
313   for (int i = 0; i < info->num_schemes; i++) {
314     status.Update(ValidateAndRegisterFilesystems(info, i));
315     info->plugin_memory_free(info->ops[i].scheme);
316     info->plugin_memory_free(info->ops[i].filesystem_ops);
317     info->plugin_memory_free(info->ops[i].random_access_file_ops);
318     info->plugin_memory_free(info->ops[i].writable_file_ops);
319     info->plugin_memory_free(info->ops[i].read_only_memory_region_ops);
320   }
321   info->plugin_memory_free(info->ops);
322   return status;
323 }
324 
325 }  // namespace filesystem_registration
326 
327 }  // namespace tensorflow
328