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