-
Jason Hiser authoredJason Hiser authored
turbod.cpp 18.00 KiB
#include <iostream>
#include <fstream>
#include <sstream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include <turbo-rpc/turbo.grpc.pb.h>
#include <pqxx/pqxx>
using namespace grpc;
using namespace Turbo;
using namespace std;
#define TURBODB "turbodb"
// Logic and data behind the server's behavior.
class TurboServiceImpl final : public TurboService::Service
{
public:
TurboServiceImpl()
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Creating turbod tables" << endl;
txn.exec(turbod_schema.c_str());
txn.commit();
}
Status Ping(ServerContext *context, const Empty_t *request, Empty_t *reply) override final
{
return Status::OK;
}
Status BoostAdd(ServerContext *context, const BoostAddRequest_t *request, BoostAddReply_t *reply) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Requested creation of new boost named " << request->name() << endl;
conn.prepare("boostcheck", "select boost_id from boosts where boost_name = $1");
const auto check_res = txn.prepared("boostcheck")(request->name()).exec();
if(check_res.size() != 0)
{
return Status(ABORTED, "Boost already exists");
}
conn.prepare("boostadd", "insert into boosts (boost_name, create_time) values ($1, NOW()) returning boost_id ");
const auto add_res = txn.prepared("boostadd")(request->name()).exec();
if(add_res.size() != 1)
{
return Status(ABORTED, "Unexpected sql reply.");
}
reply->set_boost_id(add_res[0]["boost_id"].as<int>());
txn.commit();
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
Status GetZafld (ServerContext *context, const GetZafldRequest_t *request, ZafldBinary_t *reply) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Requested zafld binary for " << request->ver_id() << endl;
conn.prepare("getzafld",
R"""(select
case when zafl_binary is null then '' else zafl_binary end as zafl_binary ,
case when err is null then false else err end as err
from boost_versions where ver_id = $1)"""
);
const auto res_table = txn.prepared("getzafld")(request->ver_id()).exec();
if(res_table.size() != 1)
{
return Status(ABORTED, "Invalid version id");
}
const auto zafl_binary_contents = pqxx::binarystring(res_table[0]["zafl_binary"]).str();
cout << "Zafl version is " << zafl_binary_contents.size() << " bytes" << endl;
reply->set_zafld_bin(zafl_binary_contents);
reply->set_err(res_table[0]["err"].as<bool>() );
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
Status BoostList(ServerContext *context, const Empty_t *request, ServerWriter<BoostInfo_t> *writer) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Requested boost list " << endl;
conn.prepare("boostlist", "select boost_id,encode(boost_name,'escape'), to_char(create_time, 'MM-DD-YYYY HH24:MI:SS') as create_time from boosts");
const auto res_table = txn.prepared("boostlist").exec();
if(res_table.size() == 0)
{
return Status(ABORTED, "No Boosts found");
}
for(const auto &res : res_table)
{
auto reply = BoostInfo_t();
reply.set_id (res["boost_id" ].as<int> ());
reply.set_name (res["encode" ].as<string>());
reply.set_create_time (res["create_time"].as<string>());
writer->Write(reply);
}
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
Status VersionList(ServerContext *context, const VersionListRequest_t *request, ServerWriter<VersionInfo_t> *writer) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Requested version list for boost id " << request->boost_id() << endl;
conn.prepare("boostlist",
R"""(
select
ver_id,
encode(orig_bin_sha,'text') as orig_sha,
encode(zafl_bin_sha,'text') as zafl_sha,
COALESCE(to_char(submit_time, 'MM-DD-YYYY HH24:MI:SS'), '') as submit_time,
COALESCE(to_char(start_time , 'MM-DD-YYYY HH24:MI:SS'), '') as start_time,
COALESCE(to_char(end_time , 'MM-DD-YYYY HH24:MI:SS'), '') as end_time,
err
from boost_versions where boost_id = $1)"""
);
const auto res_table = txn.prepared("boostlist")(request->boost_id()).exec();
if(res_table.size() == 0)
{
return Status(ABORTED, "No versions found");
}
const auto to_string = [](const pqxx::field &res) -> string
{
return res.is_null() ? "" : res.as<string>();
};
for(const auto &res : res_table)
{
auto reply = VersionInfo_t();
reply.set_ver_id (res["ver_id" ].as<int> ());
reply.set_orig_bin_sha (to_string(res["orig_sha" ]));
reply.set_zafl_bin_sha (to_string(res["zafl_sha" ]));
reply.set_submit_time (res["submit_time"].as<string>());
reply.set_start_time (res["start_time" ].as<string>());
reply.set_end_time (res["end_time" ].as<string>());
reply.set_err (res["err"].is_null() ? false : res["err"].as<bool>());
writer->Write(reply);
}
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
Status VersionAdd(ServerContext *context, const VersionAddRequest_t *request, VersionAddReply_t *reply) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Requested creation of new version for boost id " << request->boost_id() << endl;
conn.prepare("boostcheck", "select boost_id,boost_name from boosts where boost_id = $1");
const auto check_res = txn.prepared("boostcheck")(request->boost_id()).exec();
if(check_res.size() != 1)
{
return Status(ABORTED, "Boost id not found");
}
conn.prepare("versionadd", "insert into boost_versions ( boost_id, orig_binary, submit_time, err )"
" values ($1, $2, NOW(), false) returning ver_id ");
const auto boost_id = check_res[0]["boost_id"].as<int>();
const auto binary_data = pqxx::binarystring(request->version_contents().c_str(), request->version_contents().size());
const auto add_res = txn.prepared("versionadd")(boost_id)(binary_data).exec();
if(add_res.size() != 1)
{
return Status(ABORTED, "Unexpected sql reply.");
}
reply->set_version_id(add_res[0]["ver_id"].as<int>());
txn.commit();
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
Status GetZaflLog(ServerContext *context, const GetZaflLogRequest_t *request, LogFile_t *reply) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Requested zafl log for " << request->ver_id() << endl;
conn.prepare("getzafllog",
R"""(select
case when log is null then '' else encode(log,'escape') end as log
from zafl_logs where ver_id = $1)"""
);
const auto res_table = txn.prepared("getzafllog")(request->ver_id()).exec();
if(res_table.size() != 1)
{
return Status(ABORTED, "Invalid version id");
}
const auto zafl_log = res_table[0]["log"].as<string>();
reply->set_log(zafl_log);
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
Status GetFuzzLog(ServerContext *context, const GetFuzzLogRequest_t *request, LogFile_t *reply) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Requested Fuzz log for " << request->ver_id() << endl;
conn.prepare("getFuzzlog",
R"""(select
case when log is null then '' else encode(log,'escape') end as log
from fuzz_logs where ver_id = $1)"""
);
const auto res_table = txn.prepared("getFuzzlog")(request->ver_id()).exec();
if(res_table.size() != 1)
{
return Status(ABORTED, "Invalid version id");
}
const auto zafl_log = res_table[0]["log"].as<string>();
reply->set_log(zafl_log);
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
Status SeedAdd(ServerContext *context, const SeedAddRequest_t *request, SeedAddReply_t *reply) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Requested seed add " << request->boost_id() << endl;
// prepare and execute a command to insert an input
conn.prepare("input_add",
R"""(insert into inputs ( boost_id, create_time, prov, input_data) values ($1, NOW(), 'User', $2) returning input_id )""");
const auto res_input = txn.prepared("input_add")(request->boost_id())(request->seed_file()).exec();
if(res_input.size() != 1)
{
return Status(ABORTED, "Invalid boost id");
}
const auto input_id = res_input[0]["input_id"].as<int32_t>();
// prepare and execute a command to mark the input as a seed
conn.prepare("seed_add", R"""(insert into seeds ( input_id ) values ($1) returning seed_id )""");
const auto res_seed = txn.prepared("seed_add")(input_id).exec();
if(res_seed.size() != 1)
{
return Status(ABORTED, "Unexpected error");
}
const auto seed_id = res_seed[0]["seed_id"].as<int32_t>();
// finalize the transaction
txn.commit();
// prepare a reply
reply->set_input_id(input_id);
reply->set_seed_id (seed_id );
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
Status InputAdd(ServerContext *context, const InputAddRequest_t *request, InputAddReply_t *reply) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Requested input add for ver-id=" << request->ver_id() << endl;
conn.prepare("findboost", "select boost_id from boost_versions where ver_id = $1");
const auto boost_id_res = txn.prepared("findboost")(request->ver_id()).exec();
if(boost_id_res.size() != 1)
{
return Status(ABORTED, "Invalid version id");
}
const auto boost_id = boost_id_res[0]["boost_id"].as<int>();
// prepare and execute a command to insert an input
conn.prepare("aflinputadd", R"""(insert into inputs ( boost_id, create_time, prov, input_data) values ($1, NOW(), 'AFL', $2) returning input_id )""");
const auto res_input = txn.prepared("aflinputadd")(boost_id)(request->input_file()).exec();
if(res_input.size() != 1)
{
return Status(ABORTED, "Invalid boost id");
}
const auto input_id = res_input[0]["input_id"].as<int32_t>();
// finalize the transaction
txn.commit();
// prepare a reply
reply->set_input_id(input_id);
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
Status DownloadInputs(ServerContext *context, const DownloadInputsRequest_t *request, ServerWriter<DownloadInputsReply_t> *writer) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Requested boost list " << endl;
conn.prepare("input_download", "select input_id,input_data from inputs where boost_id in (select boost_id from boost_versions where ver_id = $1) ");
const auto res_table = txn.prepared("input_download")(request->ver_id()).exec();
if(res_table.size() == 0)
{
return Status(ABORTED, "No inputs found");
}
for(const auto &res : res_table)
{
// create a response
auto reply = DownloadInputsReply_t();
// get input id
const auto input_id = res["input_id"].as<uint32_t>();
reply.set_input_id(input_id);
// get yaml string
const auto input_contents = pqxx::binarystring(res["input_data"]).str();
reply.set_yaml_string(input_contents);
writer->Write(reply);
}
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
Status DownloadCrashes(ServerContext *context, const DownloadInputsRequest_t *request, ServerWriter<DownloadInputsReply_t> *writer) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Requested boost list " << endl;
conn.prepare("input_download", "select input_id,input_data from inputs where input_id in (select input_id from forensics where ver_id = $1) ");
const auto res_table = txn.prepared("input_download")(request->ver_id()).exec();
// OK to have 0 results, just means we haven't found a crash yet.
for(const auto &res : res_table)
{
// create a response
auto reply = DownloadInputsReply_t();
// get input id
const auto input_id = res["input_id"].as<uint32_t>();
reply.set_input_id(input_id);
// get yaml string
const auto input_contents = pqxx::binarystring(res["input_data"]).str();
reply.set_yaml_string(input_contents);
writer->Write(reply);
}
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
Status AddFuzzLog(ServerContext *context, const AddFuzzLogRequest_t *request, Empty_t *reply) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Add to Fuzz request" << endl;
conn.prepare("update_fuzz_log",
R"""(
with upsert as (update fuzz_logs set log = log || $2 where ver_id = $1 returning *)
insert into fuzz_logs (ver_id,log) select $1,$2 where not exists (select * from upsert)
)""");
txn.prepared("update_fuzz_log")(request->ver_id())(request->log()).exec();
txn.commit();
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
Status AddForensics(ServerContext *context, const AddForensicsRequest_t *request, AddForensicsReply_t *reply) override final
{
try
{
pqxx::connection conn{db_connect_options.c_str()};
pqxx::work txn(conn);
cout << "Add to crash request" << endl;
conn.prepare("addfor",
R"""(
insert into forensics (ver_id,input_id) values ($1,$2) returning forensic_id
)""");
const auto res = txn.prepared("addfor")(request->ver_id())(request->input_id()).exec();
txn.commit();
if(res.size() != 1)
{
return Status(ABORTED, "Internal error");
}
reply->set_forensic_id(res[0]["forensic_id"].as<uint32_t>());
return Status::OK;
}
catch(const std::exception& e)
{
return Status(ABORTED, e.what());
}
}
private:
string db_connect_options = string("dbname=" TURBODB " host=localhost");
const string turbod_schema =
R"""(
-- avoid NOTICES if tables already exist.
SET client_min_messages = error;
-- avoid error if type already exists
DO $$ BEGIN
create type InputProvenance as ENUM ('User', 'AFL', 'QSym', 'Unknown');
EXCEPTION
when duplicate_object then null;
END $$;
create table if not exists boosts
(
boost_id serial primary key,
boost_name bytea,
create_time timestamp
);
create table if not exists boost_versions
(
ver_id serial primary key,
boost_id integer references boosts(boost_id),
orig_binary bytea,
orig_bin_sha bytea,
zafl_binary bytea,
zafl_bin_sha bytea,
submit_time timestamp,
start_time timestamp,
end_time timestamp,
err bool
);
create table if not exists inputs
(
input_id serial primary key,
boost_id integer references boosts(boost_id),
create_time timestamp,
prov InputProvenance,
input_data bytea,
sha256 bytea
);
create table if not exists forensics
(
forensic_id serial primary key,
input_id integer references inputs(input_id),
ver_id integer references boost_versions(ver_id),
exit_code integer,
backtrace bytea,
prov InputProvenance
);
create table if not exists seeds
(
seed_id serial primary key,
input_id integer references inputs(input_id)
);
create table if not exists zafl_logs
(
log_id serial primary key,
log bytea,
ver_id integer references boost_versions(ver_id)
);
create table if not exists fuzz_logs
(
log_id serial primary key,
log bytea,
ver_id integer references boost_versions(ver_id),
prov InputProvenance
);
)""";
};
void RunServer()
{
// listening port
const auto server_address=string("0.0.0.0:55155");
// create the service, build it, bind to listening port, and register.
TurboServiceImpl service;
ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
// Finally start the server.
auto server = builder.BuildAndStart();
if(server == nullptr)
{
cout << "Could not start server! (Port already in use?)" << endl;
exit(1);
}
// log
cout << "Server listening on " << server_address << endl;
// Wait for the server to shutdown. Note that some other thread must be
// responsible for shutting down the server for this call to ever return.
server->Wait();
}
int main(int argc, char **argv)
{
const auto create_db_res = system("createdb " TURBODB);
if(create_db_res == -1)
{
cout << "Could not create process for creating database" << endl;
perror("main");
}
RunServer();
return 0;
}