blob: 57ae836a3de7a4eb486b06397cd52f92237668d8 [file] [log] [blame]
Karl Zhang3de5ab12021-05-31 11:45:48 +08001/*
2 * Copyright (c) 2019-2020, Arm Limited. All rights reserved.
3 *
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) {
160 // It turns out you're not allowed to remove "WRITE_ONCE" assets:
161 if ( asset->set_data.flags_string.find("PSA_STORAGE_FLAG_WRITE_ONCE")
162 == string::npos
163 ) {
164 call = bplate->bplate_string[teardown_sst];
165 find_replace_1st ("$uid", to_string(asset->asset_info.id_n), call);
166 call.append (bplate->bplate_string[teardown_sst_check]);
167 output_C_file << call;
168 }
169 }
170 // Same, but with key assets:
171 for (auto &asset : active_key_asset) {
172 call = bplate->bplate_string[teardown_key];
173 find_replace_1st ("$handle", asset->handle_str, call);
174 call.append (bplate->bplate_string[teardown_key_check]);
175 output_C_file << call;
176 }
177}
178
179// Write out the test itself.
180void tf_fuzz_info::write_test (void)
181{
182 string call;
183 string work = bplate->bplate_string[preamble_A]; // a temporary workspace string
184
185 // The test file should be open before calling this method.
186 // Spit out the obligatory preamble:
187 find_replace_all ("$purpose", test_purpose, work);
188 output_C_file << work;
189
190 // If using hashing, then spit out the hashing functions:
191 if (include_hashing_code) {
192 work = bplate->bplate_string[hashing_code];
193 output_C_file << work;
194 }
195
196 // Print out the second part of the preamble code:
197 work = bplate->bplate_string[preamble_B];
198 find_replace_all ("$purpose", test_purpose, work);
199 output_C_file << work;
200
201 output_C_file << " /* Variables (etc.) to initialize and check PSA "
202 << "assets: */" << endl;
203 for (auto call : calls) {
204 // Reminder: calls is a vector of *pointers to* psa_call subclass objects.
205 call->fill_in_prep_code();
206 call->write_out_prep_code (output_C_file);
207 }
208
209 // Print out the final part of the preamble code:
210 work = bplate->bplate_string[preamble_C];
211 find_replace_all ("$purpose", test_purpose, work);
212 output_C_file << work;
213
214 output_C_file << "\n\n /* PSA calls to test: */" << endl;
215 for (auto call : calls) {
216 call->fill_in_command(); // (fills in check code too)
217 call->write_out_command (output_C_file);
218 call->write_out_check_code (output_C_file);
219 }
220
221 output_C_file << "\n\n /* Removing assets left over from testing: */"
222 << endl;
223 teardown_test();
224
225 // Seal the deal:
226 output_C_file << bplate->bplate_string[closeout];
227
228 // Close the template and test files:
229 output_C_file.close();
230 fclose (template_file);
231}
232
233
234/* simulate_calls() goes through the vector of generated calls calculating expected
235 results for each. */
236void tf_fuzz_info::simulate_calls (void)
237{
238 bool asset_state_changed = false;
239
240 IV(cout << "Call sequence:" << endl;)
241 /* For now, much of the simulation "thinking" infrastructure is here for future
242 elaboration. The algorithm is to through each call one by one, copying
243 information to the asset in question. Then each currently-active asset is
244 allowed to react to that information until they all agree that they're
245 "quiescent." Finally, result information is copied from the asset back to
246 the call. */
247 for (auto this_call : calls) {
248 IV(cout << " " << this_call->call_description << " for asset "
249 << this_call->asset_info.get_name() << endl;)
250 this_call->copy_call_to_asset();
251 /* Note: this_call->the_asset will now point to the asset
252 associated with this_call, if any such asset exists. */
253 if (this_call->asset_info.the_asset != nullptr) {
254 /* If the asset exists, allow changes to it to affect other active
255 assets. */
256 asset_state_changed = false;
257 do {
258 for (auto this_asset : active_sst_asset) {
259 asset_state_changed |= this_asset->simulate();
260 }
261 for (auto this_asset : active_policy_asset) {
262 asset_state_changed |= this_asset->simulate();
263 }
264 for (auto this_asset : active_key_asset) {
265 asset_state_changed |= this_asset->simulate();
266 }
267 } while (asset_state_changed);
268 }
269 this_call->copy_asset_to_call();
270 }
271}
272
273
274/* Parse command-line parameters. exit() if error(s) found. Place results into
275 the resource object. */
276void tf_fuzz_info::parse_cmd_line_params (int argc, char* argv[])
277{
278 int exit_val = 0; // the linux return value, default 0, meaning all-good
279 vector<string> cmd_line_parameter, cmd_line_switch;
280 // (STL) vectors of hard cmd_line_parameter and cmd_line_switches
281 int n_parameters = 0, n_switches = 0;
282 // counting off cmd_line_parameter and cmd_line_switches while parsing
283 char testc;
284
285 // Parse arguments into lists of strings:
286 for (int i = 1; i < argc; ++i) {
287 if (argv[i][0] == '-') { // cmd_line_switch
288 if (argv[i][1] == '-') { // double-dash
289 cmd_line_switch.push_back (string(argv[i]+2));
290 } else { // single-dash cmd_line_switch; fine either way
291 cmd_line_switch.push_back (string(argv[i]+1));
292 }
293 ++n_switches;
294 } else { // hard cmd_line_parameter
295 cmd_line_parameter.push_back(argv[i]);
296 ++n_parameters;
297 }
298 }
299 // If too-few or too many cmd_line_parameter supplied
300 for (int i = 0; i < n_switches; ++i) {
301 // If usage string requested...
302 if (cmd_line_switch[i] == "h") {
303 exit_val = 10;
304 }
305 // If verbose requested, make note:
306 if (cmd_line_switch[i] == "v") {
307 verbose_mode = true;
308 }
309 }
310 if (exit_val == 10) { // -h switch
311 cout << "\nHow to run TF-Fuzz:" << endl;
312 } else if (n_parameters < 2) {
313 cerr << "\nToo few command-line parameters." << endl;
314 exit_val = 11;
315 } else if (n_parameters > 3) {
316 cerr << "\nToo many command-line parameters." << endl;
317 exit_val = 12;
318 } else {
319 template_file_name = cmd_line_parameter[0];
320 template_file = fopen (template_file_name.c_str(), "r");
321 test_output_file_name = cmd_line_parameter[1];
322 output_C_file.open (test_output_file_name, ios::out);
323 if (n_parameters == 3) {
324 /* TODO: The try-catch below doesn't always seem to work. For now,
325 manually "catch" the most basic problem: */
326 testc = cmd_line_parameter[2][0];
327 if (testc < '0' || testc > '9') {
328 cerr << "\nError: Random-seed value (third parameter) could "
329 << "not be interpreted as a number." << endl;
330 rand_seed = 0;
331 } else {
332 try {
333 rand_seed = stol (cmd_line_parameter[2], 0, 0);
334 } catch (int excep) {
335 excep = 0; // (keep compiler from complaining about not using excep)
336 cerr << "\nWarning: Random-seed value (third parameter) could "
337 << "not be interpreted as a number." << endl;
338 rand_seed = 0;
339 }
340 }
341 }
342 if (rand_seed == 0 || n_parameters < 3) {
343 if (n_parameters < 3) {
344 cout << "Info: random seed was not specified." << endl;
345 } else {
346 cout << "Warning: random seed, " << cmd_line_parameter[2]
347 << ", was not usable!" << endl;
348 }
349 srand((unsigned int) time(0)); // TODO: ideally, XOR or add in PID#
350 rand_seed = rand();
351 /* doesn't really matter, but it just "feels better" when the
352 default seed value is itself more random. */
353 }
354 cout << endl << "Using seed value of " << dec << rand_seed << " " << hex
355 << "(0x" << rand_seed << ")." << endl;
356 srand(rand_seed);
357 if (template_file == NULL) {
358 cerr << "\nError: Template file " << template_file_name
359 << " could not be opened." << endl;
360 exit_val = 13;
361 } else if (!output_C_file.is_open()) {
362 // If test-output file couldn't be opened
363 cerr << "\nError: Output C test file " << test_output_file_name
364 << " could not be opened." << endl;
365 exit_val = 14;
366 }
367 // Default (not entirely worthless) purpose of the test:
368 test_purpose.assign ( "template = " + template_file_name + ", seed = "
369 + to_string(rand_seed));
370 }
371 // Bad command line, or request for usage blurb, so tell them how to run us:
372 if (exit_val != 0) {
373 cout << endl << argv[0] << " usage:" << endl;
374 cout << " Basic cmd_line_parameter (positional, in order, "
375 << "left-to-right):" << endl;
376 cout << " Test-template file" << endl;
377 cout << " Test-output .c file" << endl;
378 cout << " (optional) random seed value" << endl;
379 cout << " Optional switches:" << endl;
380 cout << " -h or --h: This help (command-line usage) summary."
381 << endl;
382 cout << " -v or --v: Verbose mode." << endl;
383 cout << "Examples:" << endl;
384 cout << " " << argv[0] << " -h" << endl;
385 cout << " " << argv[0] << " template.txt output_test.c 0x5EED" << endl;
386 exit (exit_val);
387 }
388}
389
390
391void tf_fuzz_info::add_call (psa_call *the_call, bool append_bool,
392 bool set_barrier_bool) {
393 // For testing purposes only, uncomment this to force sequential ordering:
394 //append_bool = true;
395 vector<psa_call*>::size_type
396 barrier_pos = 0,
397 // barrier pos. before which calls for this asset may not be placed
398 insert_call_pos = 0, // where to place the new call
399 i; // loop index
400 bool barrier_found = false;
401 psa_call *candidate = nullptr; // (not for long)
402
403 if (set_barrier_bool) {
404 // Prevent calls regarding this asset being placed before this call:
405 the_call->barrier.assign (the_call->target_barrier);
406 IV(cout << "Inserted barrier for asset " << the_call->barrier << "." << endl;)
407 }
408 if (append_bool || calls.size() == 0) {
409 // Just .push_back() onto the end if asked to, or if this is the first call:
410 calls.push_back (the_call);
411 IV(cout << "Appended to end of call sequence: " << the_call->call_description
412 << "." << endl;)
413 return; // done, easy!
414 }
415 /* Now search for last call with a barrier for this asset. (Note: because
416 vector<psa_call*>::size_type is unsigned, we can't search backward from
417 .end(), decrementing past 0. Also, cannot initialize barrier_pos to -1;
418 must maintain boolean for that.) */
419 for (i = 0ULL, barrier_found = false; i < calls.size(); i++) {
420 candidate = calls[i];
421 if (candidate->barrier == the_call->target_barrier) {
422 barrier_pos = i;
423 barrier_found = true;
424 }
425 }
426 if (!barrier_found) {
427 /* STL-vector inserts occur *before* the stated index. With no barrier
428 found, we want to insert somewhere between before .begin() and after
429 .end(). So, we want a number between 0 and calls.size(), inclusive. */
430 insert_call_pos = (rand() % (calls.size() + 1));
431 IV(cout << "No barrier for asset " << the_call->asset_info.get_name()
432 << " found." << endl
433 << " Placing " << the_call->call_description
434 << " at position " << insert_call_pos << " in call sequence."
435 << endl;)
436 } else {
437 /* Insert at a random point between just after barrier and after the end
438 (including possibly after the end, but strictly after that barrier).
439 Since STL-vector inserts occur before the stated index, we want an
440 insertion point between the call after the barrier and calls.end(),
441 inclusive. */
442 insert_call_pos = (vector<psa_call*>::size_type)
443 ( barrier_pos + 1 // must be *after* barrier-carrying call
444 + (rand() % (calls.size() - barrier_pos))
445 );
446 IV(cout << "Barrier for asset " << the_call->asset_info.get_name()
447 << " found at position " << dec << barrier_pos << "." << endl;)
448 }
449 if (insert_call_pos == calls.size()) {
450 // Insert at end:
451 calls.push_back (the_call);
452 IV(cout << "Insertion position is at end of call list." << endl;)
453 } else {
454 // Insert before insert_call_position:
455 calls.insert (calls.begin() + insert_call_pos, the_call);
456 IV(cout << "Inserting " << the_call->call_description
457 << " at position " << dec << insert_call_pos << " in call sequence."
458 << endl;)
459 }
460}
461
462
463tf_fuzz_info::tf_fuzz_info (void) // (constructor)
464{
465 this->bplate = new boilerplate();
466 test_purpose = template_file_name = test_output_file_name = "";
467 rand_seed = 0;
468 verbose_mode = false;
469 include_hashing_code = false; // default
470}
471
472tf_fuzz_info::~tf_fuzz_info (void)
473{
474 delete bplate;
475}
476
477/**********************************************************************************
478 End of methods of class tf_fuzz_info.
479**********************************************************************************/
480
481
482int main(int argc, char* argv[])
483{
484 cout << "Trusted Firmware Fuzzer (TF-Fuzz) starting..." << endl << endl;
485
486 // Allocate "the world":
487 tf_fuzz_info *rsrc = new tf_fuzz_info;
488
489 // Parse parameters and open files:
490 rsrc->parse_cmd_line_params (argc, argv);
491
492 // Parse the test-template file:
493 yyin = rsrc->template_file;
494 int parse_result = yyparse (rsrc);
495
496 if (parse_result == 1) {
497 cerr << "\nError: Template file has errors." << endl;
498 } else if (parse_result == 2) {
499 cerr << "\nError: Sorry, TF-Fuzz ran out of memory." << endl;
500 }
501 cout << "Call sequence generated." << endl;
502
503 cout << "Simulating call sequence..." << endl;
504 rsrc->simulate_calls();
505
506 cout << "Writing test file, " << rsrc->test_output_file_name << "." << endl;
507 rsrc->write_test();
508 rsrc->output_C_file.close();
509
510 cout << endl << "TF-Fuzz test generation complete." << endl;
511 return 0;
512}