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