| /* |
| * Copyright (c) 2019-2024, Arm Limited. All rights reserved. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| * |
| */ |
| |
| #include "tf_fuzz.hpp" |
| |
| #include <ext/alloc_traits.h> |
| #include <ctime> // to seed random, if seed not passed in |
| #include <string> |
| #include <vector> |
| #include <iostream> |
| #include <fstream> |
| |
| #include "boilerplate.hpp" |
| #include "string_ops.hpp" |
| #include "data_blocks.hpp" |
| #include "psa_asset.hpp" |
| #include "find_or_create_asset.hpp" |
| #include "sst_asset.hpp" |
| #include "crypto_asset.hpp" |
| #include "psa_call.hpp" |
| #include "tf_fuzz_grammar.tab.hpp" |
| #include "variables.hpp" |
| #include "crypto_model.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) { |
| // write once assets cannot be removed |
| |
| // NOTE: As write once assets persist across tests, there is as |
| // chance of UID collisions between tests. There is no technical solution |
| // for this - test authors just have to be careful in their use of |
| // WRITE_ONCE. |
| if (asset->set_data.flags_string.find("PSA_STORAGE_FLAG_WRITE_ONCE") != |
| std::string::npos) { |
| continue; |
| } |
| 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;); |
| |
| /* The thinking logic will probably need elaborating in the future. |
| * |
| * The current algorithm goes through the calls, simuating their expected |
| * return codes. After call simulation, assets are updated, which are then |
| * allowed to react to the new information. Asset simulation loops 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_policy_to_call(); |
| this_call->simulate(); |
| 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; |
| |
| crypto_model::init_crypto_model(); |
| |
| // 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; |
| } |