sandboxed-api/oss-internship-2020/libarchive/examples/sapi_minitar.cc
Christian Blichmann dbaf95c724 Move utility code into sandboxed_api/util
This change should make it less confusing where utility code comes from.
Having it in two places made sense when we were debating whether to publish
Sandbox2 separately, but not any longer.

Follow-up changes will move `sandbox2/util.h` and rename the remaining
`sandbox2/util` folder.

PiperOrigin-RevId: 351601640
Change-Id: I6256845261f610e590c25e2c59851cc51da2d778
2021-01-13 09:25:52 -08:00

541 lines
18 KiB
C++

// 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
//
// http://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 char** 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<std::string> absolute_paths;
sandbox2::util::CharPtrArrToVecString(const_cast<char* const*>(argv),
&absolute_paths);
std::vector<std::string> 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<char> 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 ? absl::make_unique<ExtractTempDirectoryCleanup>(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<archive_entry*> 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<int> CopyData(sapi::v::RemotePtr* ar, sapi::v::RemotePtr* aw,
LibarchiveApi& api,
SapiLibarchiveSandboxExtract& sandbox) {
int rc;
std::string msg;
sapi::v::IntBase<archive_entry*> 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<std::string> CheckStatusAndGetString(
const absl::StatusOr<char*>& 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<std::string> 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;
}