blob: 3d52685a2c23a31679b097f9525a8a5b3b2f1f8f [file] [log] [blame]
Karl Zhang3de5ab12021-05-31 11:45:48 +08001/*
Mate Toth-Palffba10e2021-09-22 21:38:03 +02002 * Copyright (c) 2019-2022, Arm Limited. All rights reserved.
Karl Zhang3de5ab12021-05-31 11:45:48 +08003 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 *
6 */
7
8#include <ctime> // to seed random, if seed not passed in
9#include <string>
10#include <vector>
11#include <iostream>
12#include <cstdlib> // for srand() and rand()
13#include <cstdio> // for template lex&yacc input file
14#include "class_forwards.hpp"
15#include "boilerplate.hpp"
16#include "gibberish.hpp"
17#include "compute.hpp"
18#include "string_ops.hpp"
19#include "data_blocks.hpp"
20#include "psa_asset.hpp"
21#include "find_or_create_asset.hpp"
22#include "template_line.hpp"
23#include "tf_fuzz.hpp"
24#include "sst_asset.hpp"
25#include "crypto_asset.hpp"
26#include "psa_call.hpp"
27#include "tf_fuzz_grammar.tab.hpp"
28#include "variables.hpp"
29
30
31extern FILE* yyin; // telling lex&yacc which file to parse
32
33using namespace std;
34
35long psa_asset::unique_id_counter = 10; // counts unique IDs for assets
36long psa_call::unique_id_counter = 10; // counts unique IDs for assets
37 /* FYI: Must initialize these class variables outside the class. If
38 initialized inside the class, g++ requires they be const. */
39
40/**********************************************************************************
41 Methods of class tf_fuzz_info follow:
42**********************************************************************************/
43
44asset_search tf_fuzz_info::find_or_create_sst_asset (
45 psa_asset_search criterion, // what to search on
46 psa_asset_usage where, // where to search
47 string target_name, // ignored if not searching on name
48 uint64_t target_id, // also ignored if not searching on ID (e.g., SST UID)
49 long &serial_no, // search by asset's unique serial number
50 bool create_asset, // true to create the asset if it doesn't exist
51 vector<psa_asset*>::iterator &asset // returns a pointer to requested asset
52) {
53 return generic_find_or_create_asset<sst_asset>(
54 active_sst_asset, deleted_sst_asset,
55 invalid_sst_asset, criterion, where, target_name, target_id,
56 serial_no, create_asset, asset
57 );
58}
59
60asset_search tf_fuzz_info::find_or_create_key_asset (
61 psa_asset_search criterion, // what to search on
62 psa_asset_usage where, // where to search
63 string target_name, // ignored if not searching on name
64 uint64_t target_id, // also ignored if not searching on ID (e.g., SST UID)
65 long &serial_no, // search by asset's unique serial number
66 bool create_asset, // true to create the asset if it doesn't exist
67 vector<psa_asset*>:: iterator &asset // returns iterator to requested asset
68) {
69 return generic_find_or_create_asset<key_asset>(
70 active_key_asset, deleted_key_asset,
71 invalid_key_asset, criterion, where, target_name, target_id,
72 serial_no, create_asset, asset
73 );
74}
75
76asset_search tf_fuzz_info::find_or_create_policy_asset (
77 psa_asset_search criterion, // what to search on
78 psa_asset_usage where, // where to search
79 string target_name, // ignored unless searching on name
80 uint64_t target_id, // also ignored unless searching on ID (e.g., SST UID)
81 long &serial_no, // search by asset's unique serial number
82 bool create_asset, // true to create the asset if it doesn't exist
83 vector<psa_asset*>::iterator &asset // iterator to requested asset
84) {
85 return generic_find_or_create_asset<policy_asset>(
86 active_policy_asset, deleted_policy_asset,
87 invalid_policy_asset, criterion, where, target_name, target_id,
88 serial_no, create_asset, asset
89 );
90}
91
92asset_search tf_fuzz_info::find_or_create_psa_asset (
93 psa_asset_type asset_type, // what type of asset to find
94 psa_asset_search criterion, // what to search on
95 psa_asset_usage where, // where to search
96 string target_name, // ignored if not searching on name
97 uint64_t target_id, // also ignored if not searching on ID (e.g., SST UID)
98 long &serial_no, // search by asset's unique serial number
99 bool create_asset, // true to create the asset if it doesn't exist
100 vector<psa_asset*>::iterator &asset // returns iterator to asset
101) {
102 switch (asset_type) {
103 case psa_asset_type::sst:
104 return find_or_create_sst_asset (
105 criterion, where, target_name, target_id,
106 serial_no, create_asset, asset);
107 break;
108 case psa_asset_type::key:
109 return find_or_create_key_asset (
110 criterion, where, target_name, target_id,
111 serial_no, create_asset, asset);
112 break;
113 case psa_asset_type::policy:
114 return find_or_create_policy_asset (
115 criterion, where, target_name, target_id,
116 serial_no, create_asset, asset);
117 break;
118 default:
119 cerr << "\nError: Internal: Please report error "
120 << "#1503 to TF-Fuzz developers." << endl;
121 exit (1500);
122 }
123}
124
125// Return an iterator to a variable, if it exists. If not return variable.end().
126vector<variable_info>::iterator tf_fuzz_info::find_var (string var_name)
127{
128 vector<variable_info>::iterator the_var;
129 if (variable.empty()) {
130 return variable.end();
131 }
132 for (the_var = variable.begin(); the_var < variable.end(); the_var++) {
133 if (the_var->name == var_name) {
134 break;
135 }
136 }
137 return the_var;
138}
139// Add a variable to the vector if not already there; return true if already there.
140bool tf_fuzz_info::make_var (string var_name)
141{
142 bool found = false;
143 variable_info new_variable;
144
145 found = (find_var (var_name) != variable.end());
146 if (!found) {
147 new_variable.name.assign (var_name);
148 variable.push_back (new_variable);
149 }
150 return found;
151}
152
153
154// Remove any PSA resources used in the test. Returns success==true, fail==false.
155void tf_fuzz_info::teardown_test (void)
156{
157 string call;
158 // Traverse through the SST-assets list, writing out remove commands:
159 for (auto &asset : active_sst_asset) {
Mate Toth-Palffba10e2021-09-22 21:38:03 +0200160 call = bplate->bplate_string[teardown_sst];
161 find_replace_1st ("$uid", to_string(asset->asset_info.id_n), call);
162 call.append (bplate->bplate_string[teardown_sst_check]);
163 output_C_file << call;
Karl Zhang3de5ab12021-05-31 11:45:48 +0800164 }
165 // Same, but with key assets:
166 for (auto &asset : active_key_asset) {
167 call = bplate->bplate_string[teardown_key];
168 find_replace_1st ("$handle", asset->handle_str, call);
169 call.append (bplate->bplate_string[teardown_key_check]);
170 output_C_file << call;
171 }
172}
173
174// Write out the test itself.
175void tf_fuzz_info::write_test (void)
176{
177 string call;
178 string work = bplate->bplate_string[preamble_A]; // a temporary workspace string
179
180 // The test file should be open before calling this method.
181 // Spit out the obligatory preamble:
182 find_replace_all ("$purpose", test_purpose, work);
183 output_C_file << work;
184
185 // If using hashing, then spit out the hashing functions:
186 if (include_hashing_code) {
187 work = bplate->bplate_string[hashing_code];
188 output_C_file << work;
189 }
190
191 // Print out the second part of the preamble code:
192 work = bplate->bplate_string[preamble_B];
193 find_replace_all ("$purpose", test_purpose, work);
194 output_C_file << work;
195
196 output_C_file << " /* Variables (etc.) to initialize and check PSA "
197 << "assets: */" << endl;
198 for (auto call : calls) {
199 // Reminder: calls is a vector of *pointers to* psa_call subclass objects.
200 call->fill_in_prep_code();
201 call->write_out_prep_code (output_C_file);
202 }
203
204 // Print out the final part of the preamble code:
205 work = bplate->bplate_string[preamble_C];
206 find_replace_all ("$purpose", test_purpose, work);
207 output_C_file << work;
208
209 output_C_file << "\n\n /* PSA calls to test: */" << endl;
210 for (auto call : calls) {
211 call->fill_in_command(); // (fills in check code too)
212 call->write_out_command (output_C_file);
213 call->write_out_check_code (output_C_file);
214 }
215
216 output_C_file << "\n\n /* Removing assets left over from testing: */"
217 << endl;
218 teardown_test();
219
220 // Seal the deal:
221 output_C_file << bplate->bplate_string[closeout];
222
223 // Close the template and test files:
224 output_C_file.close();
225 fclose (template_file);
226}
227
228
229/* simulate_calls() goes through the vector of generated calls calculating expected
230 results for each. */
231void tf_fuzz_info::simulate_calls (void)
232{
233 bool asset_state_changed = false;
234
235 IV(cout << "Call sequence:" << endl;)
236 /* For now, much of the simulation "thinking" infrastructure is here for future
237 elaboration. The algorithm is to through each call one by one, copying
238 information to the asset in question. Then each currently-active asset is
239 allowed to react to that information until they all agree that they're
240 "quiescent." Finally, result information is copied from the asset back to
241 the call. */
242 for (auto this_call : calls) {
243 IV(cout << " " << this_call->call_description << " for asset "
244 << this_call->asset_info.get_name() << endl;)
245 this_call->copy_call_to_asset();
246 /* Note: this_call->the_asset will now point to the asset
247 associated with this_call, if any such asset exists. */
248 if (this_call->asset_info.the_asset != nullptr) {
249 /* If the asset exists, allow changes to it to affect other active
250 assets. */
251 asset_state_changed = false;
252 do {
253 for (auto this_asset : active_sst_asset) {
254 asset_state_changed |= this_asset->simulate();
255 }
256 for (auto this_asset : active_policy_asset) {
257 asset_state_changed |= this_asset->simulate();
258 }
259 for (auto this_asset : active_key_asset) {
260 asset_state_changed |= this_asset->simulate();
261 }
262 } while (asset_state_changed);
263 }
264 this_call->copy_asset_to_call();
265 }
266}
267
268
269/* Parse command-line parameters. exit() if error(s) found. Place results into
270 the resource object. */
271void tf_fuzz_info::parse_cmd_line_params (int argc, char* argv[])
272{
273 int exit_val = 0; // the linux return value, default 0, meaning all-good
274 vector<string> cmd_line_parameter, cmd_line_switch;
275 // (STL) vectors of hard cmd_line_parameter and cmd_line_switches
276 int n_parameters = 0, n_switches = 0;
277 // counting off cmd_line_parameter and cmd_line_switches while parsing
278 char testc;
279
280 // Parse arguments into lists of strings:
281 for (int i = 1; i < argc; ++i) {
282 if (argv[i][0] == '-') { // cmd_line_switch
283 if (argv[i][1] == '-') { // double-dash
284 cmd_line_switch.push_back (string(argv[i]+2));
285 } else { // single-dash cmd_line_switch; fine either way
286 cmd_line_switch.push_back (string(argv[i]+1));
287 }
288 ++n_switches;
289 } else { // hard cmd_line_parameter
290 cmd_line_parameter.push_back(argv[i]);
291 ++n_parameters;
292 }
293 }
294 // If too-few or too many cmd_line_parameter supplied
295 for (int i = 0; i < n_switches; ++i) {
296 // If usage string requested...
297 if (cmd_line_switch[i] == "h") {
298 exit_val = 10;
299 }
300 // If verbose requested, make note:
301 if (cmd_line_switch[i] == "v") {
302 verbose_mode = true;
303 }
304 }
305 if (exit_val == 10) { // -h switch
306 cout << "\nHow to run TF-Fuzz:" << endl;
307 } else if (n_parameters < 2) {
308 cerr << "\nToo few command-line parameters." << endl;
309 exit_val = 11;
310 } else if (n_parameters > 3) {
311 cerr << "\nToo many command-line parameters." << endl;
312 exit_val = 12;
313 } else {
314 template_file_name = cmd_line_parameter[0];
315 template_file = fopen (template_file_name.c_str(), "r");
316 test_output_file_name = cmd_line_parameter[1];
317 output_C_file.open (test_output_file_name, ios::out);
318 if (n_parameters == 3) {
319 /* TODO: The try-catch below doesn't always seem to work. For now,
320 manually "catch" the most basic problem: */
321 testc = cmd_line_parameter[2][0];
322 if (testc < '0' || testc > '9') {
323 cerr << "\nError: Random-seed value (third parameter) could "
324 << "not be interpreted as a number." << endl;
325 rand_seed = 0;
326 } else {
327 try {
328 rand_seed = stol (cmd_line_parameter[2], 0, 0);
329 } catch (int excep) {
330 excep = 0; // (keep compiler from complaining about not using excep)
331 cerr << "\nWarning: Random-seed value (third parameter) could "
332 << "not be interpreted as a number." << endl;
333 rand_seed = 0;
334 }
335 }
336 }
337 if (rand_seed == 0 || n_parameters < 3) {
338 if (n_parameters < 3) {
339 cout << "Info: random seed was not specified." << endl;
340 } else {
341 cout << "Warning: random seed, " << cmd_line_parameter[2]
342 << ", was not usable!" << endl;
343 }
344 srand((unsigned int) time(0)); // TODO: ideally, XOR or add in PID#
345 rand_seed = rand();
346 /* doesn't really matter, but it just "feels better" when the
347 default seed value is itself more random. */
348 }
349 cout << endl << "Using seed value of " << dec << rand_seed << " " << hex
350 << "(0x" << rand_seed << ")." << endl;
351 srand(rand_seed);
352 if (template_file == NULL) {
353 cerr << "\nError: Template file " << template_file_name
354 << " could not be opened." << endl;
355 exit_val = 13;
356 } else if (!output_C_file.is_open()) {
357 // If test-output file couldn't be opened
358 cerr << "\nError: Output C test file " << test_output_file_name
359 << " could not be opened." << endl;
360 exit_val = 14;
361 }
362 // Default (not entirely worthless) purpose of the test:
363 test_purpose.assign ( "template = " + template_file_name + ", seed = "
364 + to_string(rand_seed));
365 }
366 // Bad command line, or request for usage blurb, so tell them how to run us:
367 if (exit_val != 0) {
368 cout << endl << argv[0] << " usage:" << endl;
369 cout << " Basic cmd_line_parameter (positional, in order, "
370 << "left-to-right):" << endl;
371 cout << " Test-template file" << endl;
372 cout << " Test-output .c file" << endl;
373 cout << " (optional) random seed value" << endl;
374 cout << " Optional switches:" << endl;
375 cout << " -h or --h: This help (command-line usage) summary."
376 << endl;
377 cout << " -v or --v: Verbose mode." << endl;
378 cout << "Examples:" << endl;
379 cout << " " << argv[0] << " -h" << endl;
380 cout << " " << argv[0] << " template.txt output_test.c 0x5EED" << endl;
381 exit (exit_val);
382 }
383}
384
385
386void tf_fuzz_info::add_call (psa_call *the_call, bool append_bool,
387 bool set_barrier_bool) {
388 // For testing purposes only, uncomment this to force sequential ordering:
389 //append_bool = true;
390 vector<psa_call*>::size_type
391 barrier_pos = 0,
392 // barrier pos. before which calls for this asset may not be placed
393 insert_call_pos = 0, // where to place the new call
394 i; // loop index
395 bool barrier_found = false;
396 psa_call *candidate = nullptr; // (not for long)
397
398 if (set_barrier_bool) {
399 // Prevent calls regarding this asset being placed before this call:
400 the_call->barrier.assign (the_call->target_barrier);
401 IV(cout << "Inserted barrier for asset " << the_call->barrier << "." << endl;)
402 }
403 if (append_bool || calls.size() == 0) {
404 // Just .push_back() onto the end if asked to, or if this is the first call:
405 calls.push_back (the_call);
406 IV(cout << "Appended to end of call sequence: " << the_call->call_description
407 << "." << endl;)
408 return; // done, easy!
409 }
410 /* Now search for last call with a barrier for this asset. (Note: because
411 vector<psa_call*>::size_type is unsigned, we can't search backward from
412 .end(), decrementing past 0. Also, cannot initialize barrier_pos to -1;
413 must maintain boolean for that.) */
414 for (i = 0ULL, barrier_found = false; i < calls.size(); i++) {
415 candidate = calls[i];
416 if (candidate->barrier == the_call->target_barrier) {
417 barrier_pos = i;
418 barrier_found = true;
419 }
420 }
421 if (!barrier_found) {
422 /* STL-vector inserts occur *before* the stated index. With no barrier
423 found, we want to insert somewhere between before .begin() and after
424 .end(). So, we want a number between 0 and calls.size(), inclusive. */
425 insert_call_pos = (rand() % (calls.size() + 1));
426 IV(cout << "No barrier for asset " << the_call->asset_info.get_name()
427 << " found." << endl
428 << " Placing " << the_call->call_description
429 << " at position " << insert_call_pos << " in call sequence."
430 << endl;)
431 } else {
432 /* Insert at a random point between just after barrier and after the end
433 (including possibly after the end, but strictly after that barrier).
434 Since STL-vector inserts occur before the stated index, we want an
435 insertion point between the call after the barrier and calls.end(),
436 inclusive. */
437 insert_call_pos = (vector<psa_call*>::size_type)
438 ( barrier_pos + 1 // must be *after* barrier-carrying call
439 + (rand() % (calls.size() - barrier_pos))
440 );
441 IV(cout << "Barrier for asset " << the_call->asset_info.get_name()
442 << " found at position " << dec << barrier_pos << "." << endl;)
443 }
444 if (insert_call_pos == calls.size()) {
445 // Insert at end:
446 calls.push_back (the_call);
447 IV(cout << "Insertion position is at end of call list." << endl;)
448 } else {
449 // Insert before insert_call_position:
450 calls.insert (calls.begin() + insert_call_pos, the_call);
451 IV(cout << "Inserting " << the_call->call_description
452 << " at position " << dec << insert_call_pos << " in call sequence."
453 << endl;)
454 }
455}
456
457
458tf_fuzz_info::tf_fuzz_info (void) // (constructor)
459{
460 this->bplate = new boilerplate();
461 test_purpose = template_file_name = test_output_file_name = "";
462 rand_seed = 0;
463 verbose_mode = false;
464 include_hashing_code = false; // default
465}
466
467tf_fuzz_info::~tf_fuzz_info (void)
468{
469 delete bplate;
470}
471
472/**********************************************************************************
473 End of methods of class tf_fuzz_info.
474**********************************************************************************/
475
476
477int main(int argc, char* argv[])
478{
479 cout << "Trusted Firmware Fuzzer (TF-Fuzz) starting..." << endl << endl;
480
481 // Allocate "the world":
482 tf_fuzz_info *rsrc = new tf_fuzz_info;
483
484 // Parse parameters and open files:
485 rsrc->parse_cmd_line_params (argc, argv);
486
487 // Parse the test-template file:
488 yyin = rsrc->template_file;
489 int parse_result = yyparse (rsrc);
490
491 if (parse_result == 1) {
492 cerr << "\nError: Template file has errors." << endl;
493 } else if (parse_result == 2) {
494 cerr << "\nError: Sorry, TF-Fuzz ran out of memory." << endl;
495 }
496 cout << "Call sequence generated." << endl;
497
498 cout << "Simulating call sequence..." << endl;
499 rsrc->simulate_calls();
500
501 cout << "Writing test file, " << rsrc->test_output_file_name << "." << endl;
502 rsrc->write_test();
503 rsrc->output_C_file.close();
504
505 cout << endl << "TF-Fuzz test generation complete." << endl;
506 return 0;
507}