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