xref: /aosp_15_r20/external/sandboxed-api/oss-internship-2020/libarchive/examples/sapi_minitar.cc (revision ec63e07ab9515d95e79c211197c445ef84cefa6a)
1 // Copyright 2020 Google LLC
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 //     https://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 "sapi_minitar.h"  // NOLINT(build/include)
16 
17 #include "absl/status/status.h"
18 #include "sandboxed_api/util/path.h"
19 #include "sandboxed_api/util/status_macros.h"
20 
CreateArchive(const char * initial_filename,int compress,const std::vector<std::string> & argv,bool verbose)21 absl::Status CreateArchive(const char* initial_filename, int compress,
22                            const std::vector<std::string>& argv, bool verbose) {
23   // We split the filename path into dirname and filename. To the filename we
24   // prepend "/output/"" so that it will work with the security policy.
25   std::string abs_path = MakeAbsolutePathAtCWD(std::string(initial_filename));
26   auto [archive_path, filename_tmp] =
27       std::move(sandbox2::file::SplitPath(abs_path));
28 
29   std::string filename = sandbox2::file::JoinPath("/output/", filename_tmp);
30 
31   std::vector<std::string> absolute_paths = argv;
32 
33   std::vector<std::string> relative_paths = absolute_paths;
34 
35   std::transform(absolute_paths.begin(), absolute_paths.end(),
36                  absolute_paths.begin(), MakeAbsolutePathAtCWD);
37 
38   std::transform(relative_paths.begin(), relative_paths.end(),
39                  relative_paths.begin(), sandbox2::file::CleanPath);
40   // At this point, we have the relative and absolute paths (cleaned) saved
41   // in vectors.
42 
43   // Initialize sandbox and api objects.
44   SapiLibarchiveSandboxCreate sandbox(absolute_paths, archive_path);
45   SAPI_RETURN_IF_ERROR(sandbox.Init());
46   LibarchiveApi api(&sandbox);
47 
48   SAPI_ASSIGN_OR_RETURN(archive * ret_archive, api.archive_write_new());
49   if (ret_archive == nullptr) {
50     return absl::FailedPreconditionError("Failed to create write archive");
51   }
52 
53   // Treat the pointer as remote. There is no need to copy the data
54   // to the client process.
55   sapi::v::RemotePtr a(ret_archive);
56 
57   int rc;
58   std::string msg;
59 
60   //   switch (compress) {
61   //     case 'j':
62   //     case 'y':
63   if (compress == 'j' || compress == 'y') {
64     SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_add_filter_bzip2(&a));
65     if (rc != ARCHIVE_OK) {
66       return absl::FailedPreconditionError(
67           "Unexpected result from write_add_filter_bzip2 call");
68     }
69     //   break;
70   } else if (compress == 'Z') {
71     // case 'Z':
72     SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_add_filter_compress(&a));
73     if (rc != ARCHIVE_OK) {
74       return absl::FailedPreconditionError(
75           "Unexpected result from write_add_filter_compress call");
76     }
77     //   break;
78   } else if (compress == 'z') {
79     // case 'z':
80     SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_add_filter_gzip(&a));
81     if (rc != ARCHIVE_OK) {
82       return absl::FailedPreconditionError(
83           "Unexpected result from write_add_filter_gzip call");
84     }
85     //   break;
86   } else {
87     // default:
88     SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_add_filter_none(&a));
89     if (rc != ARCHIVE_OK) {
90       return absl::FailedPreconditionError(
91           "Unexpected result from write_add_filter_none call");
92     }
93     //   break;
94   }
95 
96   SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_set_format_ustar(&a));
97   if (rc != ARCHIVE_OK) {
98     return absl::FailedPreconditionError(
99         "Unexpected result from write_set_format_ustar call");
100   }
101 
102   const char* filename_ptr = filename.data();
103   if (filename_ptr != nullptr && strcmp(filename_ptr, "-") == 0) {
104     filename_ptr = nullptr;
105   }
106 
107   SAPI_ASSIGN_OR_RETURN(rc,
108                         api.archive_write_open_filename(
109                             &a, sapi::v::ConstCStr(filename_ptr).PtrBefore()));
110   if (rc != ARCHIVE_OK) {
111     return absl::FailedPreconditionError(
112         "Unexpected result from write_open_filename call");
113   }
114 
115   int file_idx = 0;
116 
117   // We can directly use the vectors defined before.
118   for (int file_idx = 0; file_idx < absolute_paths.size(); ++file_idx) {
119     SAPI_ASSIGN_OR_RETURN(ret_archive, api.archive_read_disk_new());
120     if (ret_archive == nullptr) {
121       return absl::FailedPreconditionError(
122           "Failed to create read_disk archive");
123     }
124 
125     sapi::v::RemotePtr disk(ret_archive);
126 
127     SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_disk_set_standard_lookup(&disk));
128     if (rc != ARCHIVE_OK) {
129       return absl::FailedPreconditionError(
130           "Unexpected result from read_disk_set_standard_lookup call");
131     }
132 
133     // We use the absolute path first.
134     SAPI_ASSIGN_OR_RETURN(
135         rc,
136         api.archive_read_disk_open(
137             &disk,
138             sapi::v::ConstCStr(absolute_paths[file_idx].c_str()).PtrBefore()));
139     if (rc != ARCHIVE_OK) {
140       SAPI_ASSIGN_OR_RETURN(msg, CheckStatusAndGetString(
141                                      api.archive_error_string(&disk), sandbox));
142       return absl::FailedPreconditionError(msg);
143     }
144 
145     while (true) {
146       archive_entry* ret_archive_entry;
147       SAPI_ASSIGN_OR_RETURN(ret_archive_entry, api.archive_entry_new());
148 
149       if (ret_archive_entry == nullptr) {
150         return absl::FailedPreconditionError("Failed to create archive_entry");
151       }
152 
153       sapi::v::RemotePtr entry(ret_archive_entry);
154 
155       SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_next_header2(&disk, &entry));
156 
157       if (rc == ARCHIVE_EOF) {
158         break;
159       }
160 
161       if (rc != ARCHIVE_OK) {
162         SAPI_ASSIGN_OR_RETURN(
163             msg,
164             CheckStatusAndGetString(api.archive_error_string(&disk), sandbox));
165         return absl::FailedPreconditionError(msg);
166       }
167 
168       SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_disk_descend(&disk));
169       if (rc != ARCHIVE_OK) {
170         return absl::FailedPreconditionError("read_disk_descend call failed");
171       }
172 
173       // After using the absolute path before, we now need to add the pathname
174       // to the archive entry. This would help store the files by their relative
175       // paths(similar to the usual tar command).
176       // However, in the case where a directory is added to the archive,
177       // all of the files inside of it are added as well so we replace the
178       // absolute path prefix with the relative one.
179       // Example:
180       // we add the folder "test_files" which becomes
181       // "/absolute/path/test_files" and the files inside of it will become
182       // similar to "/absolute/path/test_files/file1"
183       // which we then change to "test_files/file1" so that it is relative.
184       SAPI_ASSIGN_OR_RETURN(
185           std::string path_name,
186           CheckStatusAndGetString(api.archive_entry_pathname(&entry), sandbox));
187 
188       path_name.replace(path_name.begin(),
189                         path_name.begin() + absolute_paths[file_idx].length(),
190                         relative_paths[file_idx]);
191 
192       // On top of those changes, we need to remove leading '/' characters
193       // and also remove everything up to the last occurrence of '../'.
194       size_t found = path_name.find_first_not_of("/");
195       if (found != std::string::npos) {
196         path_name.erase(path_name.begin(), path_name.begin() + found);
197       }
198 
199       // Search either for the last '/../' or check if
200       // the path has '../' in the beginning.
201       found = path_name.rfind("/../");
202       if (found != std::string::npos) {
203         path_name = path_name.substr(found + 4);
204       } else if (path_name.substr(0, 3) == "../") {
205         path_name = path_name.substr(3);
206       }
207 
208       SAPI_RETURN_IF_ERROR(api.archive_entry_set_pathname(
209           &entry, sapi::v::ConstCStr(path_name.c_str()).PtrBefore()));
210 
211       if (verbose) {
212         SAPI_ASSIGN_OR_RETURN(
213             msg, CheckStatusAndGetString(api.archive_entry_pathname(&entry),
214                                          sandbox));
215         std::cout << msg << std::endl;
216       }
217 
218       SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_header(&a, &entry));
219 
220       if (rc < ARCHIVE_OK) {
221         SAPI_ASSIGN_OR_RETURN(msg, CheckStatusAndGetString(
222                                        api.archive_error_string(&a), sandbox));
223         std::cout << msg << std::endl;
224       }
225       if (rc == ARCHIVE_FATAL) {
226         return absl::FailedPreconditionError(
227             "Unexpected result from write_header call");
228       }
229 
230       // In the following section, the calls (read, archive_write_data) are done
231       // on the sandboxed process since we do not need to transfer the data in
232       // the client process.
233       if (rc > ARCHIVE_FAILED) {
234         SAPI_ASSIGN_OR_RETURN(
235             msg, CheckStatusAndGetString(api.archive_entry_sourcepath(&entry),
236                                          sandbox));
237         int fd = open(msg.c_str(), O_RDONLY);
238         if (fd < 0) {
239           return absl::FailedPreconditionError("Could not open file");
240         }
241 
242         sapi::v::Fd sapi_fd(fd);
243         sapi::v::Int read_ret;
244         sapi::v::Array<char> buff(kBuffSize);
245         sapi::v::UInt ssize(kBuffSize);
246 
247         // We allocate the buffer remotely and then we can simply use the
248         // remote pointer(with PtrNone).
249         // This allows us to keep the data in the remote process without always
250         // transferring the memory.
251         SAPI_RETURN_IF_ERROR(sandbox.Allocate(&buff, true));
252 
253         // We can use sapi methods that help us with file descriptors.
254         SAPI_RETURN_IF_ERROR(sandbox.TransferToSandboxee(&sapi_fd));
255 
256         SAPI_RETURN_IF_ERROR(
257             sandbox.Call("read", &read_ret, &sapi_fd, buff.PtrNone(), &ssize));
258 
259         while (read_ret.GetValue() > 0) {
260           SAPI_ASSIGN_OR_RETURN(
261               rc,
262               api.archive_write_data(&a, buff.PtrNone(), read_ret.GetValue()));
263 
264           SAPI_RETURN_IF_ERROR(sandbox.Call("read", &read_ret, &sapi_fd,
265                                             buff.PtrNone(), &ssize));
266         }
267         // sapi_fd variable goes out of scope here so both the local and the
268         // remote file descriptors are closed.
269       }
270       SAPI_RETURN_IF_ERROR(api.archive_entry_free(&entry));
271     }
272 
273     SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_close(&disk));
274     if (rc != ARCHIVE_OK) {
275       return absl::FailedPreconditionError(
276           "Unexpected result from read_close call");
277     }
278 
279     SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_free(&disk));
280     if (rc != ARCHIVE_OK) {
281       return absl::FailedPreconditionError(
282           "Unexpected result from read_free call");
283     }
284   }
285 
286   SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_close(&a));
287   if (rc != ARCHIVE_OK) {
288     return absl::FailedPreconditionError(
289         "Unexpected result from write_close call");
290   }
291 
292   SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_free(&a));
293   if (rc != ARCHIVE_OK) {
294     return absl::FailedPreconditionError(
295         "Unexpected result from write_free call");
296   }
297 
298   return absl::OkStatus();
299 }
300 
ExtractArchive(const char * filename,int do_extract,int flags,bool verbose)301 absl::Status ExtractArchive(const char* filename, int do_extract, int flags,
302                             bool verbose) {
303   std::string tmp_dir;
304   if (do_extract) {
305     SAPI_ASSIGN_OR_RETURN(tmp_dir, CreateTempDirAtCWD());
306   }
307 
308   // We can use a struct like this in order to delete the temporary
309   // directory that was created earlier whenever the function ends.
310   struct ExtractTempDirectoryCleanup {
311     ExtractTempDirectoryCleanup(const std::string& dir) : dir_(dir) {}
312     ~ExtractTempDirectoryCleanup() {
313       sandbox2::file_util::fileops::DeleteRecursively(dir_);
314     }
315 
316    private:
317     std::string dir_;
318   };
319 
320   // We should only delete it if the do_extract flag is true which
321   // means that this struct is instantiated only in that case.
322   auto cleanup_ptr =
323       do_extract ? std::make_unique<ExtractTempDirectoryCleanup>(tmp_dir)
324                  : nullptr;
325 
326   std::string filename_absolute = MakeAbsolutePathAtCWD(filename);
327 
328   // Initialize sandbox and api objects.
329   SapiLibarchiveSandboxExtract sandbox(filename_absolute, do_extract, tmp_dir);
330   SAPI_RETURN_IF_ERROR(sandbox.Init());
331   LibarchiveApi api(&sandbox);
332 
333   SAPI_ASSIGN_OR_RETURN(archive * ret_archive, api.archive_read_new());
334   if (ret_archive == nullptr) {
335     return absl::FailedPreconditionError("Failed to create read archive");
336   }
337 
338   sapi::v::RemotePtr a(ret_archive);
339 
340   SAPI_ASSIGN_OR_RETURN(ret_archive, api.archive_write_disk_new());
341   if (ret_archive == nullptr) {
342     return absl::FailedPreconditionError("Failed to create write disk archive");
343   }
344 
345   sapi::v::RemotePtr ext(ret_archive);
346 
347   int rc;
348   std::string msg;
349 
350   SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_disk_set_options(&ext, flags));
351   if (rc != ARCHIVE_OK) {
352     return absl::FailedPreconditionError(
353         "Unexpected result from write_disk_set_options call");
354   }
355 
356   SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_filter_bzip2(&a));
357   if (rc != ARCHIVE_OK) {
358     return absl::FailedPreconditionError(
359         "Unexpected result from read_support_filter_bzip2 call");
360   }
361 
362   SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_filter_gzip(&a));
363   if (rc != ARCHIVE_OK) {
364     return absl::FailedPreconditionError(
365         "Unexpected result from read_suppport_filter_gzip call");
366   }
367 
368   SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_filter_compress(&a));
369   if (rc != ARCHIVE_OK) {
370     return absl::FailedPreconditionError(
371         "Unexpected result from read_support_filter_compress call");
372   }
373 
374   SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_format_tar(&a));
375   if (rc != ARCHIVE_OK) {
376     return absl::FailedPreconditionError(
377         "Unexpected result fromread_support_format_tar call");
378   }
379 
380   SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_format_cpio(&a));
381   if (rc != ARCHIVE_OK) {
382     return absl::FailedPreconditionError(
383         "Unexpected result from read_support_format_tar call");
384   }
385 
386   SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_disk_set_standard_lookup(&ext));
387   if (rc != ARCHIVE_OK) {
388     return absl::FailedPreconditionError(
389         "Unexpected result from write_disk_set_standard_lookup call");
390   }
391 
392   const char* filename_ptr = filename_absolute.c_str();
393   if (filename_ptr != nullptr && strcmp(filename_ptr, "-") == 0) {
394     filename_ptr = nullptr;
395   }
396 
397   // The entries are saved with a relative path so they are all created
398   // relative to the current working directory.
399   SAPI_ASSIGN_OR_RETURN(
400       rc, api.archive_read_open_filename(
401               &a, sapi::v::ConstCStr(filename_ptr).PtrBefore(), kBlockSize));
402   if (rc != ARCHIVE_OK) {
403     SAPI_ASSIGN_OR_RETURN(
404         msg, CheckStatusAndGetString(api.archive_error_string(&a), sandbox));
405     return absl::FailedPreconditionError(msg);
406   }
407 
408   while (true) {
409     sapi::v::IntBase<archive_entry*> entry_ptr_tmp(0);
410 
411     SAPI_ASSIGN_OR_RETURN(
412         rc, api.archive_read_next_header(&a, entry_ptr_tmp.PtrAfter()));
413 
414     if (rc == ARCHIVE_EOF) {
415       break;
416     }
417 
418     if (rc != ARCHIVE_OK) {
419       SAPI_ASSIGN_OR_RETURN(
420           msg, CheckStatusAndGetString(api.archive_error_string(&a), sandbox));
421       return absl::FailedPreconditionError(msg);
422     }
423 
424     sapi::v::RemotePtr entry(entry_ptr_tmp.GetValue());
425 
426     if (verbose && do_extract) {
427       std::cout << "x ";
428     }
429 
430     if (verbose || !do_extract) {
431       SAPI_ASSIGN_OR_RETURN(
432           msg,
433           CheckStatusAndGetString(api.archive_entry_pathname(&entry), sandbox));
434       std::cout << msg << std::endl;
435     }
436 
437     if (do_extract) {
438       SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_header(&ext, &entry));
439 
440       if (rc != ARCHIVE_OK) {
441         SAPI_ASSIGN_OR_RETURN(msg, CheckStatusAndGetString(
442                                        api.archive_error_string(&a), sandbox));
443         std::cout << msg << std::endl;
444       } else {
445         SAPI_ASSIGN_OR_RETURN(rc, CopyData(&a, &ext, api, sandbox));
446         if (rc != ARCHIVE_OK) {
447           return absl::FailedPreconditionError(
448               "Failed to copy data between archive structs.");
449         }
450       }
451     }
452   }
453 
454   SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_close(&a));
455   if (rc != ARCHIVE_OK) {
456     return absl::FailedPreconditionError(
457         "Unexpected value from read_close call");
458   }
459 
460   SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_free(&a));
461   if (rc != ARCHIVE_OK) {
462     return absl::FailedPreconditionError(
463         "Unexpected result from read_free call");
464   }
465 
466   SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_close(&ext));
467   if (rc != ARCHIVE_OK) {
468     return absl::FailedPreconditionError(
469         "Unexpected result from write_close call");
470   }
471 
472   SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_free(&ext));
473   if (rc != ARCHIVE_OK) {
474     return absl::FailedPreconditionError(
475         "Unexpected result from write_free call");
476   }
477 
478   return absl::OkStatus();
479 }
480 
CopyData(sapi::v::RemotePtr * ar,sapi::v::RemotePtr * aw,LibarchiveApi & api,SapiLibarchiveSandboxExtract & sandbox)481 absl::StatusOr<int> CopyData(sapi::v::RemotePtr* ar, sapi::v::RemotePtr* aw,
482                              LibarchiveApi& api,
483                              SapiLibarchiveSandboxExtract& sandbox) {
484   int rc;
485   std::string msg;
486 
487   sapi::v::IntBase<archive_entry*> buff_ptr_tmp(0);
488   sapi::v::ULLong size;
489   sapi::v::SLLong offset;
490 
491   while (true) {
492     SAPI_ASSIGN_OR_RETURN(
493         rc, api.archive_read_data_block(ar, buff_ptr_tmp.PtrAfter(),
494                                         size.PtrAfter(), offset.PtrAfter()));
495 
496     if (rc == ARCHIVE_EOF) {
497       return ARCHIVE_OK;
498     }
499     if (rc != ARCHIVE_OK) {
500       SAPI_ASSIGN_OR_RETURN(
501           msg, CheckStatusAndGetString(api.archive_error_string(ar), sandbox));
502       std::cout << msg << std::endl;
503       return rc;
504     }
505 
506     sapi::v::RemotePtr buff(buff_ptr_tmp.GetValue());
507 
508     SAPI_ASSIGN_OR_RETURN(
509         rc, api.archive_write_data_block(aw, &buff, size.GetValue(),
510                                          offset.GetValue()));
511 
512     if (rc != ARCHIVE_OK) {
513       SAPI_ASSIGN_OR_RETURN(
514           msg, CheckStatusAndGetString(api.archive_error_string(ar), sandbox));
515       std::cout << msg << std::endl;
516       return rc;
517     }
518   }
519 }
520 
MakeAbsolutePathAtCWD(const std::string & path)521 std::string MakeAbsolutePathAtCWD(const std::string& path) {
522   std::string result = sandbox2::file_util::fileops::MakeAbsolute(
523       path, sandbox2::file_util::fileops::GetCWD());
524   CHECK(result != "") << "Could not create absolute path for: " << path;
525   return sandbox2::file::CleanPath(result);
526 }
527 
CheckStatusAndGetString(const absl::StatusOr<char * > & status,LibarchiveSandbox & sandbox)528 absl::StatusOr<std::string> CheckStatusAndGetString(
529     const absl::StatusOr<char*>& status, LibarchiveSandbox& sandbox) {
530   SAPI_ASSIGN_OR_RETURN(char* str, status);
531   if (str == nullptr) {
532     return absl::FailedPreconditionError("Could not get string from archive");
533   }
534   return sandbox.GetCString(sapi::v::RemotePtr(str));
535 }
536 
CreateTempDirAtCWD()537 absl::StatusOr<std::string> CreateTempDirAtCWD() {
538   std::string cwd = sandbox2::file_util::fileops::GetCWD();
539   CHECK(!cwd.empty()) << "Could not get current working directory";
540   cwd.append("/");
541 
542   SAPI_ASSIGN_OR_RETURN(std::string result, sandbox2::CreateTempDir(cwd));
543   return result;
544 }
545