Add tf_fuzz tool

This is fully derived from tf-m repo.

Signed-off-by: Karl Zhang <karl.zhang@arm.com>
Change-Id: I8d35e70eda9081af66d8fa3f3cb4beb1d953060e
diff --git a/tf_fuzz/tf_fuzz.cpp b/tf_fuzz/tf_fuzz.cpp
new file mode 100644
index 0000000..57ae836
--- /dev/null
+++ b/tf_fuzz/tf_fuzz.cpp
@@ -0,0 +1,512 @@
+/*
+ * Copyright (c) 2019-2020, 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) {
+        // It turns out you're not allowed to remove "WRITE_ONCE" assets:
+        if (   asset->set_data.flags_string.find("PSA_STORAGE_FLAG_WRITE_ONCE")
+            == string::npos
+           ) {
+            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;
+}