/*
* Copyright 2016 The Android Open Source Project
*
* 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 "ProtoFuzzerUtils.h"
#include <dirent.h>
#include <getopt.h>
#include <algorithm>
#include <sstream>
#include "utils/InterfaceSpecUtil.h"
using std::cerr;
using std::cout;
using std::string;
using std::unordered_map;
using std::vector;
namespace android {
namespace vts {
namespace fuzzer {
static void usage() {
cout
<< "Usage:\n"
"\n"
"./vts_proto_fuzzer <vts flags> -- <libfuzzer flags>\n"
"\n"
"VTS flags (strictly in form --flag=value):\n"
"\n"
"\tvts_binder_mode: if set, fuzzer will open the HAL in binder mode.\n"
"\tvts_exec_size: number of function calls per 1 run of "
"LLVMFuzzerTestOneInput.\n"
"\tvts_spec_dir: \":\"-separated list of directories on the target "
"containing .vts spec files.\n"
"\tvts_target_iface: name of interface targeted for fuzz, e.g. "
"\"INfc\".\n"
"\tvts_seed: optional integral argument used to initalize the random "
"number generator.\n"
"\n"
"libfuzzer flags (strictly in form -flag=value):\n"
"\tUse -help=1 to see libfuzzer flags\n"
"\n";
}
static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"vts_binder_mode", no_argument, 0, 'b'},
{"vts_spec_dir", required_argument, 0, 'd'},
{"vts_exec_size", required_argument, 0, 'e'},
{"vts_seed", required_argument, 0, 's'},
{"vts_target_iface", required_argument, 0, 't'}};
// Removes information from CompSpec not needed by fuzzer.
static void TrimCompSpec(CompSpec *comp_spec) {
if (comp_spec == nullptr) {
cerr << __func__ << ": empty CompSpec." << endl;
return;
}
if (comp_spec->has_interface()) {
auto *iface_spec = comp_spec->mutable_interface();
for (auto i = 0; i < iface_spec->api_size(); ++i) {
iface_spec->mutable_api(i)->clear_callflow();
}
}
}
static vector<CompSpec> ExtractCompSpecs(string arg) {
vector<CompSpec> result{};
string dir_path;
std::istringstream iss(arg);
while (std::getline(iss, dir_path, ':')) {
DIR *dir;
struct dirent *ent;
if (!(dir = opendir(dir_path.c_str()))) {
cerr << "Could not open directory: " << dir_path << endl;
std::abort();
}
while ((ent = readdir(dir))) {
string vts_spec_name{ent->d_name};
if (vts_spec_name.find(".vts") != string::npos) {
string vts_spec_path = dir_path + "/" + vts_spec_name;
CompSpec comp_spec{};
ParseInterfaceSpec(vts_spec_path.c_str(), &comp_spec);
TrimCompSpec(&comp_spec);
result.emplace_back(std::move(comp_spec));
}
}
}
return result;
}
static void ExtractPredefinedTypesFromVar(
const TypeSpec &var_spec,
unordered_map<string, TypeSpec> &predefined_types) {
predefined_types[var_spec.name()] = var_spec;
// Find all nested struct declarations.
for (const auto &sub_var_spec : var_spec.sub_struct()) {
ExtractPredefinedTypesFromVar(sub_var_spec, predefined_types);
}
// Find all nested union declarations.
for (const auto &sub_var_spec : var_spec.sub_union()) {
ExtractPredefinedTypesFromVar(sub_var_spec, predefined_types);
}
}
ProtoFuzzerParams ExtractProtoFuzzerParams(int argc, char **argv) {
ProtoFuzzerParams params;
int opt = 0;
int index = 0;
while ((opt = getopt_long_only(argc, argv, "", long_options, &index)) != -1) {
switch (opt) {
case 'h':
usage();
std::abort();
case 'b':
params.binder_mode_ = true;
break;
case 'd':
params.comp_specs_ = ExtractCompSpecs(optarg);
break;
case 'e':
params.exec_size_ = std::stoul(optarg);
break;
case 's':
params.seed_ = std::stoull(optarg);
break;
case 't':
params.target_iface_ = optarg;
break;
default:
// Ignore. This option will be handled by libfuzzer.
break;
}
}
return params;
}
string ProtoFuzzerParams::DebugString() const {
std::stringstream ss;
ss << "Execution size: " << exec_size_ << endl;
ss << "Target interface: " << target_iface_ << endl;
ss << "Binder mode: " << binder_mode_ << endl;
ss << "Seed: " << seed_ << endl;
ss << "Loaded specs: " << endl;
for (const auto &spec : comp_specs_) {
ss << spec.component_name() << endl;
}
return ss.str();
}
unordered_map<string, TypeSpec> ExtractPredefinedTypes(
const vector<CompSpec> &specs) {
unordered_map<string, TypeSpec> predefined_types;
for (const auto &comp_spec : specs) {
for (const auto &var_spec : comp_spec.attribute()) {
ExtractPredefinedTypesFromVar(var_spec, predefined_types);
}
for (const auto &var_spec : comp_spec.interface().attribute()) {
ExtractPredefinedTypesFromVar(var_spec, predefined_types);
}
}
return predefined_types;
}
bool FromArray(const uint8_t *data, size_t size, ExecSpec *exec_spec) {
// TODO(b/63136690): Use checksum to validate exec_spec more reliably.
return exec_spec->ParseFromArray(data, size) && exec_spec->has_valid() &&
exec_spec->valid();
}
size_t ToArray(uint8_t *data, size_t size, ExecSpec *exec_spec) {
exec_spec->set_valid(true);
size_t exec_size = exec_spec->ByteSize();
exec_spec->SerializeToArray(data, exec_size);
return exec_size;
}
} // namespace fuzzer
} // namespace vts
} // namespace android