#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <sandbox.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <string>
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/utility/safe_browsing/mac/hfs.h"
#include "chrome/utility/safe_browsing/mac/read_stream.h"
#include "chrome/utility/safe_browsing/mac/udif.h"
#include "sandbox/mac/seatbelt.h"
namespace {
class SafeDMG {
public:
SafeDMG();
SafeDMG(const SafeDMG&) = delete;
SafeDMG& operator=(const SafeDMG&) = delete;
~SafeDMG();
int Main(base::span<const char*> args);
private:
bool PrepareUnpack(const char* unpack_path);
bool EnableSandbox();
bool ParseDMG();
base::File dmg_file_;
base::ScopedFD unpack_dir_;
};
SafeDMG::SafeDMG() : dmg_file_(), unpack_dir_() {}
SafeDMG::~SafeDMG() = default;
int SafeDMG::Main(base::span<const char*> args) {
if (args.size() != 2 && args.size() != 3) {
UNSAFE_TODO(
fprintf(stderr, "Usage: %s file.dmg [unpack-directory]\n", args[0]));
UNSAFE_TODO(fprintf(stderr,
"If no unpack-directory is specified, the tool will\n"
"list the contents of the DMG.\n"));
return EXIT_FAILURE;
}
dmg_file_.Initialize(base::FilePath(args[1]),
base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!dmg_file_.IsValid()) {
LOG(ERROR) << "Failed to open " << args[1] << ": "
<< dmg_file_.error_details();
return EXIT_FAILURE;
}
if (args.size() == 3 && !PrepareUnpack(args[2])) {
return EXIT_FAILURE;
}
if (!EnableSandbox())
return EXIT_FAILURE;
if (!ParseDMG())
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
bool SafeDMG::PrepareUnpack(const char* unpack_path) {
struct stat statbuf;
if (stat(unpack_path, &statbuf) != 0) {
if (errno == ENOENT) {
if (mkdir(unpack_path, 0755) != 0) {
PLOG(ERROR) << "mkdir " << unpack_path;
return false;
}
} else {
PLOG(ERROR) << "stat " << unpack_path;
return false;
}
} else if (!S_ISDIR(statbuf.st_mode)) {
LOG(ERROR) << unpack_path << " is not a directory";
return false;
}
unpack_dir_.reset(HANDLE_EINTR(open(unpack_path, O_DIRECTORY)));
if (!unpack_dir_.is_valid()) {
PLOG(ERROR) << "open " << unpack_path;
return false;
}
return true;
}
bool SafeDMG::EnableSandbox() {
std::string sbox_profile("(version 1) (deny default)");
if (unpack_dir_.is_valid()) {
char unpack_path[PATH_MAX];
if (fcntl(unpack_dir_.get(), F_GETPATH, unpack_path) != 0) {
PLOG(ERROR) << "fcntl unpack_dir_";
return false;
}
if (UNSAFE_TODO(strchr(unpack_path, '"')) != 0 ||
UNSAFE_TODO(strchr(unpack_path, '\\')) != 0) {
LOG(ERROR) << "Unpack directory path can't contain quotes or backslashes";
return false;
}
sbox_profile += base::StringPrintf(
" (allow file-write* (subpath \"%s\"))", unpack_path);
}
std::string sbox_error;
if (!sandbox::Seatbelt::Init(sbox_profile.c_str(), 0, &sbox_error)) {
LOG(ERROR) << "Failed to initialize sandbox: " << sbox_error;
return false;
}
return true;
}
bool SafeDMG::ParseDMG() {
safe_browsing::dmg::FileReadStream read_stream(dmg_file_.GetPlatformFile());
safe_browsing::dmg::UDIFParser udif_parser(&read_stream);
if (!udif_parser.Parse())
return false;
for (size_t i = 0; i < udif_parser.GetNumberOfPartitions(); ++i) {
printf("=== Partition #%zu: %s ===\n", i,
udif_parser.GetPartitionName(i).c_str());
std::unique_ptr<safe_browsing::dmg::ReadStream> partition_stream(
udif_parser.GetPartitionReadStream(i));
safe_browsing::dmg::HFSIterator iterator(partition_stream.get());
if (!iterator.Open()) {
VLOG(1) << "Skipped since this is not an HFS partition";
continue;
}
while (iterator.Next()) {
std::string path = base::UTF16ToUTF8(iterator.GetPath());
printf("%s\n", path.c_str());
if (!unpack_dir_.is_valid())
continue;
if (iterator.IsDecmpfsCompressed()) {
LOG(WARNING) << "Skipping decmpfs-compressed file: "
<< iterator.GetPath();
continue;
}
if (iterator.IsHardLink()) {
LOG(WARNING) << "Skipping hard link: " << path;
continue;
}
if (iterator.IsSymbolicLink()) {
LOG(WARNING) << "Skipping symbolic link: " << path;
continue;
}
if (iterator.IsDirectory()) {
if (mkdirat(unpack_dir_.get(), path.c_str(), 0755) && errno != EEXIST) {
PLOG(ERROR) << "mkdirat " << path;
}
} else {
base::ScopedFD unpacked(
HANDLE_EINTR(openat(unpack_dir_.get(), path.c_str(),
O_WRONLY | O_CREAT | O_TRUNC, 0644)));
if (!unpacked.is_valid()) {
PLOG(ERROR) << "openat " << path;
continue;
}
std::unique_ptr<safe_browsing::dmg::ReadStream> stream(
iterator.GetReadStream());
size_t read_this_pass = 0;
do {
uint8_t buf[4096];
if (!stream->Read(buf, &read_this_pass)) {
LOG(ERROR) << "Failed to read stream: " << path;
unlinkat(unpack_dir_.get(), path.c_str(), 0);
break;
}
int rv = HANDLE_EINTR(write(unpacked.get(), buf, read_this_pass));
if (rv < 0 || static_cast<size_t>(rv) < read_this_pass) {
if (rv < 0)
PLOG(ERROR) << "write " << path;
else
LOG(ERROR) << "Short write: " << path;
unlinkat(unpack_dir_.get(), path.c_str(), 0);
break;
}
} while (read_this_pass != 0);
}
}
}
return true;
}
}
int main(int argc, const char* argv[]) {
SafeDMG safe_dmg;
return safe_dmg.Main(
UNSAFE_BUFFERS(base::span(argv, base::saturated_cast<size_t>(argc))));
}