blob: 3d52685a2c23a31679b097f9525a8a5b3b2f1f8f [file] [log] [blame]
/*
* Copyright (c) 2019-2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <ctime> // to seed random, if seed not passed in
#include <string>
#include <vector>
#include <iostream>
#include <cstdlib> // for srand() and rand()
#include <cstdio> // for template lex&yacc input file
#include "class_forwards.hpp"
#include "boilerplate.hpp"
#include "gibberish.hpp"
#include "compute.hpp"
#include "string_ops.hpp"
#include "data_blocks.hpp"
#include "psa_asset.hpp"
#include "find_or_create_asset.hpp"
#include "template_line.hpp"
#include "tf_fuzz.hpp"
#include "sst_asset.hpp"
#include "crypto_asset.hpp"
#include "psa_call.hpp"
#include "tf_fuzz_grammar.tab.hpp"
#include "variables.hpp"
extern FILE* yyin; // telling lex&yacc which file to parse
using namespace std;
long psa_asset::unique_id_counter = 10; // counts unique IDs for assets
long psa_call::unique_id_counter = 10; // counts unique IDs for assets
/* FYI: Must initialize these class variables outside the class. If
initialized inside the class, g++ requires they be const. */
/**********************************************************************************
Methods of class tf_fuzz_info follow:
**********************************************************************************/
asset_search tf_fuzz_info::find_or_create_sst_asset (
psa_asset_search criterion, // what to search on
psa_asset_usage where, // where to search
string target_name, // ignored if not searching on name
uint64_t target_id, // also ignored if not searching on ID (e.g., SST UID)
long &serial_no, // search by asset's unique serial number
bool create_asset, // true to create the asset if it doesn't exist
vector<psa_asset*>::iterator &asset // returns a pointer to requested asset
) {
return generic_find_or_create_asset<sst_asset>(
active_sst_asset, deleted_sst_asset,
invalid_sst_asset, criterion, where, target_name, target_id,
serial_no, create_asset, asset
);
}
asset_search tf_fuzz_info::find_or_create_key_asset (
psa_asset_search criterion, // what to search on
psa_asset_usage where, // where to search
string target_name, // ignored if not searching on name
uint64_t target_id, // also ignored if not searching on ID (e.g., SST UID)
long &serial_no, // search by asset's unique serial number
bool create_asset, // true to create the asset if it doesn't exist
vector<psa_asset*>:: iterator &asset // returns iterator to requested asset
) {
return generic_find_or_create_asset<key_asset>(
active_key_asset, deleted_key_asset,
invalid_key_asset, criterion, where, target_name, target_id,
serial_no, create_asset, asset
);
}
asset_search tf_fuzz_info::find_or_create_policy_asset (
psa_asset_search criterion, // what to search on
psa_asset_usage where, // where to search
string target_name, // ignored unless searching on name
uint64_t target_id, // also ignored unless searching on ID (e.g., SST UID)
long &serial_no, // search by asset's unique serial number
bool create_asset, // true to create the asset if it doesn't exist
vector<psa_asset*>::iterator &asset // iterator to requested asset
) {
return generic_find_or_create_asset<policy_asset>(
active_policy_asset, deleted_policy_asset,
invalid_policy_asset, criterion, where, target_name, target_id,
serial_no, create_asset, asset
);
}
asset_search tf_fuzz_info::find_or_create_psa_asset (
psa_asset_type asset_type, // what type of asset to find
psa_asset_search criterion, // what to search on
psa_asset_usage where, // where to search
string target_name, // ignored if not searching on name
uint64_t target_id, // also ignored if not searching on ID (e.g., SST UID)
long &serial_no, // search by asset's unique serial number
bool create_asset, // true to create the asset if it doesn't exist
vector<psa_asset*>::iterator &asset // returns iterator to asset
) {
switch (asset_type) {
case psa_asset_type::sst:
return find_or_create_sst_asset (
criterion, where, target_name, target_id,
serial_no, create_asset, asset);
break;
case psa_asset_type::key:
return find_or_create_key_asset (
criterion, where, target_name, target_id,
serial_no, create_asset, asset);
break;
case psa_asset_type::policy:
return find_or_create_policy_asset (
criterion, where, target_name, target_id,
serial_no, create_asset, asset);
break;
default:
cerr << "\nError: Internal: Please report error "
<< "#1503 to TF-Fuzz developers." << endl;
exit (1500);
}
}
// Return an iterator to a variable, if it exists. If not return variable.end().
vector<variable_info>::iterator tf_fuzz_info::find_var (string var_name)
{
vector<variable_info>::iterator the_var;
if (variable.empty()) {
return variable.end();
}
for (the_var = variable.begin(); the_var < variable.end(); the_var++) {
if (the_var->name == var_name) {
break;
}
}
return the_var;
}
// Add a variable to the vector if not already there; return true if already there.
bool tf_fuzz_info::make_var (string var_name)
{
bool found = false;
variable_info new_variable;
found = (find_var (var_name) != variable.end());
if (!found) {
new_variable.name.assign (var_name);
variable.push_back (new_variable);
}
return found;
}
// Remove any PSA resources used in the test. Returns success==true, fail==false.
void tf_fuzz_info::teardown_test (void)
{
string call;
// Traverse through the SST-assets list, writing out remove commands:
for (auto &asset : active_sst_asset) {
call = bplate->bplate_string[teardown_sst];
find_replace_1st ("$uid", to_string(asset->asset_info.id_n), call);
call.append (bplate->bplate_string[teardown_sst_check]);
output_C_file << call;
}
// Same, but with key assets:
for (auto &asset : active_key_asset) {
call = bplate->bplate_string[teardown_key];
find_replace_1st ("$handle", asset->handle_str, call);
call.append (bplate->bplate_string[teardown_key_check]);
output_C_file << call;
}
}
// Write out the test itself.
void tf_fuzz_info::write_test (void)
{
string call;
string work = bplate->bplate_string[preamble_A]; // a temporary workspace string
// The test file should be open before calling this method.
// Spit out the obligatory preamble:
find_replace_all ("$purpose", test_purpose, work);
output_C_file << work;
// If using hashing, then spit out the hashing functions:
if (include_hashing_code) {
work = bplate->bplate_string[hashing_code];
output_C_file << work;
}
// Print out the second part of the preamble code:
work = bplate->bplate_string[preamble_B];
find_replace_all ("$purpose", test_purpose, work);
output_C_file << work;
output_C_file << " /* Variables (etc.) to initialize and check PSA "
<< "assets: */" << endl;
for (auto call : calls) {
// Reminder: calls is a vector of *pointers to* psa_call subclass objects.
call->fill_in_prep_code();
call->write_out_prep_code (output_C_file);
}
// Print out the final part of the preamble code:
work = bplate->bplate_string[preamble_C];
find_replace_all ("$purpose", test_purpose, work);
output_C_file << work;
output_C_file << "\n\n /* PSA calls to test: */" << endl;
for (auto call : calls) {
call->fill_in_command(); // (fills in check code too)
call->write_out_command (output_C_file);
call->write_out_check_code (output_C_file);
}
output_C_file << "\n\n /* Removing assets left over from testing: */"
<< endl;
teardown_test();
// Seal the deal:
output_C_file << bplate->bplate_string[closeout];
// Close the template and test files:
output_C_file.close();
fclose (template_file);
}
/* simulate_calls() goes through the vector of generated calls calculating expected
results for each. */
void tf_fuzz_info::simulate_calls (void)
{
bool asset_state_changed = false;
IV(cout << "Call sequence:" << endl;)
/* For now, much of the simulation "thinking" infrastructure is here for future
elaboration. The algorithm is to through each call one by one, copying
information to the asset in question. Then each currently-active asset is
allowed to react to that information until they all agree that they're
"quiescent." Finally, result information is copied from the asset back to
the call. */
for (auto this_call : calls) {
IV(cout << " " << this_call->call_description << " for asset "
<< this_call->asset_info.get_name() << endl;)
this_call->copy_call_to_asset();
/* Note: this_call->the_asset will now point to the asset
associated with this_call, if any such asset exists. */
if (this_call->asset_info.the_asset != nullptr) {
/* If the asset exists, allow changes to it to affect other active
assets. */
asset_state_changed = false;
do {
for (auto this_asset : active_sst_asset) {
asset_state_changed |= this_asset->simulate();
}
for (auto this_asset : active_policy_asset) {
asset_state_changed |= this_asset->simulate();
}
for (auto this_asset : active_key_asset) {
asset_state_changed |= this_asset->simulate();
}
} while (asset_state_changed);
}
this_call->copy_asset_to_call();
}
}
/* Parse command-line parameters. exit() if error(s) found. Place results into
the resource object. */
void tf_fuzz_info::parse_cmd_line_params (int argc, char* argv[])
{
int exit_val = 0; // the linux return value, default 0, meaning all-good
vector<string> cmd_line_parameter, cmd_line_switch;
// (STL) vectors of hard cmd_line_parameter and cmd_line_switches
int n_parameters = 0, n_switches = 0;
// counting off cmd_line_parameter and cmd_line_switches while parsing
char testc;
// Parse arguments into lists of strings:
for (int i = 1; i < argc; ++i) {
if (argv[i][0] == '-') { // cmd_line_switch
if (argv[i][1] == '-') { // double-dash
cmd_line_switch.push_back (string(argv[i]+2));
} else { // single-dash cmd_line_switch; fine either way
cmd_line_switch.push_back (string(argv[i]+1));
}
++n_switches;
} else { // hard cmd_line_parameter
cmd_line_parameter.push_back(argv[i]);
++n_parameters;
}
}
// If too-few or too many cmd_line_parameter supplied
for (int i = 0; i < n_switches; ++i) {
// If usage string requested...
if (cmd_line_switch[i] == "h") {
exit_val = 10;
}
// If verbose requested, make note:
if (cmd_line_switch[i] == "v") {
verbose_mode = true;
}
}
if (exit_val == 10) { // -h switch
cout << "\nHow to run TF-Fuzz:" << endl;
} else if (n_parameters < 2) {
cerr << "\nToo few command-line parameters." << endl;
exit_val = 11;
} else if (n_parameters > 3) {
cerr << "\nToo many command-line parameters." << endl;
exit_val = 12;
} else {
template_file_name = cmd_line_parameter[0];
template_file = fopen (template_file_name.c_str(), "r");
test_output_file_name = cmd_line_parameter[1];
output_C_file.open (test_output_file_name, ios::out);
if (n_parameters == 3) {
/* TODO: The try-catch below doesn't always seem to work. For now,
manually "catch" the most basic problem: */
testc = cmd_line_parameter[2][0];
if (testc < '0' || testc > '9') {
cerr << "\nError: Random-seed value (third parameter) could "
<< "not be interpreted as a number." << endl;
rand_seed = 0;
} else {
try {
rand_seed = stol (cmd_line_parameter[2], 0, 0);
} catch (int excep) {
excep = 0; // (keep compiler from complaining about not using excep)
cerr << "\nWarning: Random-seed value (third parameter) could "
<< "not be interpreted as a number." << endl;
rand_seed = 0;
}
}
}
if (rand_seed == 0 || n_parameters < 3) {
if (n_parameters < 3) {
cout << "Info: random seed was not specified." << endl;
} else {
cout << "Warning: random seed, " << cmd_line_parameter[2]
<< ", was not usable!" << endl;
}
srand((unsigned int) time(0)); // TODO: ideally, XOR or add in PID#
rand_seed = rand();
/* doesn't really matter, but it just "feels better" when the
default seed value is itself more random. */
}
cout << endl << "Using seed value of " << dec << rand_seed << " " << hex
<< "(0x" << rand_seed << ")." << endl;
srand(rand_seed);
if (template_file == NULL) {
cerr << "\nError: Template file " << template_file_name
<< " could not be opened." << endl;
exit_val = 13;
} else if (!output_C_file.is_open()) {
// If test-output file couldn't be opened
cerr << "\nError: Output C test file " << test_output_file_name
<< " could not be opened." << endl;
exit_val = 14;
}
// Default (not entirely worthless) purpose of the test:
test_purpose.assign ( "template = " + template_file_name + ", seed = "
+ to_string(rand_seed));
}
// Bad command line, or request for usage blurb, so tell them how to run us:
if (exit_val != 0) {
cout << endl << argv[0] << " usage:" << endl;
cout << " Basic cmd_line_parameter (positional, in order, "
<< "left-to-right):" << endl;
cout << " Test-template file" << endl;
cout << " Test-output .c file" << endl;
cout << " (optional) random seed value" << endl;
cout << " Optional switches:" << endl;
cout << " -h or --h: This help (command-line usage) summary."
<< endl;
cout << " -v or --v: Verbose mode." << endl;
cout << "Examples:" << endl;
cout << " " << argv[0] << " -h" << endl;
cout << " " << argv[0] << " template.txt output_test.c 0x5EED" << endl;
exit (exit_val);
}
}
void tf_fuzz_info::add_call (psa_call *the_call, bool append_bool,
bool set_barrier_bool) {
// For testing purposes only, uncomment this to force sequential ordering:
//append_bool = true;
vector<psa_call*>::size_type
barrier_pos = 0,
// barrier pos. before which calls for this asset may not be placed
insert_call_pos = 0, // where to place the new call
i; // loop index
bool barrier_found = false;
psa_call *candidate = nullptr; // (not for long)
if (set_barrier_bool) {
// Prevent calls regarding this asset being placed before this call:
the_call->barrier.assign (the_call->target_barrier);
IV(cout << "Inserted barrier for asset " << the_call->barrier << "." << endl;)
}
if (append_bool || calls.size() == 0) {
// Just .push_back() onto the end if asked to, or if this is the first call:
calls.push_back (the_call);
IV(cout << "Appended to end of call sequence: " << the_call->call_description
<< "." << endl;)
return; // done, easy!
}
/* Now search for last call with a barrier for this asset. (Note: because
vector<psa_call*>::size_type is unsigned, we can't search backward from
.end(), decrementing past 0. Also, cannot initialize barrier_pos to -1;
must maintain boolean for that.) */
for (i = 0ULL, barrier_found = false; i < calls.size(); i++) {
candidate = calls[i];
if (candidate->barrier == the_call->target_barrier) {
barrier_pos = i;
barrier_found = true;
}
}
if (!barrier_found) {
/* STL-vector inserts occur *before* the stated index. With no barrier
found, we want to insert somewhere between before .begin() and after
.end(). So, we want a number between 0 and calls.size(), inclusive. */
insert_call_pos = (rand() % (calls.size() + 1));
IV(cout << "No barrier for asset " << the_call->asset_info.get_name()
<< " found." << endl
<< " Placing " << the_call->call_description
<< " at position " << insert_call_pos << " in call sequence."
<< endl;)
} else {
/* Insert at a random point between just after barrier and after the end
(including possibly after the end, but strictly after that barrier).
Since STL-vector inserts occur before the stated index, we want an
insertion point between the call after the barrier and calls.end(),
inclusive. */
insert_call_pos = (vector<psa_call*>::size_type)
( barrier_pos + 1 // must be *after* barrier-carrying call
+ (rand() % (calls.size() - barrier_pos))
);
IV(cout << "Barrier for asset " << the_call->asset_info.get_name()
<< " found at position " << dec << barrier_pos << "." << endl;)
}
if (insert_call_pos == calls.size()) {
// Insert at end:
calls.push_back (the_call);
IV(cout << "Insertion position is at end of call list." << endl;)
} else {
// Insert before insert_call_position:
calls.insert (calls.begin() + insert_call_pos, the_call);
IV(cout << "Inserting " << the_call->call_description
<< " at position " << dec << insert_call_pos << " in call sequence."
<< endl;)
}
}
tf_fuzz_info::tf_fuzz_info (void) // (constructor)
{
this->bplate = new boilerplate();
test_purpose = template_file_name = test_output_file_name = "";
rand_seed = 0;
verbose_mode = false;
include_hashing_code = false; // default
}
tf_fuzz_info::~tf_fuzz_info (void)
{
delete bplate;
}
/**********************************************************************************
End of methods of class tf_fuzz_info.
**********************************************************************************/
int main(int argc, char* argv[])
{
cout << "Trusted Firmware Fuzzer (TF-Fuzz) starting..." << endl << endl;
// Allocate "the world":
tf_fuzz_info *rsrc = new tf_fuzz_info;
// Parse parameters and open files:
rsrc->parse_cmd_line_params (argc, argv);
// Parse the test-template file:
yyin = rsrc->template_file;
int parse_result = yyparse (rsrc);
if (parse_result == 1) {
cerr << "\nError: Template file has errors." << endl;
} else if (parse_result == 2) {
cerr << "\nError: Sorry, TF-Fuzz ran out of memory." << endl;
}
cout << "Call sequence generated." << endl;
cout << "Simulating call sequence..." << endl;
rsrc->simulate_calls();
cout << "Writing test file, " << rsrc->test_output_file_name << "." << endl;
rsrc->write_test();
rsrc->output_C_file.close();
cout << endl << "TF-Fuzz test generation complete." << endl;
return 0;
}