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