// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "sapi_minitar.h" // NOLINT(build/include) #include "absl/status/status.h" #include "sandboxed_api/util/path.h" #include "sandboxed_api/util/status_macros.h" absl::Status CreateArchive(const char* initial_filename, int compress, const std::vector& argv, bool verbose) { // We split the filename path into dirname and filename. To the filename we // prepend "/output/"" so that it will work with the security policy. std::string abs_path = MakeAbsolutePathAtCWD(std::string(initial_filename)); auto [archive_path, filename_tmp] = std::move(sandbox2::file::SplitPath(abs_path)); std::string filename = sandbox2::file::JoinPath("/output/", filename_tmp); std::vector absolute_paths = argv; std::vector relative_paths = absolute_paths; std::transform(absolute_paths.begin(), absolute_paths.end(), absolute_paths.begin(), MakeAbsolutePathAtCWD); std::transform(relative_paths.begin(), relative_paths.end(), relative_paths.begin(), sandbox2::file::CleanPath); // At this point, we have the relative and absolute paths (cleaned) saved // in vectors. // Initialize sandbox and api objects. SapiLibarchiveSandboxCreate sandbox(absolute_paths, archive_path); SAPI_RETURN_IF_ERROR(sandbox.Init()); LibarchiveApi api(&sandbox); SAPI_ASSIGN_OR_RETURN(archive * ret_archive, api.archive_write_new()); if (ret_archive == nullptr) { return absl::FailedPreconditionError("Failed to create write archive"); } // Treat the pointer as remote. There is no need to copy the data // to the client process. sapi::v::RemotePtr a(ret_archive); int rc; std::string msg; // switch (compress) { // case 'j': // case 'y': if (compress == 'j' || compress == 'y') { SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_add_filter_bzip2(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from write_add_filter_bzip2 call"); } // break; } else if (compress == 'Z') { // case 'Z': SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_add_filter_compress(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from write_add_filter_compress call"); } // break; } else if (compress == 'z') { // case 'z': SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_add_filter_gzip(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from write_add_filter_gzip call"); } // break; } else { // default: SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_add_filter_none(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from write_add_filter_none call"); } // break; } SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_set_format_ustar(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from write_set_format_ustar call"); } const char* filename_ptr = filename.data(); if (filename_ptr != nullptr && strcmp(filename_ptr, "-") == 0) { filename_ptr = nullptr; } SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_open_filename( &a, sapi::v::ConstCStr(filename_ptr).PtrBefore())); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from write_open_filename call"); } int file_idx = 0; // We can directly use the vectors defined before. for (int file_idx = 0; file_idx < absolute_paths.size(); ++file_idx) { SAPI_ASSIGN_OR_RETURN(ret_archive, api.archive_read_disk_new()); if (ret_archive == nullptr) { return absl::FailedPreconditionError( "Failed to create read_disk archive"); } sapi::v::RemotePtr disk(ret_archive); SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_disk_set_standard_lookup(&disk)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from read_disk_set_standard_lookup call"); } // We use the absolute path first. SAPI_ASSIGN_OR_RETURN( rc, api.archive_read_disk_open( &disk, sapi::v::ConstCStr(absolute_paths[file_idx].c_str()).PtrBefore())); if (rc != ARCHIVE_OK) { SAPI_ASSIGN_OR_RETURN(msg, CheckStatusAndGetString( api.archive_error_string(&disk), sandbox)); return absl::FailedPreconditionError(msg); } while (true) { archive_entry* ret_archive_entry; SAPI_ASSIGN_OR_RETURN(ret_archive_entry, api.archive_entry_new()); if (ret_archive_entry == nullptr) { return absl::FailedPreconditionError("Failed to create archive_entry"); } sapi::v::RemotePtr entry(ret_archive_entry); SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_next_header2(&disk, &entry)); if (rc == ARCHIVE_EOF) { break; } if (rc != ARCHIVE_OK) { SAPI_ASSIGN_OR_RETURN( msg, CheckStatusAndGetString(api.archive_error_string(&disk), sandbox)); return absl::FailedPreconditionError(msg); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_disk_descend(&disk)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError("read_disk_descend call failed"); } // After using the absolute path before, we now need to add the pathname // to the archive entry. This would help store the files by their relative // paths(similar to the usual tar command). // However, in the case where a directory is added to the archive, // all of the files inside of it are added as well so we replace the // absolute path prefix with the relative one. // Example: // we add the folder "test_files" which becomes // "/absolute/path/test_files" and the files inside of it will become // similar to "/absolute/path/test_files/file1" // which we then change to "test_files/file1" so that it is relative. SAPI_ASSIGN_OR_RETURN( std::string path_name, CheckStatusAndGetString(api.archive_entry_pathname(&entry), sandbox)); path_name.replace(path_name.begin(), path_name.begin() + absolute_paths[file_idx].length(), relative_paths[file_idx]); // On top of those changes, we need to remove leading '/' characters // and also remove everything up to the last occurrence of '../'. size_t found = path_name.find_first_not_of("/"); if (found != std::string::npos) { path_name.erase(path_name.begin(), path_name.begin() + found); } // Search either for the last '/../' or check if // the path has '../' in the beginning. found = path_name.rfind("/../"); if (found != std::string::npos) { path_name = path_name.substr(found + 4); } else if (path_name.substr(0, 3) == "../") { path_name = path_name.substr(3); } SAPI_RETURN_IF_ERROR(api.archive_entry_set_pathname( &entry, sapi::v::ConstCStr(path_name.c_str()).PtrBefore())); if (verbose) { SAPI_ASSIGN_OR_RETURN( msg, CheckStatusAndGetString(api.archive_entry_pathname(&entry), sandbox)); std::cout << msg << std::endl; } SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_header(&a, &entry)); if (rc < ARCHIVE_OK) { SAPI_ASSIGN_OR_RETURN(msg, CheckStatusAndGetString( api.archive_error_string(&a), sandbox)); std::cout << msg << std::endl; } if (rc == ARCHIVE_FATAL) { return absl::FailedPreconditionError( "Unexpected result from write_header call"); } // In the following section, the calls (read, archive_write_data) are done // on the sandboxed process since we do not need to transfer the data in // the client process. if (rc > ARCHIVE_FAILED) { SAPI_ASSIGN_OR_RETURN( msg, CheckStatusAndGetString(api.archive_entry_sourcepath(&entry), sandbox)); int fd = open(msg.c_str(), O_RDONLY); if (fd < 0) { return absl::FailedPreconditionError("Could not open file"); } sapi::v::Fd sapi_fd(fd); sapi::v::Int read_ret; sapi::v::Array buff(kBuffSize); sapi::v::UInt ssize(kBuffSize); // We allocate the buffer remotely and then we can simply use the // remote pointer(with PtrNone). // This allows us to keep the data in the remote process without always // transferring the memory. SAPI_RETURN_IF_ERROR(sandbox.Allocate(&buff, true)); // We can use sapi methods that help us with file descriptors. SAPI_RETURN_IF_ERROR(sandbox.TransferToSandboxee(&sapi_fd)); SAPI_RETURN_IF_ERROR( sandbox.Call("read", &read_ret, &sapi_fd, buff.PtrNone(), &ssize)); while (read_ret.GetValue() > 0) { SAPI_ASSIGN_OR_RETURN( rc, api.archive_write_data(&a, buff.PtrNone(), read_ret.GetValue())); SAPI_RETURN_IF_ERROR(sandbox.Call("read", &read_ret, &sapi_fd, buff.PtrNone(), &ssize)); } // sapi_fd variable goes out of scope here so both the local and the // remote file descriptors are closed. } SAPI_RETURN_IF_ERROR(api.archive_entry_free(&entry)); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_close(&disk)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from read_close call"); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_free(&disk)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from read_free call"); } } SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_close(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from write_close call"); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_free(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from write_free call"); } return absl::OkStatus(); } absl::Status ExtractArchive(const char* filename, int do_extract, int flags, bool verbose) { std::string tmp_dir; if (do_extract) { SAPI_ASSIGN_OR_RETURN(tmp_dir, CreateTempDirAtCWD()); } // We can use a struct like this in order to delete the temporary // directory that was created earlier whenever the function ends. struct ExtractTempDirectoryCleanup { ExtractTempDirectoryCleanup(const std::string& dir) : dir_(dir) {} ~ExtractTempDirectoryCleanup() { sandbox2::file_util::fileops::DeleteRecursively(dir_); } private: std::string dir_; }; // We should only delete it if the do_extract flag is true which // means that this struct is instantiated only in that case. auto cleanup_ptr = do_extract ? std::make_unique(tmp_dir) : nullptr; std::string filename_absolute = MakeAbsolutePathAtCWD(filename); // Initialize sandbox and api objects. SapiLibarchiveSandboxExtract sandbox(filename_absolute, do_extract, tmp_dir); SAPI_RETURN_IF_ERROR(sandbox.Init()); LibarchiveApi api(&sandbox); SAPI_ASSIGN_OR_RETURN(archive * ret_archive, api.archive_read_new()); if (ret_archive == nullptr) { return absl::FailedPreconditionError("Failed to create read archive"); } sapi::v::RemotePtr a(ret_archive); SAPI_ASSIGN_OR_RETURN(ret_archive, api.archive_write_disk_new()); if (ret_archive == nullptr) { return absl::FailedPreconditionError("Failed to create write disk archive"); } sapi::v::RemotePtr ext(ret_archive); int rc; std::string msg; SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_disk_set_options(&ext, flags)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from write_disk_set_options call"); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_filter_bzip2(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from read_support_filter_bzip2 call"); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_filter_gzip(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from read_suppport_filter_gzip call"); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_filter_compress(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from read_support_filter_compress call"); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_format_tar(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result fromread_support_format_tar call"); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_format_cpio(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from read_support_format_tar call"); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_disk_set_standard_lookup(&ext)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from write_disk_set_standard_lookup call"); } const char* filename_ptr = filename_absolute.c_str(); if (filename_ptr != nullptr && strcmp(filename_ptr, "-") == 0) { filename_ptr = nullptr; } // The entries are saved with a relative path so they are all created // relative to the current working directory. SAPI_ASSIGN_OR_RETURN( rc, api.archive_read_open_filename( &a, sapi::v::ConstCStr(filename_ptr).PtrBefore(), kBlockSize)); if (rc != ARCHIVE_OK) { SAPI_ASSIGN_OR_RETURN( msg, CheckStatusAndGetString(api.archive_error_string(&a), sandbox)); return absl::FailedPreconditionError(msg); } while (true) { sapi::v::IntBase entry_ptr_tmp(0); SAPI_ASSIGN_OR_RETURN( rc, api.archive_read_next_header(&a, entry_ptr_tmp.PtrAfter())); if (rc == ARCHIVE_EOF) { break; } if (rc != ARCHIVE_OK) { SAPI_ASSIGN_OR_RETURN( msg, CheckStatusAndGetString(api.archive_error_string(&a), sandbox)); return absl::FailedPreconditionError(msg); } sapi::v::RemotePtr entry(entry_ptr_tmp.GetValue()); if (verbose && do_extract) { std::cout << "x "; } if (verbose || !do_extract) { SAPI_ASSIGN_OR_RETURN( msg, CheckStatusAndGetString(api.archive_entry_pathname(&entry), sandbox)); std::cout << msg << std::endl; } if (do_extract) { SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_header(&ext, &entry)); if (rc != ARCHIVE_OK) { SAPI_ASSIGN_OR_RETURN(msg, CheckStatusAndGetString( api.archive_error_string(&a), sandbox)); std::cout << msg << std::endl; } else { SAPI_ASSIGN_OR_RETURN(rc, CopyData(&a, &ext, api, sandbox)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Failed to copy data between archive structs."); } } } } SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_close(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected value from read_close call"); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_free(&a)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from read_free call"); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_close(&ext)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from write_close call"); } SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_free(&ext)); if (rc != ARCHIVE_OK) { return absl::FailedPreconditionError( "Unexpected result from write_free call"); } return absl::OkStatus(); } absl::StatusOr CopyData(sapi::v::RemotePtr* ar, sapi::v::RemotePtr* aw, LibarchiveApi& api, SapiLibarchiveSandboxExtract& sandbox) { int rc; std::string msg; sapi::v::IntBase buff_ptr_tmp(0); sapi::v::ULLong size; sapi::v::SLLong offset; while (true) { SAPI_ASSIGN_OR_RETURN( rc, api.archive_read_data_block(ar, buff_ptr_tmp.PtrAfter(), size.PtrAfter(), offset.PtrAfter())); if (rc == ARCHIVE_EOF) { return ARCHIVE_OK; } if (rc != ARCHIVE_OK) { SAPI_ASSIGN_OR_RETURN( msg, CheckStatusAndGetString(api.archive_error_string(ar), sandbox)); std::cout << msg << std::endl; return rc; } sapi::v::RemotePtr buff(buff_ptr_tmp.GetValue()); SAPI_ASSIGN_OR_RETURN( rc, api.archive_write_data_block(aw, &buff, size.GetValue(), offset.GetValue())); if (rc != ARCHIVE_OK) { SAPI_ASSIGN_OR_RETURN( msg, CheckStatusAndGetString(api.archive_error_string(ar), sandbox)); std::cout << msg << std::endl; return rc; } } } std::string MakeAbsolutePathAtCWD(const std::string& path) { std::string result = sandbox2::file_util::fileops::MakeAbsolute( path, sandbox2::file_util::fileops::GetCWD()); CHECK(result != "") << "Could not create absolute path for: " << path; return sandbox2::file::CleanPath(result); } absl::StatusOr CheckStatusAndGetString( const absl::StatusOr& status, LibarchiveSandbox& sandbox) { SAPI_ASSIGN_OR_RETURN(char* str, status); if (str == nullptr) { return absl::FailedPreconditionError("Could not get string from archive"); } return sandbox.GetCString(sapi::v::RemotePtr(str)); } absl::StatusOr CreateTempDirAtCWD() { std::string cwd = sandbox2::file_util::fileops::GetCWD(); CHECK(!cwd.empty()) << "Could not get current working directory"; cwd.append("/"); SAPI_ASSIGN_OR_RETURN(std::string result, sandbox2::CreateTempDir(cwd)); return result; }