diff --git a/scripts/bash/evmlisa-job-cpu.sh b/scripts/bash/evmlisa-job-cpu.sh index 29f515b35..1c68e0770 100644 --- a/scripts/bash/evmlisa-job-cpu.sh +++ b/scripts/bash/evmlisa-job-cpu.sh @@ -6,7 +6,7 @@ #SBATCH --cpus-per-task=1 #SBATCH --time=3-00:00:00 #SBATCH --mem=64G -#SBATCH --mail-user=saveriomattia.merenda@studenti.unipr.it +#SBATCH --mail-user= #SBATCH --mail-type=FAIL #SBATCH --partition=cpu #SBATCH --qos=cpu diff --git a/scripts/bash/evmlisa-job-cpu_guest.sh b/scripts/bash/evmlisa-job-cpu_guest.sh index ffa44ce96..2f024f36b 100644 --- a/scripts/bash/evmlisa-job-cpu_guest.sh +++ b/scripts/bash/evmlisa-job-cpu_guest.sh @@ -6,7 +6,7 @@ #SBATCH --cpus-per-task=1 #SBATCH --time=3-00:00:00 #SBATCH --mem=64G -#SBATCH --mail-user=saveriomattia.merenda@studenti.unipr.it +#SBATCH --mail-user= #SBATCH --mail-type=FAIL #SBATCH --partition=cpu_guest #SBATCH --qos=cpu_guest diff --git a/scripts/bash/job-handler.sh b/scripts/bash/job-handler.sh index 18823e92e..7653a31cb 100644 --- a/scripts/bash/job-handler.sh +++ b/scripts/bash/job-handler.sh @@ -5,7 +5,7 @@ #SBATCH --cpus-per-task=1 #SBATCH --time=0-06:00:00 #SBATCH --mem=16G -#SBATCH --mail-user=saveriomattia.merenda@studenti.unipr.it +#SBATCH --mail-user= #SBATCH --mail-type=FAIL #SBATCH --partition=cpu #SBATCH --qos=cpu diff --git a/scripts/python/benchmark-checkers/compile-smart-contracts.py b/scripts/python/benchmark-checkers/compile-smart-contracts.py index f46d186c2..187ee5f2a 100644 --- a/scripts/python/benchmark-checkers/compile-smart-contracts.py +++ b/scripts/python/benchmark-checkers/compile-smart-contracts.py @@ -33,16 +33,16 @@ def load_version_mapping(version_file): def generate_file_index(folder_path, output_json="file_index.json"): file_index = {} - + files = sorted(f for f in os.listdir(folder_path) if f.endswith(".sol") and os.path.isfile(os.path.join(folder_path, f))) - + for i, file_name in enumerate(files, start=1): - file_name_no_ext = os.path.splitext(file_name)[0] + file_name_no_ext = os.path.splitext(file_name)[0] file_index[file_name_no_ext] = i with open(output_json, 'w', encoding='utf-8') as json_file: json.dump(file_index, json_file, indent=4) - + print(f"File index saved in {output_json}") def extract_solidity_versions(src_folder, output_csv="solidity_versions.csv"): @@ -59,7 +59,7 @@ def extract_solidity_versions(src_folder, output_csv="solidity_versions.csv"): pragma_match = pragma_regex.search(content) if pragma_match: original_version = pragma_match.group(1).strip() - + cleaned_version = re.sub(r"[^\d\.\s]", "", original_version).strip() versions = cleaned_version.split() @@ -82,7 +82,7 @@ def extract_solidity_versions(src_folder, output_csv="solidity_versions.csv"): def compile_solidity_sources_with_different_version(source_dir, json_dir, version_file): """ - Compiles all .sol files in the specified source directory using different versions of solc, + Compiles all .sol files in the specified source directory using different versions of solc, saving the bytecode for each file in JSON format in the specified output directory. """ version_mapping = load_version_mapping(version_file) @@ -105,7 +105,7 @@ def compile_solidity_sources_with_different_version(source_dir, json_dir, versio output_file = os.path.join(json_dir, f"{os.path.splitext(filename)[0]}.json") compiled_version = version_mapping.get(filename, None) - + if compiled_version is None: print(f"Version not specified for {filename} in {version_file}. Skipping file.") pbar.update(1) @@ -125,24 +125,24 @@ def compile_solidity_sources_with_different_version(source_dir, json_dir, versio # Command to compile and save the bytecode in JSON format command = ( f"solc-select use {compiled_version} > /dev/null && " - f"solc --combined-json bin,bin-runtime,abi {input_file} > {output_file} 2> /dev/null" + f"solc --combined-json bin,bin-runtime,abi {input_file} > {output_file} 2> /dev/null" ) - + # Execute the compilation command try: subprocess.run(command, shell=True, check=True) count_success += 1 except subprocess.CalledProcessError as e: count_failure += 1 - + # Update the progress bar pbar.update(1) - + print(f"Compiled successfully {count_success}/{count_success + count_failure} files.") def compile_solidity_sources(source_dir, json_dir): """ - Compiles all .sol files in the specified source directory using solc, + Compiles all .sol files in the specified source directory using solc, saving the bytecode for each file in JSON format in the specified output directory. """ # Clear and create JSON output directory @@ -173,20 +173,20 @@ def compile_solidity_sources(source_dir, json_dir): # Full paths for input and output files input_file = os.path.join(source_dir, filename) output_file = os.path.join(json_dir, f"{os.path.splitext(filename)[0]}.json") - + # Command to compile and save the bytecode in JSON format command = f"solc --combined-json bin,bin-runtime,abi --pretty-json {input_file} > {output_file} 2> /dev/null " - + # Execute the compilation command try: subprocess.run(command, shell=True, check=True) count_success += 1 except subprocess.CalledProcessError as e: count_failure += 1 - + # Update the progress bar pbar.update(1) - + print(f"Compiled successfully {count_success}/{count_success + count_failure} files.") def extract_and_save_longest_bytecode(bytecode_dir, json_dir, is_ethersolve=False, file_index=None, abi_dir=None): @@ -208,7 +208,7 @@ def extract_and_save_longest_bytecode(bytecode_dir, json_dir, is_ethersolve=Fals for json_filename in os.listdir(json_dir): if json_filename.endswith(".json"): json_filepath = os.path.join(json_dir, json_filename) - + # Check if the file is empty if os.path.getsize(json_filepath) == 0: # print(f"Skipping empty file: {json_filename}") @@ -218,7 +218,7 @@ def extract_and_save_longest_bytecode(bytecode_dir, json_dir, is_ethersolve=Fals with open(json_filepath, 'r') as json_file: data = json.load(json_file) contracts = data.get("contracts", {}) - + longest_bytecode = None longest_contract_name = None max_bytecode_length = 0 # Variable to store the maximum file size @@ -242,23 +242,23 @@ def extract_and_save_longest_bytecode(bytecode_dir, json_dir, is_ethersolve=Fals # Save the longest bytecode, if it exists if longest_bytecode: base_filename = os.path.splitext(json_filename)[0] - + if file_index is not None: file_id = file_index.get(base_filename) # Match string name to integer base_filename = str(file_id) if file_id is not None else base_filename - + bytecode_filename = os.path.join(bytecode_dir, f"{base_filename}.bytecode") - + with open(bytecode_filename, 'w') as bytecode_file: bytecode_file.write("0x" + longest_bytecode) - + # Save ABI if available if abi and abi_dir is not None: - abi_filename = os.path.join(abi_dir, f"{base_filename}.abi.json") - - if isinstance(abi, str): + abi_filename = os.path.join(abi_dir, f"{base_filename}.abi") + + if isinstance(abi, str): abi = json.loads(abi) - + with open(abi_filename, 'w') as abi_file: json.dump(abi, abi_file, indent=4) # Update the progress bar @@ -279,79 +279,59 @@ def extract_and_save_bytecode(bytecode_dir, json_dir, is_ethersolve=False, file_ num_files = [f for f in os.listdir(json_dir) if f.endswith('.json')] # Progress bar setup - with tqdm(total=len(num_files), desc="Extracting files...") as pbar: - for json_filename in os.listdir(json_dir): - if json_filename.endswith(".json"): - json_filepath = os.path.join(json_dir, json_filename) - with open(json_filepath, 'r') as json_file: - # Check if the file is empty by reading the first character - if json_file.read(1) == "": - # print(f"Skipping empty file: {json_filename}") - pbar.update(1) - continue - json_file.seek(0) # Reset the file pointer to the beginning - - data = json.load(json_file) - contracts = data.get("contracts", {}) - count = 1 # Sequential counter for each bytecode in the same JSON - abi = None + # with tqdm(total=len(num_files), desc="Extracting files...") as pbar: + for json_filename in os.listdir(json_dir): + if json_filename.endswith(".json"): + json_filepath = os.path.join(json_dir, json_filename) + with open(json_filepath, 'r') as json_file: + # Check if the file is empty by reading the first character + if json_file.read(1) == "": + # print(f"Skipping empty file: {json_filename}") + # pbar.update(1) + continue + json_file.seek(0) # Reset the file pointer to the beginning - for contract_name, contract_data in contracts.items(): - if(is_ethersolve): - bytecode = contract_data.get("bin") - else: - bytecode = contract_data.get("bin-runtime") - abi = contract_data.get("abi") - - if bytecode: + data = json.load(json_file) + contracts = data.get("contracts", {}) + count = 1 # Sequential counter for each bytecode in the same JSON + abi = None + + for contract_name, contract_data in contracts.items(): + if(is_ethersolve): + bytecode = contract_data.get("bin") + else: + bytecode = contract_data.get("bin-runtime") + abi = contract_data.get("abi") + + if bytecode: + bytecode_filename = os.path.join( + bytecode_dir, f"{os.path.splitext(json_filename)[0]}_{count}.bytecode" + ) + + if file_index is not None: + file_id = file_index.get(os.path.splitext(json_filename)[0]) # Match string name to integer + # Add a sequential number to the filename bytecode_filename = os.path.join( - bytecode_dir, f"{os.path.splitext(json_filename)[0]}_{count}.bytecode" + bytecode_dir, f"{file_id}_{count}.bytecode" ) - if file_index is not None: - file_id = file_index.get(os.path.splitext(json_filename)[0]) # Match string name to integer - # Add a sequential number to the filename - bytecode_filename = os.path.join( - bytecode_dir, f"{file_id}_{count}.bytecode" - ) - - with open(bytecode_filename, 'w') as bytecode_file: - bytecode_file.write("0x" + bytecode) - - # Save ABI if available - if abi and abi_dir is not None: - abi_filename = os.path.join(abi_dir, f"{file_id}_{count}.abi.json") - - if isinstance(abi, str): - abi = json.loads(abi) - - with open(abi_filename, 'w') as abi_file: - json.dump(abi, abi_file, indent=4) - - # print(f"Extracted bytecode to {bytecode_filename}") - count += 1 # Increment counter for next bytecode - # Update the progress bar - pbar.update(1) + with open(bytecode_filename, 'w') as bytecode_file: + bytecode_file.write("0x" + bytecode) -def compile_bridge(name): - extract_solidity_versions(src_folder=f'./{name}/source-code', - output_csv=f'./{name}/source-code/version.csv') - - compile_solidity_sources_with_different_version(source_dir=f'./{name}/source-code', - json_dir=f'./{name}/json', - version_file=f'./{name}/source-code/version.csv') - - generate_file_index(folder_path=f'./{name}/source-code', - output_json=f'./{name}/match-file-index.json') - - with open(f'./{name}/match-file-index.json', 'r') as index_file: - match_file_index = json.load(index_file) - - extract_and_save_bytecode(bytecode_dir=f'./{name}/bytecode', - json_dir=f'./{name}/json', - abi_dir=f'./{name}/abi', - file_index=match_file_index) + # Save ABI if available + if abi and abi_dir is not None: + abi_filename = os.path.join(abi_dir, f"{file_id}_{count}.abi") + if isinstance(abi, str): + abi = json.loads(abi) + + with open(abi_filename, 'w') as abi_file: + json.dump(abi, abi_file, indent=4) + + # print(f"Extracted bytecode to {bytecode_filename}") + count += 1 # Increment counter for next bytecode + # Update the progress bar + # pbar.update(1) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Compile datasets.") @@ -359,26 +339,9 @@ def compile_bridge(name): parser.add_argument("--smartbugs", action="store_true", help="Compile SmartBugs dataset") parser.add_argument("--slise", action="store_true", help="Compile SliSE dataset") parser.add_argument("--longest-bytecode", action="store_true", help="Save only the longest bytecode") - parser.add_argument("--manual", action="store_true", help="Manual mode") - parser.add_argument("--cross-chain-xguard", action="store_true", help="Compile XGuard dataset") args = parser.parse_args() - if args.cross_chain_xguard: - compile_bridge('cross-chain/XGuard/QBridge') # QBridge - compile_bridge('cross-chain/XGuard/MeterBridge') # MeterBridge - - if args.manual: - # Test ThorChain Bridge - extract_solidity_versions(src_folder='./cross-chain/THORChain-bridge/source-code', - output_csv='./cross-chain/THORChain-bridge/source-code/version.csv') - compile_solidity_sources_with_different_version(source_dir='./cross-chain/THORChain-bridge/source-code', - json_dir='./cross-chain/THORChain-bridge/json', - version_file='./cross-chain/THORChain-bridge/source-code/version.csv') - extract_and_save_longest_bytecode(bytecode_dir='./cross-chain/THORChain-bridge/bytecode/', - json_dir='./cross-chain/THORChain-bridge/json', - abi_dir='./cross-chain/THORChain-bridge/abi/') - if args.solidifi: compile_solidity_sources('./solidifi/reentrancy/source-code', './solidifi/reentrancy/json') @@ -388,7 +351,7 @@ def compile_bridge(name): './solidifi/tx-origin/json') compile_solidity_sources('./solidifi/timestamp-dependency/source-code', './solidifi/timestamp-dependency/json') - + if args.longest_bytecode: # EVMLiSA extract_and_save_longest_bytecode('./solidifi/vanilla/bytecode/evmlisa', @@ -425,7 +388,7 @@ def compile_bridge(name): # Timestamp-dependency extract_and_save_bytecode('./solidifi/timestamp-dependency/bytecode/evmlisa', './solidifi/timestamp-dependency/json') - + # EtherSolve extract_and_save_bytecode('./solidifi/vanilla/bytecode/ethersolve', './solidifi/vanilla/json', @@ -433,7 +396,7 @@ def compile_bridge(name): extract_and_save_bytecode('./solidifi/reentrancy/bytecode/ethersolve', './solidifi/reentrancy/json', True) - + if args.smartbugs: compile_solidity_sources_with_different_version('./smartbugs/reentrancy/source-code', './smartbugs/reentrancy/json', @@ -459,7 +422,7 @@ def compile_bridge(name): extract_and_save_bytecode('./smartbugs/reentrancy/bytecode/ethersolve', './smartbugs/reentrancy/json', True) - + if args.slise: extract_solidity_versions('./slise/reentrancy-db1/source-code', './slise/reentrancy-db1/source-code/version.csv') @@ -468,7 +431,7 @@ def compile_bridge(name): compile_solidity_sources_with_different_version('./slise/reentrancy-db1/source-code', './slise/reentrancy-db1/json', './slise/reentrancy-db1/source-code/version.csv') - + with open('./slise/reentrancy-db1/match-file-index.json', 'r') as index_file: match_file_index = json.load(index_file) @@ -482,10 +445,10 @@ def compile_bridge(name): True, match_file_index) else: - + extract_and_save_bytecode('./slise/reentrancy-db1/bytecode/evmlisa', - './slise/reentrancy-db1/json', - False, + './slise/reentrancy-db1/json', + False, match_file_index) extract_and_save_bytecode('./slise/reentrancy-db1/bytecode/ethersolve', './slise/reentrancy-db1/json', diff --git a/scripts/python/benchmark-checkers/run-benchmark.py b/scripts/python/benchmark-checkers/run-benchmark.py index 809c1d015..0938ef980 100644 --- a/scripts/python/benchmark-checkers/run-benchmark.py +++ b/scripts/python/benchmark-checkers/run-benchmark.py @@ -46,19 +46,19 @@ def delete_files(directory): def clean_files(directory_path): """ Removes lines containing "Picked up _JAVA_OPTIONS" from all files in the specified directory. - + Args: directory_path (str): The path to the directory containing files to clean. """ for filename in os.listdir(directory_path): file_path = os.path.join(directory_path, filename) - + if os.path.isfile(file_path): with open(file_path, 'r') as file: - lines = file.readlines() - + lines = file.readlines() + cleaned_lines = [line for line in lines if "Picked up _JAVA_OPTIONS" not in line] - + if len(cleaned_lines) != len(lines): with open(file_path, 'w') as file: file.writelines(cleaned_lines) @@ -140,7 +140,7 @@ def plot_results_timestamp(data_evmlisa, data_solidifi, name="no-name"): plt.xlabel('Problem ID') plt.ylabel('Value') - plt.title(f'[{name}] Comparison of results (timestamp-dependency)') + plt.title(f'[{name}] Comparison of results (randomness-dependency)') plt.xticks(sorted(set(keys2).union(keys3))) # Show all problem IDs on x-axis plt.legend() plt.grid() @@ -153,18 +153,18 @@ def plot_results_timestamp(data_evmlisa, data_solidifi, name="no-name"): def subtract_dicts(dict1, dict2): result = {} - + for key in dict1: if key in dict2: result[key] = dict1[key] - dict2[key] else: result[key] = dict1[key] - + return result def calculate_average(data): if not data: - return 0 + return 0 total = sum(data.values()) count = len(data) @@ -176,7 +176,7 @@ def map_file_names_to_ids(sorted_data, index_path): """ Map the file names in sorted_data to their corresponding IDs from the index JSON. """ - + with open(index_path, 'r') as index_file: file_index = json.load(index_file) @@ -210,20 +210,20 @@ def build_evmlisa(): def run_evmlisa(bytecode_file, result_evmlisa_dir, type="reentrancy"): """ Runs the EVMLiSA analysis for a given bytecode file. - + Args: bytecode_file (str): The path to the bytecode file. - + Returns: str: The name of the results file. """ bytecode_filename = os.path.basename(bytecode_file) result_filename = f"{os.path.splitext(bytecode_filename)[0]}-result.json" result_filepath = os.path.join(result_evmlisa_dir, result_filename) - + command = ( f"java -jar jars/evm-lisa.jar " - f"--filepath-bytecode {bytecode_file} " + f"--bytecode-path {bytecode_file} " # f"--stack-size 40 " # f"--stack-set-size 15 " # f"--creation-code " @@ -233,7 +233,7 @@ def run_evmlisa(bytecode_file, result_evmlisa_dir, type="reentrancy"): # f"--output {os.path.splitext(bytecode_filename)[0]}" f"> /dev/null 2> {result_filepath}" ) - + try: subprocess.run(command, shell=True, check=True) return result_filepath @@ -247,7 +247,7 @@ def evmlisa(bytecode_dir, results_dir, result_evmlisa_dir, type="reentrancy"): """ delete_files(result_evmlisa_dir) os.makedirs(result_evmlisa_dir, exist_ok=True) - + # Find all bytecode files bytecode_files = [os.path.join(bytecode_dir, f) for f in os.listdir(bytecode_dir) if f.endswith(".bytecode")] num_files = len(bytecode_files) @@ -258,14 +258,14 @@ def evmlisa(bytecode_dir, results_dir, result_evmlisa_dir, type="reentrancy"): # Run analyses in parallel future_to_file = {_executor.submit(run_evmlisa, file, result_evmlisa_dir, type): file for file in bytecode_files} - + with tqdm(total=num_files, desc=f"[EVMLISA] Analyzing bytecode files in '{bytecode_dir}'") as pbar: for future in as_completed(future_to_file): result_file = future.result() if result_file: analysis_ended += 1 pbar.update(1) - + print(f"[EVMLISA] Completed {analysis_ended}/{num_files} from {bytecode_dir}.") delete_tmp_files(results_dir) delete_tmp_files("./execution/results") @@ -274,47 +274,63 @@ def evmlisa(bytecode_dir, results_dir, result_evmlisa_dir, type="reentrancy"): def check_sound_analysis_evmlisa(directory_path): sound = True - + for filename in os.listdir(directory_path): if filename.endswith(".json"): file_path = os.path.join(directory_path, filename) try: with open(file_path, 'r') as file: data = json.load(file) - - if "solved-jumps-percent" in data: - if data["solved-jumps-percent"] != 1 and data["solved-jumps-percent"] != -1: - print(f"[EVMLiSA] {filename} - solved-jumps-percent: {data['solved-jumps-percent']}") - sound = False + + if "statistics" in data: + statistics = data["statistics"] + + if "unsound_jumps" in statistics: + if statistics["unsound_jumps"] != 0: + print(f"[EVMLiSA] {filename} - unsound_jumps: {statistics['unsound_jumps']}") + sound = False + else: + print(f"[EVMLiSA] Warning: 'unsound_jumps' not found in {filename}") + + if "maybe_unsound_jumps" in statistics: + if statistics["maybe_unsound_jumps"] != 0: + print(f"[EVMLiSA] {filename} - maybe_unsound_jumps: {statistics['maybe_unsound_jumps']}") + sound = False + else: + print(f"[EVMLiSA] Warning: 'maybe_unsound_jumps' not found in {filename}") else: - print(f"[EVMLiSA] Warning: 'solved-jumps-percent' not found in {filename}") + print(f"[EVMLiSA] Warning: 'statistics' not found in {filename}") except Exception as e: print(f"[EVMLiSA] ERROR: {filename}: {e}") - + if sound: - print("[EVMLiSA] All analysis are SOUND") + print("[EVMLiSA] All analysis are SOUND.") -def get_results_evmlisa(directory_path, print_data): +def get_results_evmlisa(directory_path, print_data, vulnerability_type): warnings_counts = {} failed = 0 - + for filename in os.listdir(directory_path): if filename.endswith(".json"): file_path = os.path.join(directory_path, filename) try: with open(file_path, 'r') as file: data = json.load(file) - if "re-entrancy-warning" in data: - warnings_counts[filename] = data['re-entrancy-warning'] - elif "tx-origin-warning" in data: - warnings_counts[filename] = data['tx-origin-warning'] - elif "timestamp-dependency-warning" in data: - warnings_counts[filename] = data['timestamp-dependency-warning'] + if "vulnerabilities" in data: + vulnerabilities = data["vulnerabilities"] + if "reentrancy" in vulnerabilities and vulnerability_type == "reentrancy": + warnings_counts[filename] = vulnerabilities['reentrancy'] + elif "tx_origin" in vulnerabilities and vulnerability_type == "tx-origin": + warnings_counts[filename] = vulnerabilities['tx_origin'] + elif "randomness_dependency" in vulnerabilities and vulnerability_type == "randomness-dependency": + warnings_counts[filename] = vulnerabilities['randomness_dependency'] + else: + print(f"[EVMLiSA] Warning: 'reentrancy', 'tx_origin' and 'timestam_dependency' not found in {filename}") else: - print(f"[EVMLiSA] Warning: 're-entrancy-warning', 'tx-origin-warning' and 'timestamp-dependency-warning' not found in {filename}") + print(f"[EVMLiSA] Warning: 'vulnerabilities' not found in {filename}") except Exception as e: failed += 1 - # print(f"[EVMLiSA] ERROR: {filename}: {e}") + # print(f"[EVMLiSA] ERROR: {filename}: {e}") results = defaultdict(int) for file, result in warnings_counts.items(): @@ -323,12 +339,12 @@ def get_results_evmlisa(directory_path, print_data): if match: id = int(match.group(1)) results[id] += result - + match = re.match(r'buggy_(\d+)_(\d+)-\w+\.json', file) if match: id = int(match.group(1)) results[id] += result - + # smartbug & slise case match = re.match(r'(\d+)-\w+\.json', file) if match: @@ -341,7 +357,7 @@ def get_results_evmlisa(directory_path, print_data): results[id] += result sorted_data = dict(sorted(results.items())) - + print(print_data) print(sorted_data) @@ -352,10 +368,10 @@ def get_results_evmlisa(directory_path, print_data): def run_ethersolve(bytecode_file, result_ethersolve_dir): """ Runs the EtherSolve analysis for a given bytecode file. - + Args: bytecode_file (str): The path to the bytecode file. - + Returns: str: The name of the results file. """ @@ -363,17 +379,17 @@ def run_ethersolve(bytecode_file, result_ethersolve_dir): filename = os.path.splitext(bytecode_filename)[0] result_filename = filename + "-result.json" result_filepath = os.path.join(result_ethersolve_dir, result_filename) - + command = ( f"sleep 0.9 && " f"java -jar jars/EtherSolve.jar " f"--re-entrancy " - f"-o {result_filepath} " + f"-o {result_filepath} " f"--creation --json " f"{bytecode_file} 2> /dev/null && " f"mv *re-entrancy.csv {result_ethersolve_dir}/{filename}-reentrancy.csv" ) - + try: subprocess.run(command, shell=True, check=True) return result_filepath @@ -387,7 +403,7 @@ def ethersolve(bytecode_dir, result_ethersolve_dir): """ delete_files(result_ethersolve_dir) os.makedirs(result_ethersolve_dir, exist_ok=True) - + # Find all bytecode files bytecode_files = [os.path.join(bytecode_dir, f) for f in os.listdir(bytecode_dir) if f.endswith(".bytecode")] num_files = len(bytecode_files) @@ -399,7 +415,7 @@ def ethersolve(bytecode_dir, result_ethersolve_dir): # Run analyses in parallel with ThreadPoolExecutor(max_workers=1) as executor: future_to_file = {executor.submit(run_ethersolve, file, result_ethersolve_dir): file for file in bytecode_files} - + with tqdm(total=num_files, desc="[ETHERSOLVE] Analyzing bytecode files") as pbar: for future in as_completed(future_to_file): result_file = future.result() @@ -412,7 +428,7 @@ def ethersolve(bytecode_dir, result_ethersolve_dir): def get_results_ethersolve(directory_path, print_data): """ - Counts occurrences of the word "SSTORE" in files with "reentrancy" in their names + Counts occurrences of the word "SSTORE" in files with "reentrancy" in their names within the specified directory. Args: @@ -426,9 +442,9 @@ def get_results_ethersolve(directory_path, print_data): if os.path.isfile(file_path): with open(file_path, 'r') as file: content = file.read() - + sstore_count = content.count("SSTORE") - + # print(f"{filename}: {sstore_count}") sstore_counts[filename] = sstore_count @@ -439,12 +455,12 @@ def get_results_ethersolve(directory_path, print_data): if match: id = int(match.group(1)) results[id] += result - + match = re.match(r'buggy_(\d+)_(\d+)-\w+\.csv', file) if match: id = int(match.group(1)) results[id] += result - + # smartbugs & slise case match = re.match(r'(\d+)-\w+\.csv', file) if match: @@ -455,12 +471,12 @@ def get_results_ethersolve(directory_path, print_data): if match: id = int(match.group(1)) results[id] += result - + sorted_data = dict(sorted(results.items())) - + print(print_data) print(sorted_data) - + return sorted_data #################################### SolidiFI @@ -477,7 +493,7 @@ def get_results_solidifi(folder_path, type, print_data): 11: 1, 12: 9, 18: 1, 20: 2, 21: 4, 22: 7, 29: 3, 33: 3, 36: 7, 37: 1, 42: 3, 48: 1 } - timestampdependency_subtraction_values = { + randomnessdependency_subtraction_values = { 11: 1, 12: 9, 18: 1, 20: 2, 21: 4, 22: 7, 29: 3, 33: 3, 36: 7, 37: 1, 42: 3, 48: 1 } @@ -489,22 +505,21 @@ def get_results_solidifi(folder_path, type, print_data): # Extract the problem number from the file name problem_id = int(match.group(1)) file_path = os.path.join(folder_path, file_name) - + # Count the number of lines in the file with open(file_path, 'r') as file: num_lines = sum(1 for _ in file) - + # Store the line count in the dictionary if type == 'reentrancy': line_counts[problem_id] = num_lines - 1 - reentrancy_subtraction_values.get(problem_id, 0) elif type == 'tx-origin': line_counts[problem_id] = num_lines - 1 - txorigin_subtraction_values.get(problem_id, 0) - elif type == 'timestamp-dependency': - line_counts[problem_id] = num_lines - 1 - timestampdependency_subtraction_values.get(problem_id, 0) - + elif type == 'randomness-dependency': + line_counts[problem_id] = num_lines - 1 - randomnessdependency_subtraction_values.get(problem_id, 0) - sorted_data = dict(sorted(line_counts.items())) + sorted_data = dict(sorted(line_counts.items())) print(print_data) print(sorted_data) @@ -522,12 +537,12 @@ def get_results_smartbugs(json_path, print_data): # Load JSON data with open(json_path, 'r') as file: data = json.load(file) - + # Iterate over each entry in the JSON data for entry in data: # Extract the filename without the extension file_id = int(os.path.splitext(entry["name"])[0]) - + # Count the vulnerabilities for the file vulnerability_counts[file_id] = len(entry.get("vulnerabilities", [])) @@ -536,7 +551,7 @@ def get_results_smartbugs(json_path, print_data): print(print_data) print(sorted_data) - + return sorted_data #################################### slise @@ -551,35 +566,35 @@ def get_results_slise(json_path, print_data): # Load JSON data with open(json_path, 'r') as file: data = json.load(file) - + # Iterate over each entry in the JSON data for entry in data: # Extract the filename without the extension file_id = str(os.path.splitext(entry["name"])[0]) - + # Count the vulnerabilities for the file vulnerability_counts[file_id] = len(entry.get("vulnerabilities", [])) # Sort the data by file ID sorted_data = dict(sorted(vulnerability_counts.items())) - sorted_data = map_file_names_to_ids(sorted_data, './reentrancy-slise-db1/match-file-index.json') + sorted_data = map_file_names_to_ids(sorted_data, './slise/reentrancy-db1/match-file-index.json') print(print_data) print(sorted_data) - + return sorted_data #################################### Statistics def calculate_precision(data, truth): results_with_precision = {} - + for key, value in data.items(): truth_value = truth.get(key) if truth.get(key) is not None else 0 diff = value - truth_value tp = fp = fn = 0 if diff == 0: # True positive - tp = value + tp = value elif diff > 0: # False positive fp = diff tp = truth_value @@ -593,7 +608,7 @@ def calculate_precision(data, truth): def calculate_recall(data, truth): results_with_recall = {} - + for key, value in data.items(): truth_value = truth.get(key) if truth.get(key) is not None else 0 diff = value - truth_value @@ -610,23 +625,23 @@ def calculate_recall(data, truth): recall = tp / (tp + fn) if (tp + fn) != 0 else 0 results_with_recall[key] = recall - + return results_with_recall def calculate_f_measure(precision, recall): results_with_f_measure = {} - + for key, p in precision.items(): r = recall.get(key) f_measure = 2 * ((p * r) / (p + r)) if (p + r) != 0 else 0 results_with_f_measure[key] = f_measure - + return results_with_f_measure #################################### Main if __name__ == "__main__": - + parser = argparse.ArgumentParser(description="EVMLiSA and EtherSolve analysis.") parser.add_argument("--solidifi", action="store_true", help="Run analysis on SolidiFI dataset") parser.add_argument("--smartbugs", action="store_true", help="Run analysis on SmartBugs dataset") @@ -634,36 +649,36 @@ def calculate_f_measure(precision, recall): parser.add_argument("--no-analysis", action="store_true", help="Do not run the analysis, compute only the results") parser.add_argument("--reentrancy", action="store_true", help="Run analysis on reentrancy contracts") parser.add_argument("--tx-origin", action="store_true", help="Run analysis on tx-origin contracts") - parser.add_argument("--timestamp-dependency", action="store_true", help="Run analysis on timestamp-dependency contracts") + parser.add_argument("--randomness-dependency", action="store_true", help="Run analysis on randomness-dependency contracts") args = parser.parse_args() - + if not args.no_analysis: build_evmlisa() if args.tx_origin: if args.solidifi: if not args.no_analysis: - evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/tx-origin/bytecode/evmlisa', + evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/tx-origin/bytecode/evmlisa', 'results_dir': './solidifi/tx-origin/results', 'result_evmlisa_dir': './solidifi/tx-origin/results/evmlisa', 'type': 'txorigin'}) - evmlisa_vanilla_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/vanilla/bytecode/evmlisa', + evmlisa_vanilla_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/vanilla/bytecode/evmlisa', 'results_dir': './solidifi/vanilla/results', 'result_evmlisa_dir': './solidifi/vanilla/results/evmlisa', 'type': 'txorigin'}) - + evmlisa_vanilla_thread.start() evmlisa_thread.start() evmlisa_thread.join() evmlisa_vanilla_thread.join() check_sound_analysis_evmlisa('./solidifi/tx-origin/results/evmlisa') - + results_solidifi = get_results_solidifi('./solidifi/SolidiFI-buggy-contracts/tx.origin', 'tx-origin', 'solidify') - results_evmlisa = subtract_dicts(get_results_evmlisa('./solidifi/tx-origin/results/evmlisa', 'evmlisa-buggy-solidifi'), - get_results_evmlisa('./solidifi/vanilla/results/evmlisa', 'evmlisa-solidifi/vanilla')) - + results_evmlisa = subtract_dicts(get_results_evmlisa('./solidifi/tx-origin/results/evmlisa', 'evmlisa-buggy-solidifi', 'tx-origin'), + get_results_evmlisa('./solidifi/vanilla/results/evmlisa', 'evmlisa-solidifi/vanilla', 'tx-origin')) + # Precision evmlisa_precision = calculate_precision(results_evmlisa, results_solidifi) print(f"Precision evmlisa (avg.): {calculate_average(evmlisa_precision)}") @@ -674,34 +689,34 @@ def calculate_f_measure(precision, recall): # F-measure print(f"F-measure evmlisa (avg.): {calculate_average(calculate_f_measure(evmlisa_precision, evmlisa_recall))}") - + # Plot results - plot_results_txorigin(data_evmlisa=results_evmlisa, + plot_results_txorigin(data_evmlisa=results_evmlisa, data_solidifi=results_solidifi, name='solidifi_tx-origin') - if args.timestamp_dependency: + if args.randomness_dependency: if args.solidifi: if not args.no_analysis: - evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/timestamp-dependency/bytecode/evmlisa', - 'results_dir': './solidifi/timestamp-dependency/results', - 'result_evmlisa_dir': './solidifi/timestamp-dependency/results/evmlisa', - 'type': 'timestampdependency'}) + evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/randomness-dependency/bytecode/evmlisa', + 'results_dir': './solidifi/randomness-dependency/results', + 'result_evmlisa_dir': './solidifi/randomness-dependency/results/evmlisa', + 'type': 'randomnessdependency'}) evmlisa_vanilla_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/vanilla/bytecode/evmlisa', 'results_dir': './solidifi/vanilla/results', 'result_evmlisa_dir': './solidifi/vanilla/results/evmlisa', - 'type': 'timestampdependency'}) + 'type': 'randomnessdependency'}) evmlisa_vanilla_thread.start() evmlisa_thread.start() evmlisa_thread.join() evmlisa_vanilla_thread.join() - check_sound_analysis_evmlisa('./solidifi/timestamp-dependency/results/evmlisa') + check_sound_analysis_evmlisa('./solidifi/randomness-dependency/results/evmlisa') - results_solidifi = get_results_solidifi('./solidifi/SolidiFI-buggy-contracts/Timestamp-Dependency', 'timestamp-dependency', 'solidify') - results_evmlisa = subtract_dicts(get_results_evmlisa('./solidifi/timestamp-dependency/results/evmlisa', 'evmlisa-buggy-solidifi'), - get_results_evmlisa('./solidifi/vanilla/results/evmlisa', 'evmlisa-solidifi/vanilla')) + results_solidifi = get_results_solidifi('./solidifi/SolidiFI-buggy-contracts/Timestamp-Dependency', 'randomness-dependency', 'solidify') + results_evmlisa = subtract_dicts(get_results_evmlisa('./solidifi/randomness-dependency/results/evmlisa', 'evmlisa-buggy-solidifi', 'randomness-dependency'), + get_results_evmlisa('./solidifi/vanilla/results/evmlisa', 'evmlisa-solidifi/vanilla', 'randomness-dependency')) # Precision evmlisa_precision = calculate_precision(results_evmlisa, results_solidifi) @@ -717,21 +732,21 @@ def calculate_f_measure(precision, recall): # Plot results plot_results_timestamp(data_evmlisa=results_evmlisa, data_solidifi=results_solidifi, - name='solidifi_timestamp-dependency') + name='solidifi_randomness-dependency') if args.smartbugs: - evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './smartbugs/timestamp-dependency/bytecode/evmlisa', - 'results_dir': './smartbugs/timestamp-dependency/results', - 'result_evmlisa_dir': './smartbugs/timestamp-dependency/results/evmlisa', - 'type': 'timestampdependency'}) - + evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './smartbugs/randomness-dependency/bytecode/evmlisa', + 'results_dir': './smartbugs/randomness-dependency/results', + 'result_evmlisa_dir': './smartbugs/randomness-dependency/results/evmlisa', + 'type': 'randomnessdependency'}) + evmlisa_thread.start() evmlisa_thread.join() - check_sound_analysis_evmlisa('./smartbugs/timestamp-dependency/results/evmlisa') + check_sound_analysis_evmlisa('./smartbugs/randomness-dependency/results/evmlisa') + + results_evmlisa = get_results_evmlisa('./smartbugs/randomness-dependency/results/evmlisa', 'evmlisa-buggy-smartbugs', 'randomness-dependency') + results_smartbugs = get_results_smartbugs('./smartbugs/randomness-dependency/source-code/vulnerabilities.json', 'smartbugs') - results_evmlisa = get_results_evmlisa('./smartbugs/timestamp-dependency/results/evmlisa', 'evmlisa-buggy-smartbugs') - results_smartbugs = get_results_smartbugs('./smartbugs/timestamp-dependency/source-code/vulnerabilities.json', 'smartbugs') - # Precision evmlisa_precision = calculate_precision(results_evmlisa, results_smartbugs) print(f"Precision evmlisa (avg.): {calculate_average(evmlisa_precision)}") @@ -742,9 +757,9 @@ def calculate_f_measure(precision, recall): # F-measure print(f"F-measure evmlisa (avg.): {calculate_average(calculate_f_measure(evmlisa_precision, evmlisa_recall))}") - + # Plot results - plot_results_timestamp(data_evmlisa=results_evmlisa, + plot_results_timestamp(data_evmlisa=results_evmlisa, data_solidifi=results_smartbugs, name='smartbugs') @@ -752,22 +767,22 @@ def calculate_f_measure(precision, recall): if args.solidifi: # SolidiFI dataset if not args.no_analysis: - evmlisa_vanilla_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/vanilla/bytecode/evmlisa', + evmlisa_vanilla_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/vanilla/bytecode/evmlisa', 'results_dir': './solidifi/vanilla/results', 'result_evmlisa_dir': './solidifi/vanilla/results/evmlisa'}) - evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/reentrancy/bytecode/evmlisa', + evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/reentrancy/bytecode/evmlisa', 'results_dir': './solidifi/reentrancy/results', 'result_evmlisa_dir': './solidifi/reentrancy/results/evmlisa'}) - + ethersolve_thread = threading.Thread(target=ethersolve, kwargs={'bytecode_dir': './solidifi/reentrancy/bytecode/ethersolve', 'result_ethersolve_dir': './solidifi/reentrancy/results/ethersolve'}) ethersolve_vanilla_thread = threading.Thread(target=ethersolve, kwargs={'bytecode_dir': './solidifi/vanilla/bytecode/ethersolve', 'result_ethersolve_dir': './solidifi/vanilla/results/ethersolve'}) - + evmlisa_vanilla_thread.start() evmlisa_thread.start() ethersolve_thread.start() - + ethersolve_thread.join() evmlisa_vanilla_thread.join() evmlisa_thread.join() @@ -777,13 +792,13 @@ def calculate_f_measure(precision, recall): check_sound_analysis_evmlisa('./solidifi/reentrancy/results/evmlisa') check_sound_analysis_evmlisa('./solidifi/vanilla/results/evmlisa') - - results_evmlisa = subtract_dicts(get_results_evmlisa('./solidifi/reentrancy/results/evmlisa', 'evmlisa-buggy-solidifi'), - get_results_evmlisa('./solidifi/vanilla/results/evmlisa', 'evmlisa-solidifi/vanilla')) + + results_evmlisa = subtract_dicts(get_results_evmlisa('./solidifi/reentrancy/results/evmlisa', 'evmlisa-buggy-solidifi', 'reentrancy'), + get_results_evmlisa('./solidifi/vanilla/results/evmlisa', 'evmlisa-solidifi/vanilla', 'reentrancy')) results_ethersolve = subtract_dicts(get_results_ethersolve('./solidifi/reentrancy/results/ethersolve', 'ethersolve-buggy-solidifi'), get_results_ethersolve('./solidifi/vanilla/results/ethersolve', 'ethersolve-solidifi/vanilla')) results_solidifi = get_results_solidifi('./solidifi/SolidiFI-buggy-contracts/Re-entrancy', 'reentrancy', 'solidify') - + # Precision evmlisa_precision = calculate_precision(results_evmlisa, results_solidifi) ethersolve_precision = calculate_precision(results_ethersolve, results_solidifi) @@ -799,34 +814,34 @@ def calculate_f_measure(precision, recall): # F-measure print(f"F-measure evmlisa (avg.): {calculate_average(calculate_f_measure(evmlisa_precision, evmlisa_recall))}") print(f"F-measure ethersolve (avg.): {calculate_average(calculate_f_measure(ethersolve_precision, ethersolve_recall))}") - + # Plot results - plot_results(data_evmlisa=results_evmlisa, + plot_results(data_evmlisa=results_evmlisa, data_ethersolve=results_ethersolve, data_solidifi=results_solidifi, name='solidifi') - + if args.smartbugs: # SmartBugs dataset if not args.no_analysis: - evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './reentrancy-smartbugs/bytecode/evmlisa', - 'results_dir': './reentrancy-smartbugs/results', - 'result_evmlisa_dir': './reentrancy-smartbugs/results/evmlisa'}) - ethersolve_thread = threading.Thread(target=ethersolve, kwargs={'bytecode_dir': './reentrancy-smartbugs/bytecode/ethersolve', - 'result_ethersolve_dir': './reentrancy-smartbugs/results/ethersolve'}) - + evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './smartbugs/reentrancy/bytecode/evmlisa', + 'results_dir': './smartbugs/reentrancy/results', + 'result_evmlisa_dir': './smartbugs/reentrancy/results/evmlisa'}) + ethersolve_thread = threading.Thread(target=ethersolve, kwargs={'bytecode_dir': './smartbugs/reentrancy/bytecode/ethersolve', + 'result_ethersolve_dir': './smartbugs/reentrancy/results/ethersolve'}) + evmlisa_thread.start() ethersolve_thread.start() - + ethersolve_thread.join() evmlisa_thread.join() - check_sound_analysis_evmlisa('./reentrancy-smartbugs/results/evmlisa') + check_sound_analysis_evmlisa('./smartbugs/reentrancy/results/evmlisa') + + results_evmlisa = get_results_evmlisa('./smartbugs/reentrancy/results/evmlisa', 'evmlisa-buggy-smartbugs', 'reentrancy') + results_ethersolve = get_results_ethersolve('./smartbugs/reentrancy/results/ethersolve', 'ethersolve-buggy-smartbugs') + results_smartbugs = get_results_smartbugs('./smartbugs/reentrancy/source-code/vulnerabilities.json', 'smartbugs') - results_evmlisa = get_results_evmlisa('./reentrancy-smartbugs/results/evmlisa', 'evmlisa-buggy-smartbugs') - results_ethersolve = get_results_ethersolve('./reentrancy-smartbugs/results/ethersolve', 'ethersolve-buggy-smartbugs') - results_smartbugs = get_results_smartbugs('./reentrancy-smartbugs/source-code/vulnerabilities.json', 'smartbugs') - # Precision evmlisa_precision = calculate_precision(results_evmlisa, results_smartbugs) ethersolve_precision = calculate_precision(results_ethersolve, results_smartbugs) @@ -842,34 +857,34 @@ def calculate_f_measure(precision, recall): # F-measure print(f"F-measure evmlisa (avg.): {calculate_average(calculate_f_measure(evmlisa_precision, evmlisa_recall))}") print(f"F-measure ethersolve (avg.): {calculate_average(calculate_f_measure(ethersolve_precision, ethersolve_recall))}") - + # Plot results - plot_results(data_evmlisa=results_evmlisa, + plot_results(data_evmlisa=results_evmlisa, data_ethersolve=results_ethersolve, data_solidifi=results_smartbugs, name='smartbugs') - + if args.slise: # SliSE dataset if not args.no_analysis: - evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './reentrancy-slise-db1/bytecode/evmlisa', - 'results_dir': './reentrancy-slise-db1/results', - 'result_evmlisa_dir': './reentrancy-slise-db1/results/evmlisa'}) - ethersolve_thread = threading.Thread(target=ethersolve, kwargs={'bytecode_dir': './reentrancy-slise-db1/bytecode/ethersolve', - 'result_ethersolve_dir': './reentrancy-slise-db1/results/ethersolve'}) - + evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './slise/reentrancy-db1/bytecode/evmlisa', + 'results_dir': './slise/reentrancy-db1/results', + 'result_evmlisa_dir': './slise/reentrancy-db1/results/evmlisa'}) + ethersolve_thread = threading.Thread(target=ethersolve, kwargs={'bytecode_dir': './slise/reentrancy-db1/bytecode/ethersolve', + 'result_ethersolve_dir': './slise/reentrancy-db1/results/ethersolve'}) + evmlisa_thread.start() ethersolve_thread.start() - + ethersolve_thread.join() evmlisa_thread.join() - check_sound_analysis_evmlisa('./reentrancy-slise-db1/results/evmlisa') + check_sound_analysis_evmlisa('./slise/reentrancy-db1/results/evmlisa') + + results_evmlisa = get_results_evmlisa('./slise/reentrancy-db1/results/evmlisa', 'evmlisa-buggy-slise-db1', 'reentrancy') + results_ethersolve = get_results_ethersolve('./slise/reentrancy-db1/results/ethersolve', 'ethersolve-buggy-slise-db1') + results_slise = get_results_slise('./slise/reentrancy-db1/source-code/vulnerabilities.json', 'slise-db1') - results_evmlisa = get_results_evmlisa('./reentrancy-slise-db1/results/evmlisa', 'evmlisa-buggy-slise-db1') - results_ethersolve = get_results_ethersolve('./reentrancy-slise-db1/results/ethersolve', 'ethersolve-buggy-slise-db1') - results_slise = get_results_slise('./reentrancy-slise-db1/source-code/vulnerabilities.json', 'slise-db1') - # Precision evmlisa_precision = calculate_precision(results_evmlisa, results_slise) ethersolve_precision = calculate_precision(results_ethersolve, results_slise) @@ -885,12 +900,12 @@ def calculate_f_measure(precision, recall): # F-measure print(f"F-measure evmlisa (avg.): {calculate_average(calculate_f_measure(evmlisa_precision, evmlisa_recall))}") print(f"F-measure ethersolve (avg.): {calculate_average(calculate_f_measure(ethersolve_precision, ethersolve_recall))}") - + # Plot results - plot_results(data_evmlisa=results_evmlisa, + plot_results(data_evmlisa=results_evmlisa, data_ethersolve=results_ethersolve, data_solidifi=results_slise, name='slise') - + # Shut down the global executor after all analyses are complete shutdown_executor() \ No newline at end of file diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/1.sol b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/1.sol similarity index 100% rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/1.sol rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/1.sol diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/2.sol b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/2.sol similarity index 100% rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/2.sol rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/2.sol diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/3.sol b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/3.sol similarity index 100% rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/3.sol rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/3.sol diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/4.sol b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/4.sol similarity index 100% rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/4.sol rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/4.sol diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/5.sol b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/5.sol similarity index 100% rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/5.sol rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/5.sol diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/version.csv b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/version.csv similarity index 100% rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/version.csv rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/version.csv diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/vulnerabilities.json b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/vulnerabilities.json similarity index 100% rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/vulnerabilities.json rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/vulnerabilities.json diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_1.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_1.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_1.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_1.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_10.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_10.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_10.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_10.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_11.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_11.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_11.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_11.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_12.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_12.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_12.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_12.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_13.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_13.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_13.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_13.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_14.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_14.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_14.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_14.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_15.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_15.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_15.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_15.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_16.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_16.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_16.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_16.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_17.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_17.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_17.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_17.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_18.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_18.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_18.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_18.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_19.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_19.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_19.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_19.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_2.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_2.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_2.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_2.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_20.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_20.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_20.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_20.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_21.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_21.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_21.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_21.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_22.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_22.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_22.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_22.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_23.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_23.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_23.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_23.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_24.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_24.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_24.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_24.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_25.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_25.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_25.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_25.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_26.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_26.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_26.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_26.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_27.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_27.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_27.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_27.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_28.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_28.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_28.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_28.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_29.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_29.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_29.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_29.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_3.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_3.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_3.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_3.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_30.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_30.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_30.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_30.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_31.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_31.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_31.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_31.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_32.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_32.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_32.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_32.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_33.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_33.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_33.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_33.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_34.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_34.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_34.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_34.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_35.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_35.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_35.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_35.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_36.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_36.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_36.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_36.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_37.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_37.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_37.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_37.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_38.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_38.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_38.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_38.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_39.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_39.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_39.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_39.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_4.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_4.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_4.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_4.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_40.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_40.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_40.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_40.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_41.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_41.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_41.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_41.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_42.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_42.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_42.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_42.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_43.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_43.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_43.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_43.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_44.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_44.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_44.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_44.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_45.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_45.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_45.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_45.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_46.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_46.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_46.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_46.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_47.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_47.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_47.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_47.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_48.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_48.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_48.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_48.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_49.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_49.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_49.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_49.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_5.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_5.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_5.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_5.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_50.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_50.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_50.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_50.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_6.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_6.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_6.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_6.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_7.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_7.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_7.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_7.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_8.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_8.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_8.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_8.sol diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_9.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_9.sol similarity index 100% rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_9.sol rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_9.sol diff --git a/src/main/java/it/unipr/EVMLiSA.java b/src/main/java/it/unipr/EVMLiSA.java index c76aa3283..befbfef57 100644 --- a/src/main/java/it/unipr/EVMLiSA.java +++ b/src/main/java/it/unipr/EVMLiSA.java @@ -2,16 +2,18 @@ import it.unipr.analysis.*; import it.unipr.analysis.contract.SmartContract; -import it.unipr.analysis.taint.TimestampDependencyAbstractDomain; +import it.unipr.analysis.taint.RandomnessDependencyAbstractDomain; import it.unipr.analysis.taint.TxOriginAbstractDomain; import it.unipr.cfg.EVMCFG; import it.unipr.cfg.Jump; import it.unipr.cfg.Jumpi; import it.unipr.checker.JumpSolver; +import it.unipr.checker.RandomnessDependencyChecker; import it.unipr.checker.ReentrancyChecker; -import it.unipr.checker.TimestampDependencyChecker; import it.unipr.checker.TxOriginChecker; import it.unipr.frontend.EVMFrontend; +import it.unipr.frontend.EVMLiSAFeatures; +import it.unipr.frontend.EVMLiSATypeSystem; import it.unipr.utils.*; import it.unive.lisa.LiSA; import it.unive.lisa.analysis.SimpleAbstractState; @@ -45,7 +47,6 @@ public class EVMLiSA { private static final Logger log = LogManager.getLogger(EVMLiSA.class); // Configuration - private static int CORES = 1; private static boolean TEST_MODE = false; private static Path OUTPUT_DIRECTORY_PATH; @@ -86,15 +87,21 @@ public static void setWorkingDirectory(Path workingDirectoryPath) { SmartContract.setWorkingDirectory(workingDirectoryPath); } + public static Path getWorkingDirectory() { + return EVMLiSA.OUTPUT_DIRECTORY_PATH; + } + + public static int getCores() { + return EVMLiSAExecutor.getCoresAvailable(); + } + /** * Sets the number of processing cores. * * @param cores the number of cores */ public static void setCores(int cores) { - if (cores > Runtime.getRuntime().availableProcessors()) - cores = Runtime.getRuntime().availableProcessors() - 1; - EVMLiSA.CORES = Math.max(cores, 1); + EVMLiSAExecutor.setCoresAvailable(cores); } /** @@ -105,11 +112,12 @@ public static void setLinkUnsoundJumpsToAllJumpdest() { } /** - * Enables all security checkers. + * Enables all security checkers (i.e., reentrancy, randomness dependency, + * tx.origin). */ public static void enableAllSecurityCheckers() { EVMLiSA.enableReentrancyChecker(); - EVMLiSA.enableTimestampDependencyCheckerChecker(); + EVMLiSA.enableRandomnessDependencyChecker(); EVMLiSA.enableTxOriginChecker(); } @@ -121,10 +129,10 @@ public static void enableReentrancyChecker() { } /** - * Enables the timestamp dependency checker. + * Enables the randomness dependency checker. */ - public static void enableTimestampDependencyCheckerChecker() { - TimestampDependencyChecker.enableChecker(); + public static void enableRandomnessDependencyChecker() { + RandomnessDependencyChecker.enableChecker(); } /** @@ -141,6 +149,10 @@ public static void setTestMode() { TEST_MODE = true; } + public static boolean isInTestMode() { + return TEST_MODE; + } + /** * Executes the analysis workflow. * @@ -196,11 +208,13 @@ else if (cmd.hasOption("bytecode")) { else { JSONManager.throwNewError("No valid option provided."); + new HelpFormatter().printHelp("help", getOptions()); System.exit(1); } EVMLiSA.analyzeContract(contract); System.err.println(contract); + EVMLiSAExecutor.shutdown(); } /** @@ -209,7 +223,7 @@ else if (cmd.hasOption("bytecode")) { * @param filePath the path to the file containing contract addresses */ public static void analyzeSetOfContracts(Path filePath) { - log.info("Building contracts..."); + log.info("Building contracts."); List contracts = buildContractsFromFile(filePath); analyzeSetOfContracts(contracts); } @@ -220,19 +234,18 @@ public static void analyzeSetOfContracts(Path filePath) { * @param contracts the list of {@link SmartContract} to be analyzed */ public static void analyzeSetOfContracts(List contracts) { - log.info("Analyzing {} contracts...", contracts.size()); + log.info("Analyzing {} contracts.", contracts.size()); - ExecutorService executor = Executors.newFixedThreadPool(CORES); List> futures = new ArrayList<>(); for (SmartContract contract : contracts) - futures.add(executor.submit(() -> analyzeContract(contract))); + futures.add(EVMLiSAExecutor.submit(() -> analyzeContract(contract))); - log.info("{} contracts submitted to Thread pool with {} workers.", contracts.size(), CORES); + log.debug("{} contracts submitted to Thread pool with {} workers.", contracts.size(), + EVMLiSAExecutor.getCoresAvailable()); - waitForCompletion(futures); + EVMLiSAExecutor.awaitCompletionFutures(futures); - executor.shutdown(); log.info("Finished analysis of {} contracts.", contracts.size()); Path outputDir = OUTPUT_DIRECTORY_PATH.resolve("set-of-contracts"); @@ -247,22 +260,29 @@ public static void analyzeSetOfContracts(List contracts) { System.err.println(JSONManager.throwNewError("Failed to save results in " + outputDir)); System.exit(1); } + EVMLiSAExecutor.shutdown(); } /** - * Analyzes a given smart contract. + * Builds the Control Flow Graph (CFG) for the given smart contract. This + * method generates the CFG from the contract's mnemonic bytecode, + * configures and runs the LiSA analysis, and stores the computed CFG and + * statistics in the contract object. * - * @param contract the smart contract to analyze + * @param contract the smart contract for which the CFG is to be built */ - public static void analyzeContract(SmartContract contract) { - log.info("Analyzing contract {}...", contract.getAddress()); + public static void buildCFG(SmartContract contract) { + if (contract == null) + return; + + log.info("[IN] Building CFG of contract {}.", contract.getName()); Program program = null; try { program = EVMFrontend.generateCfgFromFile(contract.getMnemonicBytecodePath().toString()); } catch (IOException e) { System.err.println( - JSONManager.throwNewError("Unable to generate partial CFG from file", contract.toJson())); + JSONManager.throwNewError("Unable to generate partial CFG from file.", contract.toJson())); System.exit(1); } @@ -273,62 +293,156 @@ public static void analyzeContract(SmartContract contract) { conf.semanticChecks.add(checker); LiSA lisa = new LiSA(conf); - lisa.run(program); - log.info("Analysis ended {}", contract.getAddress()); + long startTime = System.currentTimeMillis(); + lisa.run(program); + contract.setExecutionTime(System.currentTimeMillis() - startTime); + log.info("[OUT] CFG of contract {} built.", contract.getName()); + log.info("[IN] Computing statistics of contract {}.", contract.getName()); contract.setStatistics( computeStatistics(checker, lisa, program)); contract.setCFG(checker.getComputedCFG()); + log.debug("[OUT] Contract {} statistics: {}", contract.getAddress(), contract.getStatistics()); + if (TEST_MODE) return; + log.info("[IN] Computing functions and events of contract {}.", contract.getName()); contract.computeFunctionsSignatureEntryPoints(); contract.computeFunctionsSignatureExitPoints(); contract.computeEventsSignatureEntryPoints(); contract.computeEventsExitPoints(); + log.info("[OUT] Functions and events of contract {} computed.", contract.getName()); + } - if (ReentrancyChecker.isEnabled()) { - log.info("Running reentrancy checker..."); - conf.semanticChecks.clear(); - conf.semanticChecks.add(new ReentrancyChecker()); - lisa.run(program); - log.info("{} vulnerabilities found", - MyCache.getInstance().getReentrancyWarnings(checker.getComputedCFG().hashCode())); - } - if (TxOriginChecker.isEnabled()) { - log.info("Running tx. origin checker..."); - conf.semanticChecks.clear(); - conf.semanticChecks.add(new TxOriginChecker()); - conf.abstractState = new SimpleAbstractState<>(new MonolithicHeap(), new TxOriginAbstractDomain(), - new TypeEnvironment<>(new InferredTypes())); - lisa.run(program); - log.info("{} vulnerabilities found", - MyCache.getInstance().getTxOriginWarnings(checker.getComputedCFG().hashCode())); - } - if (TimestampDependencyChecker.isEnabled()) { - log.info("Running timestamp dependency checker..."); - conf.semanticChecks.clear(); - conf.semanticChecks.add(new TimestampDependencyChecker()); - conf.abstractState = new SimpleAbstractState<>(new MonolithicHeap(), - new TimestampDependencyAbstractDomain(), - new TypeEnvironment<>(new InferredTypes())); - lisa.run(program); - log.info("{} vulnerabilities found", - MyCache.getInstance().getTimestampDependencyWarnings(checker.getComputedCFG().hashCode())); - } + /** + * Runs all enabled security checkers on the given smart contract. This + * method executes various security checkers (e.g., Reentrancy, TxOrigin, + * and Randomness Dependency checkers) on the computed CFG of the contract. + * It then stores the detected vulnerabilities in the contract object. + * + * @param contract the smart contract on which security checkers are + * executed + */ + public static void runCheckers(SmartContract contract) { + if (contract == null || contract.getCFG() == null) + return; + + log.info("[IN] Running checkers on contract {}.", contract.getName()); + + if (ReentrancyChecker.isEnabled()) + runReentrancyChecker(contract); + if (TxOriginChecker.isEnabled()) + runTxOriginChecker(contract); + if (RandomnessDependencyChecker.isEnabled()) + runRandomnessDependencyChecker(contract); contract.setVulnerabilities( - VulnerabilitiesObject.newVulnerabilitiesObject() - .reentrancy( - MyCache.getInstance().getReentrancyWarnings(checker.getComputedCFG().hashCode())) - .txOrigin(MyCache.getInstance().getTxOriginWarnings(checker.getComputedCFG().hashCode())) - .timestamp(MyCache.getInstance() - .getTimestampDependencyWarnings(checker.getComputedCFG().hashCode())) - .build()); - contract.generateCFGWithBasicBlocks(); - contract.toFile(); + VulnerabilitiesObject.buildFromCFG( + contract.getCFG())); + + log.info("[OUT] Checkers run on contract {}.", contract.getName()); + } + + /** + * Analyzes a given smart contract. + * + * @param contract the smart contract to analyze + */ + public static void analyzeContract(SmartContract contract) { + log.info("[IN] Analyzing contract {}.", contract.getName()); + + buildCFG(contract); + + if (!TEST_MODE) { + runCheckers(contract); + contract.generateCFGWithBasicBlocks(); + contract.toFile(); + } + + log.info("[OUT] Analysis ended of contract {}.", contract.getName()); + } + + /** + * Runs the reentrancy checker on the given smart contract. + * + * @param contract The smart contract to analyze. + */ + public static void runReentrancyChecker(SmartContract contract) { + log.info("[IN] Running reentrancy checker on {}.", contract.getName()); + + // Setup configuration + Program program = new Program(new EVMLiSAFeatures(), new EVMLiSATypeSystem()); + program.addCodeMember(contract.getCFG()); + LiSAConfiguration conf = LiSAConfigurationManager.createConfiguration(contract); + LiSA lisa = new LiSA(conf); + + // Reentrancy checker + ReentrancyChecker checker = new ReentrancyChecker(); + conf.semanticChecks.add(checker); + lisa.run(program); + + log.info("[OUT] Reentrancy checker ended on {}, with {} vulnerabilities found.", + contract.getName(), + MyCache.getInstance().getReentrancyWarnings(contract.getCFG().hashCode())); + } + + /** + * Runs the randomness dependency checker on the given smart contract. + * + * @param contract The smart contract to analyze. + */ + public static void runRandomnessDependencyChecker(SmartContract contract) { + log.info("[IN] Running randomness dependency checker on {}.", contract.getName()); + + // Setup configuration + Program program = new Program(new EVMLiSAFeatures(), new EVMLiSATypeSystem()); + program.addCodeMember(contract.getCFG()); + LiSAConfiguration conf = LiSAConfigurationManager.createConfiguration(contract); + LiSA lisa = new LiSA(conf); + + // Randomness dependency checker + RandomnessDependencyChecker checker = new RandomnessDependencyChecker(); + conf.semanticChecks.add(checker); + conf.abstractState = new SimpleAbstractState<>(new MonolithicHeap(), + new RandomnessDependencyAbstractDomain(), + new TypeEnvironment<>(new InferredTypes())); + lisa.run(program); + + log.info( + "[OUT] Randomness dependency checker ended on {}, with {} definite and {} possible vulnerabilities found.", + contract.getName(), + MyCache.getInstance().getRandomnessDependencyWarnings(contract.getCFG().hashCode()), + MyCache.getInstance().getPossibleRandomnessDependencyWarnings(contract.getCFG().hashCode())); + } + + /** + * Runs the tx. origin checker on the given smart contract. + * + * @param contract The smart contract to analyze. + */ + public static void runTxOriginChecker(SmartContract contract) { + log.info("[IN] Running tx. origin checker on {}.", contract.getName()); + + // Setup configuration + Program program = new Program(new EVMLiSAFeatures(), new EVMLiSATypeSystem()); + program.addCodeMember(contract.getCFG()); + LiSAConfiguration conf = LiSAConfigurationManager.createConfiguration(contract); + LiSA lisa = new LiSA(conf); + + // Tx. Origin checker + TxOriginChecker checker = new TxOriginChecker(); + conf.semanticChecks.add(checker); + conf.abstractState = new SimpleAbstractState<>(new MonolithicHeap(), + new TxOriginAbstractDomain(), + new TypeEnvironment<>(new InferredTypes())); + lisa.run(program); + + log.info("[OUT] Tx. origin checker ended on {}, with {} vulnerabilities found.", + contract.getName(), + MyCache.getInstance().getTxOriginWarnings(contract.getCFG().hashCode())); } /** @@ -448,8 +562,6 @@ private static StatisticsObject computeJumps(JumpSolver checker, Set .maybeUnsoundJumps(maybeUnsoundJumps) .build(); - log.info("### Calculating statistics ###\n{}", stats); - return stats; } @@ -529,18 +641,19 @@ public static List buildContractsFromFile(Path filePath) { private void setupGlobalOptions(CommandLine cmd) { try { - CORES = cmd.hasOption("cores") ? Integer.parseInt(cmd.getOptionValue("cores")) : 1; + EVMLiSAExecutor + .setCoresAvailable(cmd.hasOption("cores") ? Integer.parseInt(cmd.getOptionValue("cores")) : 1); } catch (Exception e) { log.warn("Cores set to 1: {}", e.getMessage()); - CORES = 1; + EVMLiSAExecutor.setCoresAvailable(1); } if (cmd.hasOption("checker-reentrancy") || cmd.hasOption("checker-all")) ReentrancyChecker.enableChecker(); if (cmd.hasOption("checker-txorigin") || cmd.hasOption("checker-all")) TxOriginChecker.enableChecker(); - if (cmd.hasOption("checker-timestampdependency") || cmd.hasOption("checker-all")) - TimestampDependencyChecker.enableChecker(); + if (cmd.hasOption("checker-randomnessdependency") || cmd.hasOption("checker-all")) + RandomnessDependencyChecker.enableChecker(); if (cmd.hasOption("output-directory-path")) OUTPUT_DIRECTORY_PATH = Path.of(cmd.getOptionValue("output-directory-path")); @@ -565,6 +678,8 @@ private void setupGlobalOptions(CommandLine cmd) { EVMAbstractState.setUseStorageLive(); if (cmd.hasOption("etherscan-api-key")) EVMFrontend.setEtherscanAPIKey(cmd.getOptionValue("etherscan-api-key")); + if (cmd.hasOption("test-mode")) + EVMLiSA.setTestMode(); } private Options getOptions() { @@ -605,6 +720,7 @@ private Options getOptions() { .required(false) .hasArg(true) .build(); + Option abiOption = Option.builder() .longOpt("abi") .desc("ABI of the bytecode to be analyzed (JSON format).") @@ -675,9 +791,9 @@ private Options getOptions() { .hasArg(false) .build(); - Option enableTimestampDependencyCheckerOption = Option.builder() - .longOpt("checker-timestampdependency") - .desc("Enable timestamp-dependency checker.") + Option enableRandomnessDependencyCheckerOption = Option.builder() + .longOpt("checker-randomnessdependency") + .desc("Enable randomness-dependency checker.") .required(false) .hasArg(false) .build(); @@ -689,6 +805,13 @@ private Options getOptions() { .hasArg(true) .build(); + Option useTestModeOption = Option.builder() + .longOpt("test-mode") + .desc("Use the test mode (i.e., do not compute functions and events).") + .required(false) + .hasArg(false) + .build(); + options.addOption(addressOption); options.addOption(bytecodeOption); options.addOption(bytecodePathOption); @@ -702,10 +825,11 @@ private Options getOptions() { options.addOption(enableAllCheckerOption); options.addOption(enableReentrancyCheckerOption); options.addOption(enableTxOriginCheckerOption); - options.addOption(enableTimestampDependencyCheckerOption); + options.addOption(enableRandomnessDependencyCheckerOption); options.addOption(outputDirectoryPathOption); options.addOption(etherscanAPIKeyOption); options.addOption(abiOption); + options.addOption(useTestModeOption); return options; } @@ -724,23 +848,4 @@ private CommandLine parseCommandLine(String[] args) { return null; } } - - /** - * Waits for all submitted tasks in the given list of futures to complete. - * - * @param futures A list of {@link Future} objects representing running - * tasks. - */ - static private void waitForCompletion(List> futures) { - for (Future future : futures) - try { - future.get(); - } catch (ExecutionException e) { - System.err.println(JSONManager.throwNewError("Error during task execution: " + e.getMessage())); - System.exit(1); - } catch (InterruptedException ie) { - System.err.println(JSONManager.throwNewError("Interrupted during task execution: " + ie.getMessage())); - System.exit(1); - } - } } diff --git a/src/main/java/it/unipr/analysis/EVMAbstractState.java b/src/main/java/it/unipr/analysis/EVMAbstractState.java index 8619201b8..50fbaad45 100644 --- a/src/main/java/it/unipr/analysis/EVMAbstractState.java +++ b/src/main/java/it/unipr/analysis/EVMAbstractState.java @@ -975,8 +975,13 @@ public EVMAbstractState smallStepSemantics(ValueExpression expression, ProgramPo } else if (memory.isTop()) { resultStack.push(StackElement.TOP); } else { - byte[] mloadValue = memory.mload(offset.getNumber().intValue()); + BigInteger os = Number.toBigInteger(offset.getNumber()); + if (os.compareTo(BigInteger.ZERO) < 0 + || os.compareTo(Number.MAX_INT) > 0) + continue; + + byte[] mloadValue = memory.mload(offset.getNumber().intValue()); StackElement mload = StackElement.fromBytes(mloadValue); if (mload.isBottom()) @@ -1009,6 +1014,12 @@ public EVMAbstractState smallStepSemantics(ValueExpression expression, ProgramPo } else if (memory.isTop()) { memoryResult = AbstractMemory.TOP; } else { + + BigInteger os = Number.toBigInteger(offset.getNumber()); + if (os.compareTo(BigInteger.ZERO) < 0 + || os.compareTo(Number.MAX_INT) > 0) + continue; + byte[] valueBytes = convertStackElementToBytes(value); memoryResult = memoryResult.lub(memory.mstore(offset.getNumber().intValue(), valueBytes)); } @@ -1035,10 +1046,15 @@ public EVMAbstractState smallStepSemantics(ValueExpression expression, ProgramPo memoryResult = AbstractMemory.TOP; } else if (memory.isTop()) { memoryResult = AbstractMemory.TOP; - } else + } else { + BigInteger os = Number.toBigInteger(offset.getNumber()); + if (os.compareTo(BigInteger.ZERO) < 0 + || os.compareTo(Number.MAX_INT) > 0) + continue; + memoryResult = memoryResult.lub( memory.mstore8(offset.getNumber().intValue(), (byte) value.getNumber().intValue())); - + } result.add(stackResult); } diff --git a/src/main/java/it/unipr/analysis/Number.java b/src/main/java/it/unipr/analysis/Number.java index b89bd922c..9eeb6f3d9 100644 --- a/src/main/java/it/unipr/analysis/Number.java +++ b/src/main/java/it/unipr/analysis/Number.java @@ -15,14 +15,14 @@ public class Number implements Comparable { /** * Maximal representable integer value. */ - private static final BigInteger MAX_INT = BigInteger.valueOf(2).pow(31); + public static final BigInteger MAX_INT = BigInteger.valueOf(2).pow(31); /** * Maximal representable long value. */ - private static final BigInteger MAX_LONG = BigInteger.valueOf(2).pow(63); + public static final BigInteger MAX_LONG = BigInteger.valueOf(2).pow(63); - private static enum Type { + public enum Type { INT, LONG, BIGINTEGER diff --git a/src/main/java/it/unipr/analysis/contract/EventKnowledge.java b/src/main/java/it/unipr/analysis/contract/EventKnowledge.java deleted file mode 100644 index 380efc584..000000000 --- a/src/main/java/it/unipr/analysis/contract/EventKnowledge.java +++ /dev/null @@ -1,44 +0,0 @@ -package it.unipr.analysis.contract; - -import java.util.Arrays; -import java.util.List; - -public class EventKnowledge { - public enum EventType { - DEPOSIT, WITHDRAWAL, UNKNOWN - } - - public static final List DEPOSIT_KNOWLEDGE = Arrays.asList( - "Deposit", "Send", "LiFiTransferStarted", "LogAnySwapTradeTokensForTokens", - "TokenDeposit", "TokenDepositAndSwap", "LockApplied", "CrossChainEvent", - "TransferAllowance", "Sent"); - - public static final List WITHDRAWAL_KNOWLEDGE = Arrays.asList( - "Transfer", "ProposalEvent", "Approval", "Authorize", "LiFiTransferCompleted", - "LogWithdraw", "ProposalEvent", "ProposalVote", "LogAnySwapTradeTokensForNative", - "Withdraw", "EmergencyWithdraw", "Harvest", "LockRemoved", - "VerifyHeaderAndExecuteTxEvent", "TransferOut", "VaultTransfer"); - - public static EventType getKnowledge(String signature) { - signature = signature.toLowerCase(); - - for (String keyword : DEPOSIT_KNOWLEDGE) { - if (signature.contains(keyword.toLowerCase())) { - return EventType.DEPOSIT; - } - } - - for (String keyword : WITHDRAWAL_KNOWLEDGE) { - if (signature.contains(keyword.toLowerCase())) { - return EventType.WITHDRAWAL; - } - } - - return EventType.UNKNOWN; - } - - public static void main(String[] args) { - System.out.println("Deposit Knowledge: " + DEPOSIT_KNOWLEDGE); - System.out.println("Withdrawal Knowledge: " + WITHDRAWAL_KNOWLEDGE); - } -} diff --git a/src/main/java/it/unipr/analysis/contract/EventsExitPointsComputer.java b/src/main/java/it/unipr/analysis/contract/EventsExitPointsComputer.java deleted file mode 100644 index cbca8bf07..000000000 --- a/src/main/java/it/unipr/analysis/contract/EventsExitPointsComputer.java +++ /dev/null @@ -1,151 +0,0 @@ -package it.unipr.analysis.contract; - -import it.unipr.analysis.*; -import it.unipr.cfg.*; -import it.unipr.utils.MyCache; -import it.unive.lisa.analysis.AnalysisState; -import it.unive.lisa.analysis.AnalyzedCFG; -import it.unive.lisa.analysis.SemanticException; -import it.unive.lisa.analysis.SimpleAbstractState; -import it.unive.lisa.analysis.heap.MonolithicHeap; -import it.unive.lisa.analysis.nonrelational.value.TypeEnvironment; -import it.unive.lisa.analysis.types.InferredTypes; -import it.unive.lisa.checks.semantic.CheckToolWithAnalysisResults; -import it.unive.lisa.checks.semantic.SemanticCheck; -import it.unive.lisa.program.cfg.CFG; -import it.unive.lisa.program.cfg.statement.Statement; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class EventsExitPointsComputer implements - SemanticCheck>> { - - private static final Logger log = LogManager.getLogger(EventsExitPointsComputer.class); - - @Override - public boolean visit( - CheckToolWithAnalysisResults< - SimpleAbstractState>> tool, - CFG graph, Statement node) { - - boolean isLog = (node instanceof Log1) || (node instanceof Log2) || (node instanceof Log3) - || (node instanceof Log4); - - if (isLog) { - EVMCFG cfg = ((EVMCFG) graph); - Statement logStatement = node; - - for (AnalyzedCFG>> result : tool.getResultOf(cfg)) { - AnalysisState>> analysisResult = null; - - try { - analysisResult = result.getAnalysisStateBefore(logStatement); - } catch (SemanticException e1) { - log.error("(EventsExitPointsComputer): {}", e1.getMessage()); - } - - // Retrieve the symbolic stack from the analysis result - EVMAbstractState valueState = analysisResult.getState().getValueState(); - - // If the value state is bottom, the jump is definitely - // unreachable - if (valueState.isBottom()) - // Nothing to do - continue; - else if (valueState.isTop()) - continue; - else { - checkForEventSignature(node, valueState.getStacks()); - } - } - - } - - return true; - } - - /** - * Extracts and stores event signatures from the EVM stack based on LOG - * instructions. Depending on the LOG opcode type (LOG1, LOG2, LOG3, LOG4), - * it retrieves the corresponding topics from the stack and stores their - * first 4 bytes as event exit points. - * - * @param node The statement representing the LOG instruction. - * @param stacks The abstract stack set containing stack states to analyze. - */ - private void checkForEventSignature(Statement node, AbstractStackSet stacks) { - for (AbstractStack originalStack : stacks) { - AbstractStack stack = originalStack.clone(); - - stack.pop(); // offset - stack.pop(); // size - - if (node instanceof Log1) { - StackElement topic = stack.pop(); - MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic)); - } else if (node instanceof Log2) { - StackElement topic1 = stack.pop(); - StackElement topic2 = stack.pop(); - MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic1)); - MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic2)); - } else if (node instanceof Log3) { - StackElement topic1 = stack.pop(); - StackElement topic2 = stack.pop(); - StackElement topic3 = stack.pop(); - MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic1)); - MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic2)); - MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic3)); - } else if (node instanceof Log4) { - StackElement topic1 = stack.pop(); - StackElement topic2 = stack.pop(); - StackElement topic3 = stack.pop(); - StackElement topic4 = stack.pop(); - MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic1)); - MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic2)); - MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic3)); - MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic4)); - } - } - } - - /** - * Converts a stack element to its hexadecimal representation and returns - * only the first 4 bytes. If the value represents a special case (e.g., - * bottom, top, topNumeric), it returns a corresponding string. - * - * @param value The stack element to convert. - * - * @return A lowercase hexadecimal string of the first 4 bytes or a special - * identifier if applicable. - */ - public static String toHexFirst4Bytes(StackElement value) { - if (value.isBottom()) - return "bottom"; - if (value.isTopNotJumpdest()) - return "topNotJumpdest"; - if (value.isTopNumeric()) - return "topNumeric"; - if (value.isTop()) - return "top"; - - StringBuilder hex = new StringBuilder(toHex(value)); - while (hex.length() < 8) - hex.insert(0, "0"); - return hex.toString().toLowerCase().substring(0, 8); - } - - /** - * Converts a stack element's numeric value to its full hexadecimal - * representation. - * - * @param value The stack element to convert. - * - * @return A hexadecimal string representing the numeric value of the stack - * element. - */ - public static String toHex(StackElement value) { - return it.unipr.analysis.Number.toBigInteger(value.getNumber()).toString(16); - } -} \ No newline at end of file diff --git a/src/main/java/it/unipr/analysis/contract/SmartContract.java b/src/main/java/it/unipr/analysis/contract/SmartContract.java index 62f3d16ea..fb43c07f1 100644 --- a/src/main/java/it/unipr/analysis/contract/SmartContract.java +++ b/src/main/java/it/unipr/analysis/contract/SmartContract.java @@ -1,14 +1,10 @@ package it.unipr.analysis.contract; +import it.unipr.EVMLiSA; import it.unipr.cfg.EVMCFG; import it.unipr.cfg.push.Push; import it.unipr.frontend.EVMFrontend; -import it.unipr.frontend.EVMLiSAFeatures; -import it.unipr.frontend.EVMLiSATypeSystem; import it.unipr.utils.*; -import it.unive.lisa.LiSA; -import it.unive.lisa.conf.LiSAConfiguration; -import it.unive.lisa.program.Program; import it.unive.lisa.program.cfg.statement.Statement; import java.io.File; import java.io.IOException; @@ -32,6 +28,8 @@ public class SmartContract { private static final Logger log = LogManager.getLogger(SmartContract.class); + private String _name; + /** Contract address on the Ethereum blockchain. */ private String _address; @@ -74,6 +72,11 @@ public class SmartContract { /** Event signatures extracted from the contract ABI. */ private Set _eventsSignature; + private Set _allFunctionsEntryPoints; + + /** Execution time in milliseconds of the contract. */ + private long _executionTime; + /** * Constructs a new SmartContract with a generated address. */ @@ -90,6 +93,7 @@ public SmartContract() { JSONManager.throwNewError("Unable to create output directory: " + outputDir); System.exit(1); } + this._executionTime = 0; } /** @@ -108,6 +112,7 @@ public SmartContract(String address) { } this._address = address; + this._name = address; Path outputDir = _workingDirectory.resolve(address); this._abiFilePath = outputDir.resolve(address + ".abi"); this._bytecodeFilePath = outputDir.resolve(address + ".bytecode"); @@ -177,6 +182,7 @@ public SmartContract(String address) { this._functionsSignature = ABIManager.parseFunctionsFromABI(this._abiFilePath); this._eventsSignature = ABIManager.parseEventsFromABI(this._abiFilePath); + this._executionTime = 0; } /** @@ -187,7 +193,6 @@ public SmartContract(String address) { * @throws IllegalArgumentException If bytecodeFilePath is null. */ public SmartContract(Path bytecodeFilePath) { - this(); if (bytecodeFilePath == null) { log.error("Bytecode file path is null"); System.err.println(JSONManager.throwNewError("Bytecode file path is null")); @@ -198,7 +203,18 @@ public SmartContract(Path bytecodeFilePath) { System.exit(1); } + this._name = getFileNameWithoutExtension(bytecodeFilePath); + this._address = getFileNameWithoutExtension(bytecodeFilePath); + Path outputDir = _workingDirectory.resolve(_address); + try { + Files.createDirectories(outputDir); + } catch (IOException e) { + log.error("Unable to create output directory: {}", outputDir); + JSONManager.throwNewError("Unable to create output directory: " + outputDir); + System.exit(1); + } + this._bytecodeFilePath = outputDir.resolve(_address + ".bytecode"); this._mnemonicBytecodeFilePath = outputDir.resolve(_address + ".opcode"); @@ -244,7 +260,15 @@ public SmartContract(Path bytecodeFilePath, Path abiFilePath) { this._eventsSignature = ABIManager.parseEventsFromABI(abiFilePath); } - ///////////////////// GETTERs + /** + * Returns the contract name. + * + * @return The contract name. + */ + public String getName() { + return this._name; + } + /** * Returns the contract address. * @@ -299,6 +323,15 @@ public Path getWorkingDirectory() { return _workingDirectory.resolve(this._address); } + /** + * Returns the execution time for this contract. + * + * @return Execution time in milliseconds. + */ + public long getExecutionTime() { + return _executionTime; + } + /** * Returns the path of the mnemonic bytecode file. * @@ -359,10 +392,21 @@ public StatisticsObject getStatistics() { * @return Vulnerabilities object. */ public VulnerabilitiesObject getVulnerabilities() { - return _vulnerabilities; + return this._vulnerabilities = VulnerabilitiesObject.buildFromCFG(this._cfg); + } + + /** + * Sets the contract name. + * + * @param name The contract name. + * + * @return This SmartContract instance for method chaining. + */ + public SmartContract setName(String name) { + this._name = name; + return this; } - ///////////////////// SETTERs /** * Sets the contract address. * @@ -564,6 +608,18 @@ public static void setWorkingDirectory(Path workingDirectory) { _workingDirectory = workingDirectory; } + /** + * Sets the execution time for this contract. + * + * @param executionTime Execution time in milliseconds. + * + * @return This SmartContract instance for method chaining. + */ + public SmartContract setExecutionTime(long executionTime) { + this._executionTime = executionTime; + return this; + } + /** * Identifies and associates entry points for each function signature in the * contract. Uses the selector from each signature to find matching Push @@ -632,15 +688,15 @@ public void computeEventsExitPoints() { return; } - // Setup configuration - Program program = new Program(new EVMLiSAFeatures(), new EVMLiSATypeSystem()); - program.addCodeMember(this._cfg); - LiSAConfiguration conf = LiSAConfigurationManager.createConfiguration(this); - LiSA lisa = new LiSA(conf); - - EventsExitPointsComputer checker = new EventsExitPointsComputer(); - conf.semanticChecks.add(checker); - lisa.run(program); + Set logStatements = _cfg.getAllLogX(); + for (Signature signature : _eventsSignature) { + for (Statement eventEntryPoint : signature.getEntryPoints()) { + for (Statement logStatement : logStatements) { + if (_cfg.reachableFrom(eventEntryPoint, logStatement)) + signature.addExitPoint(logStatement); + } + } + } } /** @@ -648,11 +704,72 @@ public void computeEventsExitPoints() { * the graph to a file in the contract's working directory. */ public void generateCFGWithBasicBlocks() { + if (_functionsSignature == null) { + log.warn("Unable to generate CFG with Basic Blocks (_functionsSignature is null)"); + return; + } + if (_eventsSignature == null) { + log.warn("Unable to generate CFG with Basic Blocks (_eventsSignature is null)"); + return; + } + Path dotFile = _workingDirectory.resolve(_address).resolve("CFG.dot"); DOTFileManager.generateDotGraph(JSONManager.basicBlocksToJson(this), dotFile.toString()); log.info("Generated CFG at {}", dotFile); } + /** + * Retrieves all entry-point statements for every function in this contract. + * Caches the result on first invocation for efficiency. + * + * @return a set containing the entry-point statements of all functions + */ + public Set getAllFunctionEntryPoints() { + if (_allFunctionsEntryPoints == null) { + Set result = new HashSet<>(); + for (Signature signature : _functionsSignature) + result.addAll(signature.getEntryPoints()); + _allFunctionsEntryPoints = result; + } + return _allFunctionsEntryPoints; + } + + /** + * Determines the full function signature that corresponds to a given + * entry-point statement. If no matching function is found, returns + * "no-function-found". + * + * @param entryPoint the entry-point statement to look up + * + * @return the full signature of the function owning that entry-point, or + * "no-function-found" if none matches + */ + public String getFunctionSignatureFromEntryPoint(Statement entryPoint) { + for (Signature signature : _functionsSignature) + for (Statement entry : signature.getEntryPoints()) + if (entry.equals(entryPoint)) + return signature.getFullSignature(); + return "no-function-found"; + } + + /** + * Locates the function signature for an arbitrary statement by performing a + * reverse reachability search to its nearest entry-point, then mapping that + * entry-point back to its function signature. + * + * @param statement any statement within the contract + * + * @return the full signature of the containing function, or an empty string + * if no function context is found + */ + public String getFunctionSignatureByStatement(Statement statement) { + Statement ep = _cfg.reachableFromReverse(statement, getAllFunctionEntryPoints()); + + if (getFunctionSignatureFromEntryPoint(ep) != null) + return getFunctionSignatureFromEntryPoint(ep); + return ""; + } + /** * Converts this SmartContract to a JSON object. Includes all fields of the * contract, including bytecode, ABI, signatures, and analysis results. @@ -663,9 +780,25 @@ public JSONObject toJson() { JSONObject jsonObject = new JSONObject(); jsonObject.put("address", _address != null ? _address : new JSONArray()); - jsonObject.put("bytecode", _bytecode != null ? _bytecode : new JSONArray()); - jsonObject.put("mnemonic_bytecode", _mnemonicBytecode != null ? _mnemonicBytecode : new JSONArray()); - jsonObject.put("abi", _abi != null ? _abi : new JSONArray()); + if (!EVMLiSA.isInTestMode()) { + jsonObject.put("bytecode", _bytecode != null ? _bytecode : new JSONArray()); + jsonObject.put("mnemonic_bytecode", _mnemonicBytecode != null ? _mnemonicBytecode : new JSONArray()); + jsonObject.put("abi", _abi != null ? _abi : new JSONArray()); + + jsonObject.put("basic_blocks", + _basicBlocks != null ? JSONManager.basicBlocksToJson(this) : new JSONArray()); + JSONArray functionsArray = new JSONArray(); + if (_functionsSignature != null && !_functionsSignature.isEmpty()) + for (Signature signature : _functionsSignature) + functionsArray.put(signature.toJson()); + jsonObject.put("functions_signature", !functionsArray.isEmpty() ? functionsArray : new JSONArray()); + + JSONArray eventsArray = new JSONArray(); + if (_eventsSignature != null && !_eventsSignature.isEmpty()) + for (Signature signature : _eventsSignature) + eventsArray.put(signature.toJson()); + jsonObject.put("events_signature", !eventsArray.isEmpty() ? eventsArray : new JSONArray()); + } jsonObject.put("working_directory", _workingDirectory.toString()); @@ -678,23 +811,10 @@ public JSONObject toJson() { jsonObject.put("vulnerabilities", _vulnerabilities != null ? _vulnerabilities.toJson() : new JSONArray()); - jsonObject.put("basic_blocks", - _basicBlocks != null ? JSONManager.basicBlocksToJson(this) : new JSONArray()); - jsonObject.put("basic_blocks_pc", _basicBlocks != null ? BasicBlock.basicBlocksToLongArrayToString( BasicBlock.basicBlocksToLongArray(_basicBlocks)) : new JSONArray()); - JSONArray functionsArray = new JSONArray(); - if (_functionsSignature != null && !_functionsSignature.isEmpty()) - for (Signature signature : _functionsSignature) - functionsArray.put(signature.toJson()); - jsonObject.put("functions_signature", !functionsArray.isEmpty() ? functionsArray : new JSONArray()); - - JSONArray eventsArray = new JSONArray(); - if (_eventsSignature != null && !_eventsSignature.isEmpty()) - for (Signature signature : _eventsSignature) - eventsArray.put(signature.toJson()); - jsonObject.put("events_signature", !eventsArray.isEmpty() ? eventsArray : new JSONArray()); + jsonObject.put("execution_time", _executionTime); return jsonObject; } @@ -723,4 +843,17 @@ public boolean toFile() { } return true; } + + /** + * Extracts the filename without extension from a given Path. + * + * @param path The file path + * + * @return The filename without extension + */ + private static String getFileNameWithoutExtension(Path path) { + String fileName = path.getFileName().toString(); + int lastDotIndex = fileName.lastIndexOf('.'); + return (lastDotIndex == -1) ? fileName : fileName.substring(0, lastDotIndex); + } } \ No newline at end of file diff --git a/src/main/java/it/unipr/analysis/taint/RandomnessDependencyAbstractDomain.java b/src/main/java/it/unipr/analysis/taint/RandomnessDependencyAbstractDomain.java new file mode 100644 index 000000000..5bcb3a865 --- /dev/null +++ b/src/main/java/it/unipr/analysis/taint/RandomnessDependencyAbstractDomain.java @@ -0,0 +1,59 @@ +package it.unipr.analysis.taint; + +import it.unipr.cfg.*; +import it.unive.lisa.program.cfg.statement.Statement; +import it.unive.lisa.symbolic.value.Operator; +import java.util.Set; + +public class RandomnessDependencyAbstractDomain extends TaintAbstractDomain { + private static final RandomnessDependencyAbstractDomain TOP = new RandomnessDependencyAbstractDomain( + createFilledArray(TaintAbstractDomain.STACK_LIMIT, TaintElement.BOTTOM), TaintElement.CLEAN); + private static final RandomnessDependencyAbstractDomain BOTTOM = new RandomnessDependencyAbstractDomain(null, + TaintElement.BOTTOM); + + /** + * Builds an initial symbolic stack. + */ + public RandomnessDependencyAbstractDomain() { + this(createFilledArray(STACK_LIMIT, TaintElement.BOTTOM), TaintElement.CLEAN); + } + + /** + * Builds a taint abstract stack starting from a given stack and a list of + * elements that push taint. + * + * @param stack the stack of values + */ + protected RandomnessDependencyAbstractDomain(TaintElement[] stack, TaintElement memory) { + super(stack, memory); + } + + @Override + public boolean isTainted(Statement stmt) { + return stmt instanceof Timestamp + || stmt instanceof Blockhash + || stmt instanceof Difficulty + || stmt instanceof Balance; + } + + @Override + public Set getSanitizedOpcode() { + return Set.of(); + } + + @Override + public RandomnessDependencyAbstractDomain top() { + return TOP; + } + + @Override + public RandomnessDependencyAbstractDomain bottom() { + return BOTTOM; + } + + @Override + public TaintAbstractDomain mk(TaintElement[] stack, TaintElement memory) { + return new RandomnessDependencyAbstractDomain(stack, memory); + } + +} \ No newline at end of file diff --git a/src/main/java/it/unipr/analysis/taint/TaintAbstractDomain.java b/src/main/java/it/unipr/analysis/taint/TaintAbstractDomain.java index 0b5e53fe3..7a73c465d 100644 --- a/src/main/java/it/unipr/analysis/taint/TaintAbstractDomain.java +++ b/src/main/java/it/unipr/analysis/taint/TaintAbstractDomain.java @@ -8,6 +8,7 @@ import it.unive.lisa.analysis.lattices.Satisfiability; import it.unive.lisa.analysis.value.ValueDomain; import it.unive.lisa.program.cfg.ProgramPoint; +import it.unive.lisa.program.cfg.statement.Statement; import it.unive.lisa.symbolic.value.Constant; import it.unive.lisa.symbolic.value.Identifier; import it.unive.lisa.symbolic.value.Operator; @@ -21,6 +22,21 @@ import java.util.Set; import java.util.function.Predicate; +/** + * An abstract domain for taint analysis of EVM smart contracts. + *

+ * This abstract class provides the framework to track and propagate taint + * information along the symbolic execution of EVM bytecode. It models a + * circular stack of {@link TaintElement} objects and an associated memory + * element. Concrete implementations should define how to determine whether a + * statement is tainted and provide a set of sanitizing opcodes, as well as + * instantiate new instances of the domain. + *

+ * + * @see TaintElement + * @see ValueDomain + * @see BaseLattice + */ public abstract class TaintAbstractDomain implements ValueDomain, BaseLattice { @@ -32,7 +48,7 @@ public abstract class TaintAbstractDomain /** * The abstract stack as a circular array. */ - private final TaintElement[] circularArray; + private final TaintElement[] stack; /** * The index representing the beginning of the logical stack in the circular @@ -58,14 +74,15 @@ public abstract class TaintAbstractDomain private final TaintElement memory; /** - * Builds a taint abstract stack starting from a given stack and a memory - * element. + * Builds a taint abstract stack starting from a given stack and a list of + * elements that push taint. Builds a taint abstract stack starting from a + * given stack and a memory element. * - * @param circularArray the stack of values - * @param memory the memory element + * @param stack the stack of values + * @param memory the memory element */ - protected TaintAbstractDomain(TaintElement[] circularArray, TaintElement memory) { - this.circularArray = circularArray; + protected TaintAbstractDomain(TaintElement[] stack, TaintElement memory) { + this.stack = stack; this.memory = memory; this.head = 0; this.tail = 0; @@ -117,7 +134,7 @@ public TaintAbstractDomain smallStepSemantics(ValueExpression expression, Progra case "PushOperator": case "Push0Operator": { TaintAbstractDomain resultStack = clone(); - if (this.getTaintedOpcode().contains(op)) + if (this.isTainted((Statement) pp)) resultStack.push(TaintElement.TAINT); else resultStack.push(TaintElement.CLEAN); @@ -162,7 +179,6 @@ public TaintAbstractDomain smallStepSemantics(ValueExpression expression, Progra return resultStack; } - case "CalldatacopyOperator": { if (hasBottomUntil(3)) return bottom(); @@ -170,8 +186,8 @@ public TaintAbstractDomain smallStepSemantics(ValueExpression expression, Progra TaintAbstractDomain resultStack = clone(); resultStack.popX(3); - if (this.getTaintedOpcode().contains(op)) - return mk(resultStack.circularArray, TaintElement.TAINT); + if (this.isTainted((Statement) pp)) + return mk(resultStack.stack, TaintElement.TAINT); return resultStack; } @@ -188,7 +204,7 @@ public TaintAbstractDomain smallStepSemantics(ValueExpression expression, Progra TaintAbstractDomain resultStack = clone(); TaintElement opnd1 = resultStack.pop(); - if (this.getTaintedOpcode().contains(op)) + if (this.isTainted((Statement) pp)) resultStack.push(TaintElement.TAINT); else resultStack.push(TaintElement.semantics(opnd1)); @@ -217,8 +233,7 @@ else if (memory.isClean()) TaintElement value = resultStack.pop(); if (value.isTaint()) - return mk(resultStack.circularArray, TaintElement.TAINT); - + return mk(resultStack.stack, TaintElement.TAINT); else if (value.isClean()) return resultStack; } @@ -439,7 +454,7 @@ else if (value.isClean()) TaintElement offset = resultStack.pop(); TaintElement length = resultStack.pop(); - if (this.getTaintedOpcode().contains(op)) + if (this.isTainted((Statement) pp)) resultStack.push(TaintElement.TAINT); else resultStack.push(TaintElement.semantics(value, offset, length)); @@ -454,7 +469,7 @@ else if (value.isClean()) TaintElement length = resultStack.pop(); TaintElement salt = resultStack.pop(); - if (this.getTaintedOpcode().contains(op)) + if (this.isTainted((Statement) pp)) resultStack.push(TaintElement.TAINT); else resultStack.push(TaintElement.semantics(value, offset, length, salt)); @@ -473,7 +488,7 @@ else if (value.isClean()) TaintElement outOffset = resultStack.pop(); TaintElement outLength = resultStack.pop(); - if (this.getTaintedOpcode().contains(op)) + if (this.isTainted((Statement) pp)) resultStack.push(TaintElement.TAINT); else resultStack @@ -501,7 +516,7 @@ else if (value.isClean()) TaintElement outOffset = resultStack.pop(); TaintElement outLength = resultStack.pop(); - if (this.getTaintedOpcode().contains(op)) + if (this.isTainted((Statement) pp)) resultStack.push(TaintElement.TAINT); else resultStack.push(TaintElement.semantics(gas, to, inOffset, inLength, outOffset, outLength)); @@ -540,7 +555,7 @@ else if (value.isClean()) TaintAbstractDomain resultStack = clone(); TaintElement address = resultStack.pop(); - if (this.getTaintedOpcode().contains(op)) + if (this.isTainted((Statement) pp)) resultStack.push(TaintElement.TAINT); else resultStack.push(TaintElement.semantics(address)); @@ -568,7 +583,7 @@ else if (value.isClean()) TaintAbstractDomain resultStack = clone(); TaintElement address = resultStack.pop(); - if (this.getTaintedOpcode().contains(op)) + if (this.isTainted((Statement) pp)) resultStack.push(TaintElement.TAINT); else resultStack.push(TaintElement.semantics(address)); @@ -581,10 +596,10 @@ else if (value.isClean()) throw new SemanticException("Unrecognized opcode: " + pp); } - private TaintAbstractDomain dupXoperator(int x, TaintAbstractDomain stack) { - if (stack.isEmpty() || stack.hasBottomUntil(x)) + private TaintAbstractDomain dupXoperator(int x, TaintAbstractDomain other) { + if (other.isEmpty() || other.hasBottomUntil(x)) return bottom(); - return stack.dupX(x); + return other.dupX(x); } private TaintAbstractDomain swapXoperator(int x, TaintAbstractDomain stack) { @@ -618,9 +633,9 @@ public TaintAbstractDomain swapX(int x) { int posX = (tail - x + STACK_LIMIT) % STACK_LIMIT; int topIndex = (tail - 1 + STACK_LIMIT) % STACK_LIMIT; TaintAbstractDomain copy = this.clone(); - TaintElement tmp = copy.circularArray[posX]; - copy.circularArray[posX] = copy.circularArray[topIndex]; - copy.circularArray[topIndex] = tmp; + TaintElement tmp = copy.stack[posX]; + copy.stack[posX] = copy.stack[topIndex]; + copy.stack[topIndex] = tmp; return copy; } @@ -638,13 +653,13 @@ public TaintAbstractDomain dupX(int x) { return bottom(); int posX = (tail - x + STACK_LIMIT) % STACK_LIMIT; TaintAbstractDomain copy = this.clone(); - copy.push(circularArray[posX]); + copy.push(stack[posX]); return copy; } @Override public TaintAbstractDomain assume(ValueExpression expression, ProgramPoint src, ProgramPoint dest, - SemanticOracle oracle) throws SemanticException { + SemanticOracle oracle) { // nothing to do here return this; } @@ -656,13 +671,13 @@ public boolean knowsIdentifier(Identifier id) { } @Override - public TaintAbstractDomain forgetIdentifier(Identifier id) throws SemanticException { + public TaintAbstractDomain forgetIdentifier(Identifier id) { // nothing to do here return this; } @Override - public TaintAbstractDomain forgetIdentifiersIf(Predicate test) throws SemanticException { + public TaintAbstractDomain forgetIdentifiersIf(Predicate test) { // nothing to do here return this; } @@ -693,7 +708,7 @@ public String toString() { StringBuilder sb = new StringBuilder("["); for (int i = 0; i < STACK_LIMIT; i++) { int pos = (head + i) % STACK_LIMIT; - sb.append(circularArray[pos]); + sb.append(stack[pos]); if (i < STACK_LIMIT - 1) sb.append(", "); } @@ -702,23 +717,29 @@ public String toString() { } @Override - public TaintAbstractDomain pushScope(ScopeToken token) throws SemanticException { + public TaintAbstractDomain pushScope(ScopeToken token) { // nothing to do here return this; } @Override - public TaintAbstractDomain popScope(ScopeToken token) throws SemanticException { + public TaintAbstractDomain popScope(ScopeToken token) { // nothing to do here return this; } + protected static TaintElement[] createFilledArray(int size, TaintElement element) { + TaintElement[] array = new TaintElement[size]; + Arrays.fill(array, element); + return array; + } + /** * Computes the greatest lower bound (GLB) between this abstract domain and * another. * * @param other the other domain - * + * * @return a new domain representing the greatest lower bound of the two * domains */ @@ -736,7 +757,7 @@ public TaintAbstractDomain glbAux(TaintAbstractDomain other) throws SemanticExce * another. * * @param other the other domain - * + * * @return a new domain representing the least upper bound of the two * domains */ @@ -759,7 +780,7 @@ public TaintAbstractDomain lubAux(TaintAbstractDomain other) throws SemanticExce public TaintElement get(int index) { if (index < 0 || index >= STACK_LIMIT) return TaintElement.BOTTOM; - return circularArray[(head + index) % STACK_LIMIT]; + return stack[(head + index) % STACK_LIMIT]; } @Override @@ -782,7 +803,7 @@ public boolean lessOrEqualAux(TaintAbstractDomain other) throws SemanticExceptio * @param target the element to be pushed onto the stack */ public void push(TaintElement target) { - circularArray[tail] = target; + stack[tail] = target; tail = (tail + 1) % STACK_LIMIT; head = (head + 1) % STACK_LIMIT; } @@ -800,26 +821,26 @@ public void push(TaintElement target) { */ public TaintElement pop() { int topIndex = (tail - 1 + STACK_LIMIT) % STACK_LIMIT; - TaintElement popped = circularArray[topIndex]; + TaintElement popped = stack[topIndex]; head = (head - 1 + STACK_LIMIT) % STACK_LIMIT; int bottomIndex = head; int nextIndex = (head + 1) % STACK_LIMIT; - if (circularArray[nextIndex].isTop()) - circularArray[bottomIndex] = TaintElement.TOP; + if (stack[nextIndex].isTop()) + stack[bottomIndex] = TaintElement.TOP; else - circularArray[bottomIndex] = TaintElement.BOTTOM; + stack[bottomIndex] = TaintElement.BOTTOM; tail = (tail - 1 + STACK_LIMIT) % STACK_LIMIT; - circularArray[topIndex] = TaintElement.BOTTOM; + stack[topIndex] = TaintElement.BOTTOM; return popped; } /** * Checks whether between 0 and x-positions of the stack an element is - * bottom. /** Checks whether between 0 and x-positions of the stack an - * element is bottom. + * bottom. Checks whether between 0 and x-positions of the stack an element + * is bottom. * * @param x the position * @@ -829,7 +850,7 @@ public TaintElement pop() { public boolean hasBottomUntil(int x) { for (int i = 0; i < x; i++) { int index = (tail - 1 - i + STACK_LIMIT) % STACK_LIMIT; - if (circularArray[index].isBottom()) + if (stack[index].isBottom()) return true; } return false; @@ -837,7 +858,7 @@ public boolean hasBottomUntil(int x) { @Override public TaintAbstractDomain clone() { - TaintElement[] newArray = circularArray.clone(); + TaintElement[] newArray = stack.clone(); TaintAbstractDomain copy = mk(newArray, memory); copy.head = this.head; copy.tail = this.tail; @@ -858,12 +879,12 @@ public boolean equals(Object obj) { if (!memory.equals(other.memory)) return false; - return Arrays.equals(this.circularArray, other.circularArray); + return Arrays.equals(this.stack, other.stack); } @Override public int hashCode() { - return Objects.hash(circularArray, memory); + return Objects.hash(stack, memory); } /** @@ -872,11 +893,7 @@ public int hashCode() { * @return the second element of this abstract stack */ public TaintElement getSecondElement() { - if (isBottom()) - return TaintElement.BOTTOM; - if (isTop()) - return TaintElement.TOP; - return circularArray[(tail - 2 + STACK_LIMIT) % STACK_LIMIT]; + return getElementAtPosition(2); } /** @@ -885,11 +902,7 @@ public TaintElement getSecondElement() { * @return the first element of this abstract stack */ public TaintElement getFirstElement() { - if (isBottom()) - return TaintElement.BOTTOM; - if (isTop()) - return TaintElement.TOP; - return circularArray[(tail - 1 + STACK_LIMIT) % STACK_LIMIT]; + return getElementAtPosition(1); } /** @@ -903,11 +916,35 @@ public void popX(int pos) { } /** - * Yields the set of opcodes that push taint elements. + * Retrieves the element from the stack at the specified position. The + * position is calculated from the top of the stack, where the top is at + * position 1. If the stack state is {@code BOTTOM}, + * {@code TaintElement.BOTTOM} is returned. If the stack state is + * {@code TOP}, {@code TaintElement.TOP} is returned. * - * @return the set of opcodes that push taint elements + * @param position the position of the element to retrieve from the stack + * + * @return the element at the specified position in the stack */ - public abstract Set getTaintedOpcode(); + public TaintElement getElementAtPosition(int position) { + if (position < 1 || position > STACK_LIMIT) + throw new IllegalArgumentException("Invalid position: " + position); + if (isBottom()) + return TaintElement.BOTTOM; + else if (isTop()) + return TaintElement.TOP; + return stack[(tail - position + STACK_LIMIT) % STACK_LIMIT]; + } + + /** + * Determines whether the given statement is tainted. + * + * @param stmt the statement to check + * + * @return {@code true} if the statement is tainted, {@code false} + * otherwise. + */ + public abstract boolean isTainted(Statement stmt); /** * Yields the set of opcodes that sanitize the state. @@ -920,11 +957,10 @@ public void popX(int pos) { * Utility for creating a concrete instance of {@link TaintAbstractDomain} * given the stack and the memory. * - * @param array the stack + * @param stack the stack * @param memory the memory * * @return a new concrete instance of {@link TaintAbstractDomain} */ - public abstract TaintAbstractDomain mk(TaintElement[] array, TaintElement memory); - + public abstract TaintAbstractDomain mk(TaintElement[] stack, TaintElement memory); } \ No newline at end of file diff --git a/src/main/java/it/unipr/analysis/taint/TaintElement.java b/src/main/java/it/unipr/analysis/taint/TaintElement.java index de55086c0..6c299bca0 100644 --- a/src/main/java/it/unipr/analysis/taint/TaintElement.java +++ b/src/main/java/it/unipr/analysis/taint/TaintElement.java @@ -2,11 +2,29 @@ import it.unive.lisa.analysis.BaseLattice; import it.unive.lisa.analysis.Lattice; -import it.unive.lisa.analysis.SemanticException; import it.unive.lisa.util.representation.StringRepresentation; import it.unive.lisa.util.representation.StructuredRepresentation; import java.util.Objects; +/** + * Represents an element in the taint abstract domain for EVM bytecode analysis. + *

+ * This class models a taint element using a simple byte value and defines + * several special constants: + *

    + *
  • TOP: Represents the highest abstract value, indicating an unknown + * or over-approximated value.
  • + *
  • BOTTOM: Represents the lowest abstract value, indicating the + * absence of any information or an error state.
  • + *
  • TAINT: Marks an element as tainted, meaning it carries untrusted + * or external data that may lead to vulnerabilities.
  • + *
  • CLEAN: Marks an element as clean, meaning it is not tainted.
  • + *
+ *

+ * + * @see BaseLattice + * @see Lattice + */ public class TaintElement implements BaseLattice { public static final TaintElement TOP = new TaintElement((byte) 0); @@ -71,20 +89,30 @@ public boolean isClean() { } @Override - public TaintElement lubAux(TaintElement other) throws SemanticException { + public TaintElement lubAux(TaintElement other) { return TOP; } @Override - public boolean lessOrEqualAux(TaintElement other) throws SemanticException { + public boolean lessOrEqualAux(TaintElement other) { return false; } @Override - public TaintElement glbAux(TaintElement other) throws SemanticException { + public TaintElement glbAux(TaintElement other) { return BOTTOM; } + @Override + public boolean isTop() { + return this == TOP; + } + + @Override + public boolean isBottom() { + return this == BOTTOM; + } + /** * Check whether a set of {@link TaintElement}s contains at least one * tainted element, if so returns taint. Otherwise, if there is at least a @@ -112,6 +140,53 @@ else if (t == TOP) return CLEAN; } + /** + * Checks if any of the provided {@link TaintElement} objects is tainted or + * has the top value. + * + * @param elements the array of {@link TaintElement} objects to check + * + * @return {@code true} if at least one of the elements is tainted or has + * the top value, {@code false} otherwise + */ + public static boolean isTaintedOrTop(TaintElement... elements) { + return isAtLeastOneTainted(elements) || isAtLeastOneTop(elements); + } + + /** + * Checks if at least one of the provided {@link TaintElement} objects is + * tainted. + * + * @param elements the array of {@link TaintElement} objects to check + * + * @return {@code true} if at least one of the elements is tainted, + * {@code false} otherwise + */ + public static boolean isAtLeastOneTainted(TaintElement... elements) { + for (TaintElement element : elements) { + if (element.isTaint()) + return true; + } + return false; + } + + /** + * Checks if at least one of the provided {@link TaintElement} objects has + * the top value. + * + * @param elements the array of {@link TaintElement} objects to check + * + * @return {@code true} if at least one of the elements has the top value, + * {@code false} otherwise + */ + public static boolean isAtLeastOneTop(TaintElement... elements) { + for (TaintElement element : elements) { + if (element.isTop()) + return true; + } + return false; + } + @Override public int hashCode() { return Objects.hash(v); diff --git a/src/main/java/it/unipr/analysis/taint/TimestampDependencyAbstractDomain.java b/src/main/java/it/unipr/analysis/taint/TimestampDependencyAbstractDomain.java deleted file mode 100644 index 3af779d58..000000000 --- a/src/main/java/it/unipr/analysis/taint/TimestampDependencyAbstractDomain.java +++ /dev/null @@ -1,69 +0,0 @@ -package it.unipr.analysis.taint; - -import it.unipr.analysis.operator.BalanceOperator; -import it.unipr.analysis.operator.BlockhashOperator; -import it.unipr.analysis.operator.DifficultyOperator; -import it.unipr.analysis.operator.TimestampOperator; -import it.unive.lisa.symbolic.value.Operator; -import java.util.*; - -public class TimestampDependencyAbstractDomain extends TaintAbstractDomain { - private static final TimestampDependencyAbstractDomain TOP = new TimestampDependencyAbstractDomain( - createFilledArray(TaintAbstractDomain.STACK_LIMIT, TaintElement.BOTTOM), TaintElement.CLEAN); - private static final TimestampDependencyAbstractDomain BOTTOM = new TimestampDependencyAbstractDomain(null, - TaintElement.BOTTOM); - - private static TaintElement[] createFilledArray(int size, TaintElement element) { - TaintElement[] array = new TaintElement[size]; - Arrays.fill(array, element); - return array; - } - - /** - * Builds an initial symbolic stack. - */ - public TimestampDependencyAbstractDomain() { - this(createFilledArray(STACK_LIMIT, TaintElement.BOTTOM), TaintElement.CLEAN); - } - - /** - * Builds a taint abstract stack starting from a given stack and a list of - * elements that push taint. - * - * @param circularArray the stack of values - */ - protected TimestampDependencyAbstractDomain(TaintElement[] circularArray, TaintElement memory) { - super(circularArray, memory); - } - - @Override - public Set getTaintedOpcode() { - Set taintedOpcode = new HashSet<>(); - taintedOpcode.add(TimestampOperator.INSTANCE); - taintedOpcode.add(BlockhashOperator.INSTANCE); - taintedOpcode.add(DifficultyOperator.INSTANCE); - taintedOpcode.add(BalanceOperator.INSTANCE); - return taintedOpcode; - } - - @Override - public Set getSanitizedOpcode() { - return Set.of(); - } - - @Override - public TimestampDependencyAbstractDomain top() { - return TOP; - } - - @Override - public TimestampDependencyAbstractDomain bottom() { - return BOTTOM; - } - - @Override - public TaintAbstractDomain mk(TaintElement[] array, TaintElement memory) { - return new TimestampDependencyAbstractDomain(array, memory); - } - -} \ No newline at end of file diff --git a/src/main/java/it/unipr/analysis/taint/TxOriginAbstractDomain.java b/src/main/java/it/unipr/analysis/taint/TxOriginAbstractDomain.java index 6cf263c60..83fc3980e 100644 --- a/src/main/java/it/unipr/analysis/taint/TxOriginAbstractDomain.java +++ b/src/main/java/it/unipr/analysis/taint/TxOriginAbstractDomain.java @@ -1,9 +1,8 @@ package it.unipr.analysis.taint; -import it.unipr.analysis.operator.OriginOperator; +import it.unipr.cfg.Origin; +import it.unive.lisa.program.cfg.statement.Statement; import it.unive.lisa.symbolic.value.Operator; -import java.util.Arrays; -import java.util.Collections; import java.util.Set; public class TxOriginAbstractDomain extends TaintAbstractDomain { @@ -12,12 +11,6 @@ public class TxOriginAbstractDomain extends TaintAbstractDomain { createFilledArray(TaintAbstractDomain.STACK_LIMIT, TaintElement.BOTTOM), TaintElement.CLEAN); private static final TxOriginAbstractDomain BOTTOM = new TxOriginAbstractDomain(null, TaintElement.BOTTOM); - private static TaintElement[] createFilledArray(int size, TaintElement element) { - TaintElement[] array = new TaintElement[size]; - Arrays.fill(array, element); - return array; - } - /** * Builds an initial symbolic stack. */ @@ -29,15 +22,15 @@ public TxOriginAbstractDomain() { * Builds a taint abstract stack starting from a given stack and a list of * elements that push taint. * - * @param circularArray the stack of values + * @param stack the stack of values */ - protected TxOriginAbstractDomain(TaintElement[] circularArray, TaintElement memory) { - super(circularArray, memory); + protected TxOriginAbstractDomain(TaintElement[] stack, TaintElement memory) { + super(stack, memory); } @Override - public Set getTaintedOpcode() { - return Collections.singleton(OriginOperator.INSTANCE); + public boolean isTainted(Statement stmt) { + return stmt instanceof Origin; } @Override @@ -56,7 +49,7 @@ public TxOriginAbstractDomain bottom() { } @Override - public TaintAbstractDomain mk(TaintElement[] array, TaintElement memory) { - return new TxOriginAbstractDomain(array, memory); + public TaintAbstractDomain mk(TaintElement[] stack, TaintElement memory) { + return new TxOriginAbstractDomain(stack, memory); } } diff --git a/src/main/java/it/unipr/cfg/EVMCFG.java b/src/main/java/it/unipr/cfg/EVMCFG.java index a3810920f..c9767e1c6 100644 --- a/src/main/java/it/unipr/cfg/EVMCFG.java +++ b/src/main/java/it/unipr/cfg/EVMCFG.java @@ -38,12 +38,17 @@ public class EVMCFG extends CFG { private static final Logger log = LogManager.getLogger(EVMCFG.class); + private Set _basicBlocks; private Set jumpDestsNodes; private Set jumpNodes; private Set pushedJumps; private Set sstores; private Set jumpDestsNodesLocations; - private Set basicBlocks; + public Set logxs; + public Set calls; + public Set externalData; + public Set jumpI; + public Set successfullyTerminationStatements; /** * Builds a EVMCFG starting from its description. @@ -87,12 +92,137 @@ else if ((statement instanceof Jump) || (statement instanceof Jumpi)) { } /** - * Returns a set of all the SSTORE statements in the CFG. SSTORE + * Returns a set of all the CALLDATA and CALLDATACOPY statements in the CFG. + * + * @return a set of all the CALLDATA and CALLDATACOPY statements in the CFG + */ + public Set getExternalData() { + if (this.externalData == null) { + NodeList cfgNodeList = this.getNodeList(); + Set externalData = new HashSet<>(); + + for (Statement statement : cfgNodeList.getNodes()) { + if (statement instanceof Calldataload + || statement instanceof Calldatacopy) { + externalData.add(statement); + } + } + + return this.externalData = externalData; + } + + return this.externalData; + } + + /** + * Returns a set of all the SSTORE statements in the CFG. * * @return a set of all the SSTORE statements in the CFG */ public Set getAllSstore() { - return sstores; + if (this.sstores == null) { + NodeList cfgNodeList = this.getNodeList(); + Set sstores = new HashSet<>(); + + for (Statement statement : cfgNodeList.getNodes()) + if (statement instanceof Sstore) + sstores.add(statement); + + return this.sstores = sstores; + } + + return this.sstores; + } + + /** + * Returns a set of all the STOP and RETURN statements in the CFG. + * + * @return a set of all the STOP and RETURN statements in the CFG + */ + public Set getAllSuccessfullyTerminationStatements() { + if (this.successfullyTerminationStatements == null) { + NodeList cfgNodeList = this.getNodeList(); + Set successfullyTerminationStatements = new HashSet<>(); + + for (Statement statement : cfgNodeList.getNodes()) + if (statement instanceof Stop + || statement instanceof Return) + successfullyTerminationStatements.add(statement); + + return this.successfullyTerminationStatements = successfullyTerminationStatements; + } + + return this.successfullyTerminationStatements; + } + + /** + * Returns a set of all the CALL, STATICCALL and DELEGATECALL statements in + * the CFG. + * + * @return a set of all the CALL, STATICCALL and DELEGATECALL statements in + * the CFG + */ + public Set getAllCall() { + if (this.calls == null) { + NodeList cfgNodeList = this.getNodeList(); + Set calls = new HashSet<>(); + + for (Statement statement : cfgNodeList.getNodes()) { + if (statement instanceof Call) { + calls.add(statement); + } else if (statement instanceof Staticcall) { + calls.add(statement); + } else if (statement instanceof Delegatecall) { + calls.add(statement); + } + } + + return this.calls = calls; + } + + return this.calls; + } + + public Set getAllJumpI() { + if (this.jumpI == null) { + NodeList cfgNodeList = this.getNodeList(); + Set jumpI = new HashSet<>(); + + for (Statement statement : cfgNodeList.getNodes()) { + if (statement instanceof Jumpi) { + jumpI.add(statement); + } + } + + return this.jumpI = jumpI; + } + + return this.jumpI; + } + + /** + * Returns a set of all the LOGx statements in the CFG. + * + * @return a set of all the LOGx statements in the CFG + */ + public Set getAllLogX() { + if (logxs == null) { + NodeList cfgNodeList = this.getNodeList(); + Set logxs = new HashSet<>(); + + for (Statement statement : cfgNodeList.getNodes()) { + if (statement instanceof Log1 + || statement instanceof Log2 + || statement instanceof Log3 + || statement instanceof Log4) { + logxs.add(statement); + } + } + + return this.logxs = logxs; + } + + return logxs; } /** @@ -128,6 +258,39 @@ public Set getAllJumps() { return jumpNodes; } + /** + * Retrieves all basic blocks in this control-flow graph (CFG). If the basic + * blocks have not been computed yet, they are generated. + * + * @return a set containing all {@link BasicBlock} instances in the CFG + */ + public Set getAllBasicBlocks() { + if (this._basicBlocks == null) + this._basicBlocks = BasicBlock.getBasicBlocks(this); + return this._basicBlocks; + } + + /** + * Finds the basic block that contains the given statement. If the basic + * blocks have not been computed yet, they are generated first. + * + * @param stmt the statement to search for within the basic blocks + * + * @return the {@link BasicBlock} containing the given statement, or + * {@code null} if not found + */ + public BasicBlock getBasicBlock(Statement stmt) { + if (this._basicBlocks == null) + this._basicBlocks = BasicBlock.getBasicBlocks(this); + + for (BasicBlock bb : this._basicBlocks) + for (Statement s : bb.getStatements()) + if (s.equals(stmt)) + return bb; + + return null; + } + public int getOpcodeCount() { // -1 for the return statement, that it does not correspond to an actual // statement of the smart contract, but it is added by EVMLiSA @@ -243,15 +406,89 @@ public void validate() throws ProgramValidationException { */ public boolean reachableFrom(Statement start, Statement target) { String key = this.hashCode() + "" + start.hashCode() + "" + target.hashCode(); - if (MyCache.getInstance().existsInReachableFrom(key)) { + + if (MyCache.getInstance().existsInReachableFrom(key)) return MyCache.getInstance().isReachableFrom(key); - } boolean result = dfs(start, target, new HashSet<>()); MyCache.getInstance().addReachableFrom(key, result); return result; } + public boolean reachableFromReverse(Statement start, Statement target) { + String key = this.hashCode() + "" + start.hashCode() + "" + target.hashCode() + "reverse"; + + if (MyCache.getInstance().existsInReachableFrom(key)) + return MyCache.getInstance().isReachableFrom(key); + + boolean result = dfsReverse(start, target, new HashSet<>()); + MyCache.getInstance().addReachableFrom(key, result); + return result; + } + + /** + * Attempts to find a reachable entrypoint statement by traversing the CFG + * in reverse (following ingoing edges) from the specified start node. + * + * @param start the node from which to begin the reverse search + * @param entrypoints the set of candidate entrypoint statements to detect + * + * @return the first entrypoint encountered, or null if none are reachable + */ + public Statement reachableFromReverse(Statement start, Set entrypoints) { + return bfsReverse(start, entrypoints, new HashSet<>()); + } + + /** + * Performs a breadth‑first search on the reversed CFG, starting from a + * given node, to locate any of a set of entrypoint statements. + * + * @param start the initial node of the search + * @param entrypoints the set of target statements to find + * @param visited the set of already visited nodes (to prevent cycles) + * + * @return the first matching entrypoint found, or null if no entrypoint is + * reachable + */ + private Statement bfsReverse(Statement start, Set entrypoints, Set visited) { + Queue queue = new LinkedList<>(); + queue.offer(start); + visited.add(start); + + while (!queue.isEmpty()) { + Statement current = queue.poll(); + + for (Edge edge : list.getIngoingEdges(current)) { + Statement next = edge.getSource(); + if (visited.add(next)) { + queue.offer(next); + } + if (entrypoints.contains(next)) { + return next; + } + } + } + + return null; + } + + /** + * Determines if the target statement is reachable from the start statement + * across a cross-chain edge in the control flow graph (CFG). + * + * @param start the starting statement in the first control flow graph. + * @param target the target statement which may belong to a different + * control flow graph. + * + * @return true if the target statement is reachable from the start + * statement across a cross-chain edge; false otherwise. + */ + public boolean reachableFromCrossingACrossChainEdge(Statement start, Statement target) { + if (start.getCFG().equals(target.getCFG())) + return false; + return reachableFrom(start, target); + } + /** * Performs a depth-first search (DFS) to determine if the target statement * is reachable from the start statement. @@ -284,6 +521,60 @@ private boolean dfs(Statement start, Statement target, Set visited) { return false; } + /** + * Performs a breadth-first search (BFS) to determine if the target + * statement is reachable from the start statement. + * + * @param start The starting statement. + * @param target The target statement. + * @param visited A set of visited statements to avoid cycles. + * + * @return True if the target is reachable from the start, false otherwise. + */ + private boolean bfs(Statement start, Statement target, Set visited) { + Queue queue = new LinkedList<>(); + queue.offer(start); + visited.add(start); + + while (!queue.isEmpty()) { + Statement current = queue.poll(); + if (current.equals(target)) + return true; + + for (Edge edge : list.getOutgoingEdges(current)) { + Statement next = edge.getDestination(); + if (visited.add(next)) { // add returns true if next was not + // already in visited + queue.offer(next); + } + } + } + + return false; + } + + private boolean dfsReverse(Statement start, Statement target, Set visited) { + Deque stack = new ArrayDeque<>(); + stack.push(start); + + while (!stack.isEmpty()) { + Statement current = stack.pop(); + + if (current.equals(target)) + return true; + + if (visited.add(current)) { + for (Edge edge : list.getIngoingEdges(current)) { + Statement next = edge.getSource(); + if (!visited.contains(next)) + stack.push(next); + } + } + } + + return false; + } + /** * Finds the furthest reachable SSTORE statements from a given start * statement using BFS. @@ -328,9 +619,9 @@ public Set getFurthestSstores(Statement start) { */ public boolean reachableFromSequentially(Statement start, Statement target) { String key = this.hashCode() + "" + start.hashCode() + "" + target.hashCode() + "sequentially"; - if (MyCache.getInstance().existsInReachableFrom(key)) { + + if (MyCache.getInstance().existsInReachableFrom(key)) return MyCache.getInstance().isReachableFrom(key); - } boolean result = dfsSequential(start, target, new HashSet<>()); MyCache.getInstance().addReachableFrom(key, result); @@ -413,98 +704,157 @@ public Set> findMatchingStatements(Statement start, return matchingStatements; } - public boolean reachableFromCrossing(Statement start, Statement target, Set statements) { - return dfsCrossing(start, target, new HashSet<>(), statements); + /** + * Determines if the target statement is reachable from the start statement + * without triggering any of the specified statement types. + * + * @param start the starting statement from which the reachability is + * checked + * @param target the target statement to check for reachability + * @param avoidTypes the types of statements to avoid during traversal + * + * @return true if the target statement is reachable from the start without + * triggering any of the specified statement types, false + * otherwise + */ + public boolean reachableFromWithoutTypes(Statement start, Statement target, Set> avoidTypes) { + String key = this.hashCode() + "" + start.hashCode() + "" + target.hashCode() + "withouttypes" + + avoidTypes.hashCode(); + + if (MyCache.getInstance().existsInReachableFrom(key)) + return MyCache.getInstance().isReachableFrom(key); + + boolean result = dfsWithoutTypes(start, target, new HashSet<>(), avoidTypes); + MyCache.getInstance().addReachableFrom(key, result); + return result; } /** - * Check whether all paths from {@code start} to {@code target} contain at - * least one {@code statements} statement. This method explores all possible - * paths using a depth-first search (DFS). If there is at least one path - * where a required statement is missing, the method returns {@code false}. - * - * @param start The starting statement of the search. - * @param target The target statement. - * @param visited A set of visited statements to avoid cycles. - * @param statements The set of statements that must be present in all - * paths. + * Performs a depth-first search (DFS) to determine if a path exists from a + * start statement to a target statement in a graph, avoiding any paths that + * involve edges with a source node of the specified types. * - * @return {@code true} if all paths contain all statements, otherwise - * {@code false}. + * @param start the starting statement for the DFS traversal + * @param target the target statement to reach during the DFS traversal + * @param visited a set of already visited statements to avoid cycles + * during the traversal + * @param avoidTypes the types of statements to avoid during traversal + * + * @return true if a path exists from the start statement to the target + * statement without traversing through edges originating from + * nodes of the specified types, false otherwise */ - private boolean dfsCrossing(Statement start, Statement target, Set visited, Set statements) { - boolean foundTarget = false; - Set pathStatements = new HashSet<>(); - - Stack> stack = new Stack<>(); - stack.push(Collections.singletonList(start)); + private boolean dfsWithoutTypes(Statement start, + Statement target, + Set visited, + Set> avoidTypes) { + Stack stack = new Stack<>(); + boolean noVisitedLogs = true; + stack.push(start); while (!stack.isEmpty()) { - List path = stack.pop(); - Statement current = path.get(path.size() - 1); - - if (visited.contains(current)) { - continue; - } + Statement current = stack.pop(); - visited.add(current); - pathStatements.add(current); + if (current.equals(target)) + return noVisitedLogs; - if (current.equals(target)) { - foundTarget = true; + if (!visited.contains(current)) { + visited.add(current); + Collection outgoingEdges = list.getOutgoingEdges(current); - // Check if this specific path contains at least a statement - boolean contains = false; - for (Statement statement : statements) { - if (pathStatements.contains(statement)) { - contains = true; - break; + for (Edge edge : outgoingEdges) { + if (avoidTypes.stream().anyMatch(type -> type.isInstance(edge.getSource()))) { + noVisitedLogs = false; + continue; } - } - if (!contains) - return false; - } - Collection outgoingEdges = list.getOutgoingEdges(current); - for (Edge edge : outgoingEdges) { - Statement next = edge.getDestination(); - if (!visited.contains(next)) { - List newPath = new ArrayList<>(path); - newPath.add(next); - stack.push(newPath); + Statement next = edge.getDestination(); + if (!visited.contains(next)) + stack.push(next); } } } - - return foundTarget; + return noVisitedLogs; } /** - * Checks if the target statement is reachable from the start statement - * without traversing any Jumpi instructions. + * Retrieves all statements of the specified types that lie on any + * control‑flow path between two given statements in the CFG. + *

+ * This method performs a depth‑first traversal from the {@code start} + * statement until reaching the {@code target}. Along every explored path, + * it collects any intermediate statements whose runtime class matches one + * of the provided types. * - * @param start the starting statement - * @param target the target statement + * @param start the entry point of the search + * @param target the termination point of the search + * @param getTypes a set of statement classes to filter and collect along + * the paths * - * @return true if the target is reachable without passing through Jumpi, - * false otherwise + * @return a set of all matching statements encountered before reaching + * {@code target}, or an empty set if none are found */ - public boolean reachableFromWithoutJumpI(Statement start, Statement target) { - return dfsWithoutJumpI(start, target, new HashSet<>()); + public Set getStatementsInAPathWithTypes( + Statement start, + Statement target, + Set> getTypes) { + + Set visited = new HashSet<>(); + Set matchingStatements = new HashSet<>(); + Stack stack = new Stack<>(); + stack.push(start); + + while (!stack.isEmpty()) { + Statement current = stack.pop(); + + if (current.equals(target)) + return matchingStatements; + + if (!visited.contains(current)) { + visited.add(current); + + Collection outgoingEdges = list.getOutgoingEdges(current); + + for (Edge edge : outgoingEdges) { + if (getTypes.stream().anyMatch(type -> type.isInstance(edge.getDestination()))) + matchingStatements.add(edge.getDestination()); + Statement next = edge.getDestination(); + if (!visited.contains(next)) + stack.push(next); + } + } + } + + return matchingStatements; } /** - * Performs a depth-first search (DFS) to determine if the target statement - * is reachable from the start statement while avoiding Jumpi instructions. + * Determines if the 'target' statement is reachable from the 'start' + * statement while avoiding any statements in 'avoidStatements'. Results are + * cached to avoid repeated computations. * - * @param start the starting statement - * @param target the target statement - * @param visited the set of already visited statements - * - * @return true if the target is reachable without passing through Jumpi, - * false otherwise + * @param start the starting statement for the search + * @param target the target statement to verify reachability + * @param avoidStatements the set of statements to ignore during traversal + * + * @return true if the 'target' is reachable from 'start' without going + * through any of the 'avoidStatements' it return false + * otherwise */ - private boolean dfsWithoutJumpI(Statement start, Statement target, Set visited) { + public boolean reachableFromWithoutStatements(Statement start, Statement target, Set avoidStatements) { + String key = this.hashCode() + "" + start.hashCode() + "" + target.hashCode() + "withoutstatements" + + avoidStatements.hashCode(); + + if (MyCache.getInstance().existsInReachableFrom(key)) + return MyCache.getInstance().isReachableFrom(key); + + boolean result = dfsWithoutStatements(start, target, new HashSet<>(), avoidStatements); + MyCache.getInstance().addReachableFrom(key, result); + return result; + } + + private boolean dfsWithoutStatements(Statement start, Statement target, Set visited, + Set avoidStatements) { Stack stack = new Stack<>(); stack.push(start); @@ -520,7 +870,7 @@ private boolean dfsWithoutJumpI(Statement start, Statement target, Set outgoingEdges = list.getOutgoingEdges(current); for (Edge edge : outgoingEdges) { - if (edge.getSource() instanceof Jumpi) + if (avoidStatements.contains(edge.getDestination())) continue; Statement next = edge.getDestination(); if (!visited.contains(next)) diff --git a/src/main/java/it/unipr/checker/RandomnessDependencyChecker.java b/src/main/java/it/unipr/checker/RandomnessDependencyChecker.java new file mode 100644 index 000000000..9f2512dda --- /dev/null +++ b/src/main/java/it/unipr/checker/RandomnessDependencyChecker.java @@ -0,0 +1,141 @@ +package it.unipr.checker; + +import it.unipr.analysis.taint.TaintAbstractDomain; +import it.unipr.analysis.taint.TaintElement; +import it.unipr.cfg.*; +import it.unipr.utils.MyCache; +import it.unive.lisa.analysis.AnalysisState; +import it.unive.lisa.analysis.AnalyzedCFG; +import it.unive.lisa.analysis.SemanticException; +import it.unive.lisa.analysis.SimpleAbstractState; +import it.unive.lisa.analysis.heap.MonolithicHeap; +import it.unive.lisa.analysis.nonrelational.value.TypeEnvironment; +import it.unive.lisa.analysis.types.InferredTypes; +import it.unive.lisa.checks.semantic.CheckToolWithAnalysisResults; +import it.unive.lisa.checks.semantic.SemanticCheck; +import it.unive.lisa.program.cfg.CFG; +import it.unive.lisa.program.cfg.statement.Statement; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class RandomnessDependencyChecker implements + SemanticCheck>> { + + private static final Logger log = LogManager.getLogger(RandomnessDependencyChecker.class); + + private static boolean isEnabled = false; + + public static void enableChecker() { + isEnabled = true; + } + + public static void disableChecker() { + isEnabled = false; + } + + public static boolean isEnabled() { + return isEnabled; + } + + @Override + public boolean visit( + CheckToolWithAnalysisResults< + SimpleAbstractState>> tool, + CFG graph, Statement node) { + + EVMCFG cfg = ((EVMCFG) graph); + + if (node instanceof Jump || node instanceof Jumpi || node instanceof Sstore + || node instanceof Sha3) + for (AnalyzedCFG>> result : tool.getResultOf(cfg)) { + AnalysisState>> analysisResult = null; + + try { + analysisResult = result.getAnalysisStateBefore(node); + } catch (SemanticException e1) { + log.error("(RandomnessDependencyChecker): {}", e1.getMessage()); + } + + // Retrieve the symbolic stack from the analysis result + TaintAbstractDomain taintedStack = analysisResult.getState().getValueState(); + + // If the stack is bottom, the jump is definitely + // unreachable + if (taintedStack.isBottom()) + // Nothing to do + continue; + else { + if (node instanceof Sha3 + || node instanceof Sstore + || node instanceof Jumpi) { + + if (TaintElement.isAtLeastOneTainted(taintedStack.getElementAtPosition(1), + taintedStack.getElementAtPosition(2))) + raiseWarning(node, tool, cfg); + else if (TaintElement.isAtLeastOneTop(taintedStack.getElementAtPosition(1), + taintedStack.getElementAtPosition(2))) + raisePossibleWarning(node, tool, cfg); + + } else if (node instanceof Jump) { + + if (TaintElement.isAtLeastOneTainted(taintedStack.getElementAtPosition(1))) + raiseWarning(node, tool, cfg); + else if (TaintElement.isAtLeastOneTop(taintedStack.getElementAtPosition(1))) + raisePossibleWarning(node, tool, cfg); + + } + } + } + + return true; + } + + /** + * Raises a warning indicating a randomness dependency vulnerability in the + * analyzed program. + * + * @param sink the statement causing the warning + * @param tool the analysis tool and results used for the check + * @param cfg the control flow graph where the warning is identified + */ + private void raiseWarning(Statement sink, CheckToolWithAnalysisResults< + SimpleAbstractState>> tool, + EVMCFG cfg) { + ProgramCounterLocation sinkLoc = (ProgramCounterLocation) sink.getLocation(); + + log.warn("[DEFINITE] Randomness dependency vulnerability at pc {} (line {}).", + sinkLoc.getPc(), + sinkLoc.getSourceCodeLine()); + + String warn = "[DEFINITE] Randomness dependency vulnerability at pc " + + ((ProgramCounterLocation) sink.getLocation()).getSourceCodeLine(); + tool.warn(warn); + MyCache.getInstance().addRandomnessDependencyWarning(cfg.hashCode(), warn); + } + + /** + * Logs a possible randomness dependency vulnerability warning and updates + * the tool and cache with the detected issue. + * + * @param sink the statement where the potential vulnerability is detected + * @param tool the analysis tool containing the current state and analysis + * results + * @param cfg the control flow graph associated with the statement + */ + private void raisePossibleWarning(Statement sink, CheckToolWithAnalysisResults< + SimpleAbstractState>> tool, + EVMCFG cfg) { + ProgramCounterLocation sinkLoc = (ProgramCounterLocation) sink.getLocation(); + + log.warn("[POSSIBLE] Randomness dependency vulnerability at pc {} (line {}).", + sinkLoc.getPc(), + sinkLoc.getSourceCodeLine()); + + String warn = "[POSSIBLE] Randomness dependency vulnerability at pc " + + ((ProgramCounterLocation) sink.getLocation()).getSourceCodeLine(); + tool.warn(warn); + MyCache.getInstance().addPossibleRandomnessDependencyWarning(cfg.hashCode(), warn); + } +} diff --git a/src/main/java/it/unipr/checker/ReentrancyChecker.java b/src/main/java/it/unipr/checker/ReentrancyChecker.java index 194adc994..4019b60f2 100644 --- a/src/main/java/it/unipr/checker/ReentrancyChecker.java +++ b/src/main/java/it/unipr/checker/ReentrancyChecker.java @@ -111,8 +111,11 @@ private void checkForReentrancy(Statement call, CheckToolWithAnalysisResults< if (!ss2.equals(ss1) && cfg.reachableFromSequentially(ss1, ss2)) sstoreLoc = (ProgramCounterLocation) ss2.getLocation(); - log.debug("Reentrancy attack at {} at line no. {} coming from line {}", sstoreLoc.getPc(), - sstoreLoc.getSourceCodeLine(), ((ProgramCounterLocation) call.getLocation()).getSourceCodeLine()); + log.warn("Reentrancy attack at pc {} (line {}) coming from pc {} (line {}).", + sstoreLoc.getPc(), + sstoreLoc.getSourceCodeLine(), + ((ProgramCounterLocation) call.getLocation()).getPc(), + ((ProgramCounterLocation) call.getLocation()).getSourceCodeLine()); String warn = "Reentrancy attack at " + sstoreLoc.getPc(); tool.warn(warn); MyCache.getInstance().addReentrancyWarning(cfg.hashCode(), warn); diff --git a/src/main/java/it/unipr/checker/TimestampDependencyChecker.java b/src/main/java/it/unipr/checker/TimestampDependencyChecker.java deleted file mode 100644 index 1c7861be0..000000000 --- a/src/main/java/it/unipr/checker/TimestampDependencyChecker.java +++ /dev/null @@ -1,124 +0,0 @@ -package it.unipr.checker; - -import it.unipr.analysis.taint.TaintAbstractDomain; -import it.unipr.analysis.taint.TaintElement; -import it.unipr.cfg.EVMCFG; -import it.unipr.cfg.Jump; -import it.unipr.cfg.Jumpi; -import it.unipr.cfg.ProgramCounterLocation; -import it.unipr.cfg.Return; -import it.unipr.cfg.Sha3; -import it.unipr.cfg.Sstore; -import it.unipr.utils.MyCache; -import it.unive.lisa.analysis.AnalysisState; -import it.unive.lisa.analysis.AnalyzedCFG; -import it.unive.lisa.analysis.SemanticException; -import it.unive.lisa.analysis.SimpleAbstractState; -import it.unive.lisa.analysis.heap.MonolithicHeap; -import it.unive.lisa.analysis.nonrelational.value.TypeEnvironment; -import it.unive.lisa.analysis.types.InferredTypes; -import it.unive.lisa.checks.semantic.CheckToolWithAnalysisResults; -import it.unive.lisa.checks.semantic.SemanticCheck; -import it.unive.lisa.program.cfg.CFG; -import it.unive.lisa.program.cfg.statement.Statement; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class TimestampDependencyChecker implements - SemanticCheck>> { - - private static final Logger log = LogManager.getLogger(TimestampDependencyChecker.class); - - private static boolean isEnabled = false; - - public static void enableChecker() { - isEnabled = true; - } - - public static void disableChecker() { - isEnabled = false; - } - - public static boolean isEnabled() { - return isEnabled; - } - - @Override - public boolean visit( - CheckToolWithAnalysisResults< - SimpleAbstractState>> tool, - CFG graph, Statement node) { - - EVMCFG cfg = ((EVMCFG) graph); - - if (node instanceof Jump || node instanceof Return || node instanceof Jumpi || node instanceof Sstore - || node instanceof Sha3) - for (AnalyzedCFG>> result : tool.getResultOf(cfg)) { - AnalysisState>> analysisResult = null; - - try { - analysisResult = result.getAnalysisStateBefore(node); - } catch (SemanticException e1) { - log.error("(TimestampDependencyChecker): {}", e1.getMessage()); - } - - // Retrieve the symbolic stack from the analysis result - TaintAbstractDomain taintedStack = analysisResult.getState().getValueState(); - - // If the stack is bottom, the jump is definitely - // unreachable - if (taintedStack.isBottom()) - // Nothing to do - continue; - else { - if (node instanceof Sha3 || node instanceof Sstore || node instanceof Jumpi - || node instanceof Return) { - if (checkTaintTwoPops(taintedStack)) - raiseWarning(node, tool, cfg); - } else if (node instanceof Jump) { - if (checkTaintOnePop(taintedStack)) - raiseWarning(node, tool, cfg); - } - } - } - - return true; - } - - // 2 pop() - private boolean checkTaintTwoPops(TaintAbstractDomain taintedStack) { - TaintElement firstStackElement = taintedStack.getFirstElement(); - TaintElement secondStackElement = taintedStack.getSecondElement(); - if (secondStackElement.isBottom() || secondStackElement.isBottom()) - return false; - else if (firstStackElement.isTaint() || secondStackElement.isTaint()) - return true; - return false; - } - - // 1 pop() - private boolean checkTaintOnePop(TaintAbstractDomain taintedStack) { - TaintElement firstStackElement = taintedStack.getFirstElement(); - - if (firstStackElement.isBottom()) - return false; - else if (firstStackElement.isTaint()) - return true; - return false; - } - - private void raiseWarning(Statement sink, CheckToolWithAnalysisResults< - SimpleAbstractState>> tool, - EVMCFG cfg) { - ProgramCounterLocation sinkLoc = (ProgramCounterLocation) sink.getLocation(); - - log.debug("Timestamp attack at {} at line no. {}", sinkLoc.getPc(), - sinkLoc.getSourceCodeLine()); - - String warn = "Timestamp attack at " + ((ProgramCounterLocation) sink.getLocation()).getSourceCodeLine(); - tool.warn(warn); - MyCache.getInstance().addTimestampDependencyWarning(cfg.hashCode(), warn); - } -} diff --git a/src/main/java/it/unipr/checker/TxOriginChecker.java b/src/main/java/it/unipr/checker/TxOriginChecker.java index cb9e3b1bd..4d83e9f63 100644 --- a/src/main/java/it/unipr/checker/TxOriginChecker.java +++ b/src/main/java/it/unipr/checker/TxOriginChecker.java @@ -60,33 +60,25 @@ public boolean visit( } // Retrieve the symbolic stack from the analysis result - TaintAbstractDomain stack = analysisResult.getState().getValueState(); + TaintAbstractDomain taintedStack = analysisResult.getState().getValueState(); // If the stack is bottom, the node is definitely // unreachable - if (stack.isBottom()) + if (taintedStack.isBottom()) // Nothing to do continue; else { - TaintElement firstElem = stack.getFirstElement(); - TaintElement secondElem = stack.getSecondElement(); - if (firstElem.isBottom() || secondElem.isBottom()) + if (taintedStack.getElementAtPosition(1).isBottom() + || taintedStack.getElementAtPosition(2).isBottom()) // Nothing to do continue; else { - // Checks if either first or second element in the - // stack is tainted - if (firstElem.isTaint() || secondElem.isTaint()) { - ProgramCounterLocation jumploc = (ProgramCounterLocation) node.getLocation(); - - log.debug("Tx. Origin attack at {} at line no. {}", jumploc.getPc(), - jumploc.getSourceCodeLine()); - - String warn = "TxOrigin attack at " - + ((ProgramCounterLocation) node.getLocation()).getSourceCodeLine(); - tool.warn(warn); - MyCache.getInstance().addTxOriginWarning(cfg.hashCode(), warn); - } + if (TaintElement.isAtLeastOneTainted(taintedStack.getElementAtPosition(1), + taintedStack.getElementAtPosition(2))) + raiseWarning(node, tool, cfg); + else if (TaintElement.isAtLeastOneTop(taintedStack.getElementAtPosition(1), + taintedStack.getElementAtPosition(2))) + raisePossibleWarning(node, tool, cfg); } } } @@ -94,4 +86,35 @@ public boolean visit( return true; } + + private void raiseWarning(Statement node, CheckToolWithAnalysisResults< + SimpleAbstractState>> tool, + EVMCFG cfg) { + + ProgramCounterLocation nodeLocation = (ProgramCounterLocation) node.getLocation(); + + log.warn("[DEFINITE] Tx.Origin attack at pc {} (line {}).", + nodeLocation.getPc(), + nodeLocation.getSourceCodeLine()); + + String warn = "[DEFINITE] TxOrigin attack at " + + ((ProgramCounterLocation) node.getLocation()).getSourceCodeLine(); + tool.warn(warn); + MyCache.getInstance().addTxOriginWarning(cfg.hashCode(), warn); + } + + private void raisePossibleWarning(Statement node, CheckToolWithAnalysisResults< + SimpleAbstractState>> tool, + EVMCFG cfg) { + + ProgramCounterLocation nodeLocation = (ProgramCounterLocation) node.getLocation(); + + log.warn("[POSSIBLE] Tx.Origin attack at pc {} (line {}).", nodeLocation.getPc(), + nodeLocation.getSourceCodeLine()); + + String warn = "[POSSIBLE] Tx.Origin attack at " + + ((ProgramCounterLocation) node.getLocation()).getSourceCodeLine(); + tool.warn(warn); + MyCache.getInstance().addPossibleTxOriginWarning(cfg.hashCode(), warn); + } } \ No newline at end of file diff --git a/src/main/java/it/unipr/utils/ABIManager.java b/src/main/java/it/unipr/utils/ABIManager.java index d25c9d872..4606bbfb1 100644 --- a/src/main/java/it/unipr/utils/ABIManager.java +++ b/src/main/java/it/unipr/utils/ABIManager.java @@ -1,6 +1,5 @@ package it.unipr.utils; -import it.unipr.analysis.contract.EventKnowledge; import it.unipr.analysis.contract.Signature; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -74,6 +73,7 @@ public static Set parseEventsFromABI(Path abi) { } private static Set parseABI(Path abi, String type) { + Set stateMutabilityForbidden = Set.of("view", "pure"); Set signatures = new HashSet<>(); String abiJson; @@ -89,6 +89,12 @@ private static Set parseABI(Path abi, String type) { for (int i = 0; i < abiArray.length(); i++) { JSONObject obj = abiArray.getJSONObject(i); if (obj.has("type") && obj.getString("type").equals(type)) { + + /* We do not need to collect global variables */ + if (obj.has("stateMutability") + && stateMutabilityForbidden.contains(obj.getString("stateMutability"))) + continue; + String functionName = obj.getString("name"); JSONArray inputs = obj.getJSONArray("inputs"); JSONArray outputs = obj.optJSONArray("outputs"); @@ -118,8 +124,6 @@ private static Set parseABI(Path abi, String type) { String selector = getFunctionSelector(fullSignature); signatures.add(new Signature(functionName, type, paramTypes, outputTypes, fullSignature, selector)); - -// log.debug("[{}] {} -> {}", type, fullSignature, selector); } } return signatures; @@ -210,11 +214,6 @@ public static void main(String[] args) { log.info("Parsing events"); Set events = parseEventsFromABI(abi); verifyFunctionSelectors(events, bytecode); - - log.info("Computing events knowledge"); - for (Signature event : events) { - log.debug("{}: {}", event.getFullSignature(), EventKnowledge.getKnowledge(event.getName())); - } } catch (Exception e) { log.error("Error reading ABI or bytecode file", e); } diff --git a/src/main/java/it/unipr/utils/EVMLiSAExecutor.java b/src/main/java/it/unipr/utils/EVMLiSAExecutor.java new file mode 100644 index 000000000..6e74b6f4c --- /dev/null +++ b/src/main/java/it/unipr/utils/EVMLiSAExecutor.java @@ -0,0 +1,227 @@ +package it.unipr.utils; + +import java.util.*; +import java.util.concurrent.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class EVMLiSAExecutor { + private static final Logger log = LogManager.getLogger(EVMLiSAExecutor.class); + + private static int CORES = (Runtime.getRuntime().availableProcessors() > 1) + ? Runtime.getRuntime().availableProcessors() - 1 + : 1; + private static ExecutorService _executor = Executors.newFixedThreadPool(CORES); + + private static long tasksInQueue = 0; + private static long tasksExecuted = 0; + private static long tasksTimedOut = 0; + + /** + * Submits a task for execution in the thread pool. + * + * @param task the task to be executed + * + * @return a Future representing the pending result of the task + */ + public static Future submit(Runnable task) { + return _executor.submit(new EVMLiSAExecutorTask(task)); + } + + /** + * Submits a list of tasks for execution in the thread pool. + * + * @param tasks the list of tasks to be executed + * + * @return a list of Future objects representing the pending results of the + * tasks + */ + public static List> submitAll(List tasks) { + List> futures = new ArrayList<>(); + + for (Runnable task : tasks) + futures.add(submit(new EVMLiSAExecutorTask(task))); + + return futures; + } + + /** + * Executes a main task asynchronously and schedules additional tasks upon + * its completion. + * + * @param mainTask the main task to execute + * @param secondaryTasks additional tasks to execute after the main task + * + * @return a CompletableFuture that completes when all tasks are finished + */ + public static CompletableFuture runAsync(Runnable mainTask, Runnable... secondaryTasks) { + return runAsync(mainTask, Set.of(secondaryTasks)); + } + + /** + * Executes a main task asynchronously and schedules additional tasks upon + * its completion. + * + * @param mainTask the main task to execute + * @param secondaryTasks additional tasks to execute after the main task + * + * @return a CompletableFuture that completes when all tasks are finished + */ + public static CompletableFuture runAsync(Runnable mainTask, Set secondaryTasks) { + return CompletableFuture.runAsync(new EVMLiSAExecutorTask(mainTask), _executor) + .thenCompose(ignored -> { + List> checkerFutures = new ArrayList<>(); + for (Runnable secondaryTask : secondaryTasks) + checkerFutures + .add(CompletableFuture.runAsync(new EVMLiSAExecutorTask(secondaryTask), _executor)); + return CompletableFuture.allOf(checkerFutures.toArray(new CompletableFuture[0])); + }); + } + + /** + * Waits for the completion of all provided CompletableFutures. + * + * @param futures the list of CompletableFutures to wait for + */ + public static void awaitCompletionCompletableFutures(List> futures) { + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + } + + /** + * Waits for the completion of all provided {@code CompletableFuture}, + * applying a timeout to each future. + * + * @param futures the list of {@code CompletableFuture} instances to + * await + * @param timeout the maximum time to wait for each future to complete + * @param unit the time unit of the {@code timeout} parameter + */ + public static void awaitCompletionCompletableFutures(List> futures, long timeout, + TimeUnit unit) { + CompletableFuture.allOf(futures.stream() + .map(f -> f.orTimeout(timeout, unit) + .exceptionally(ex -> { + log.warn("Future timed out after {} {}. Skipping this task.", timeout, unit); + + synchronized (EVMLiSAExecutor.class) { + ++tasksTimedOut; + } + + return null; + })) + .toArray(CompletableFuture[]::new)).join(); + } + + /** + * Waits for the completion of all provided {@code Future} objects. + * + * @param futures the list of {@code Future} objects to wait for + */ + public static void awaitCompletionFutures(List> futures) { + for (Future future : futures) { + try { + future.get(); + } catch (ExecutionException e) { + System.err.println(JSONManager.throwNewError("Error during task execution: " + e.getMessage())); + System.exit(1); + } catch (InterruptedException ie) { + System.err.println(JSONManager.throwNewError("Interrupted during task execution: " + ie.getMessage())); + System.exit(1); + } + } + } + + /** + * Waits for the completion of all provided futures with a timeout for each. + * If a future does not complete within the specified timeout, it is + * skipped. + * + * @param futures the list of futures to wait for + * @param timeout the maximum time to wait for each future + * @param unit the time unit of the timeout argument + */ + public static void awaitCompletionFutures(List> futures, long timeout, TimeUnit unit) { + for (Future future : futures) { + try { + future.get(timeout, unit); + } catch (TimeoutException te) { + log.warn("Future timed out after {} {}. Skipping this task.", timeout, unit); + } catch (ExecutionException e) { + System.err.println(JSONManager.throwNewError("Error during task execution: " + e.getMessage())); + System.exit(1); + } catch (InterruptedException ie) { + System.err.println(JSONManager.throwNewError("Interrupted during task execution: " + ie.getMessage())); + System.exit(1); + } + } + } + + /** + * Shuts down the executor service, preventing new tasks from being + * submitted. + */ + public static void shutdown() { + log.debug("Tasks submitted: {}.", tasksExecuted); + log.debug("Tasks timed out: {}.", tasksTimedOut); + log.info("Shutting down executor."); + _executor.shutdown(); + } + + public static void setCoresAvailable(int cores) { + if (cores > Runtime.getRuntime().availableProcessors()) + cores = Runtime.getRuntime().availableProcessors() - 1; + + if (CORES == cores) + return; + + shutdown(); + CORES = Math.max(cores, 1); + _executor = Executors.newFixedThreadPool(CORES); + } + + public static int getCoresAvailable() { + return CORES; + } + + /** + * A private static class that wraps a {@link Runnable} task and manages + * task execution tracking. It increments the count of tasks in the queue + * upon creation and decrements it upon completion. + */ + private static class EVMLiSAExecutorTask implements Runnable { + private final Runnable task; + + /** + * Creates a new {@code MyTask} instance wrapping the given task. It + * increments the count of tasks in the queue upon instantiation. + * + * @param task the {@code Runnable} task to be executed. + */ + public EVMLiSAExecutorTask(Runnable task) { + this.task = task; + + synchronized (EVMLiSAExecutor.class) { + ++tasksInQueue; + ++tasksExecuted; + } + } + + /** + * Executes the wrapped task and updates the task queue count. + */ + @Override + public void run() { + task.run(); + + synchronized (EVMLiSAExecutor.class) { + --tasksInQueue; + } + + if (tasksInQueue % 10 == 0 || tasksInQueue < 100) + log.debug("{} tasks in progress, {} tasks in queue to be analyzed.", + ((tasksInQueue - CORES) > 0 ? CORES : tasksInQueue), + ((tasksInQueue - CORES) > 0 ? (tasksInQueue - CORES) : 0)); + } + } + +} \ No newline at end of file diff --git a/src/main/java/it/unipr/utils/JSONManager.java b/src/main/java/it/unipr/utils/JSONManager.java index 06e2013a9..56bbb26e7 100644 --- a/src/main/java/it/unipr/utils/JSONManager.java +++ b/src/main/java/it/unipr/utils/JSONManager.java @@ -107,6 +107,15 @@ public static JSONObject aggregateSmartContractsToJson(List contr * @return a JSON array representing the basic blocks */ public static JSONArray basicBlocksToJson(SmartContract contract) { + if (contract.getFunctionsSignature() == null) { + log.warn("Unable to generate CFG with Basic Blocks (contract.getFunctionsSignature() is null)"); + return new JSONArray(); + } + if (contract.getEventsSignature() == null) { + log.warn("Unable to generate CFG with Basic Blocks (contract.getEventsSignature() is null)"); + return new JSONArray(); + } + JSONArray blocksArray = new JSONArray(); for (BasicBlock block : contract.getBasicBlocks()) { diff --git a/src/main/java/it/unipr/utils/LiSAConfigurationManager.java b/src/main/java/it/unipr/utils/LiSAConfigurationManager.java index 44a497db8..0dac08fe7 100644 --- a/src/main/java/it/unipr/utils/LiSAConfigurationManager.java +++ b/src/main/java/it/unipr/utils/LiSAConfigurationManager.java @@ -9,6 +9,7 @@ import it.unive.lisa.conf.LiSAConfiguration; import it.unive.lisa.interprocedural.ModularWorstCaseAnalysis; import it.unive.lisa.interprocedural.callgraph.RTACallGraph; +import java.nio.file.Path; public class LiSAConfigurationManager { @@ -47,10 +48,35 @@ public static LiSAConfiguration createConfiguration(SmartContract contract, bool conf.jsonOutput = false; if (dumpResults) { - conf.analysisGraphs = LiSAConfiguration.GraphType.HTML; + conf.analysisGraphs = LiSAConfiguration.GraphType.DOT; conf.jsonOutput = true; } return conf; } + + public static LiSAConfiguration createConfiguration(Path path, boolean dumpResults) { + LiSAConfiguration conf = new LiSAConfiguration(); + conf.abstractState = new SimpleAbstractState<>(new MonolithicHeap(), + new EVMAbstractState(null), + new TypeEnvironment<>(new InferredTypes())); + conf.workdir = path.toString(); + conf.interproceduralAnalysis = new ModularWorstCaseAnalysis<>(); + conf.callGraph = new RTACallGraph(); + conf.serializeResults = false; + conf.optimize = false; + conf.useWideningPoints = false; + conf.jsonOutput = false; + + if (dumpResults) { + conf.analysisGraphs = LiSAConfiguration.GraphType.DOT; + conf.jsonOutput = true; + } + + return conf; + } + + public static LiSAConfiguration createConfiguration(Path path) { + return createConfiguration(path, false); + } } diff --git a/src/main/java/it/unipr/utils/MyCache.java b/src/main/java/it/unipr/utils/MyCache.java index fba991a9b..54338b00a 100644 --- a/src/main/java/it/unipr/utils/MyCache.java +++ b/src/main/java/it/unipr/utils/MyCache.java @@ -2,12 +2,13 @@ import it.unipr.analysis.Number; import it.unipr.analysis.StackElement; -import it.unive.lisa.program.cfg.statement.Statement; +import it.unipr.analysis.contract.Signature; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.apache.commons.collections4.map.LRUMap; import org.apache.commons.lang3.tuple.Pair; +import org.json.JSONArray; /** * Singleton class implementing a cache with an LRU (Least Recently Used) @@ -17,16 +18,22 @@ */ public class MyCache { private static MyCache _instance = null; + private static long _timesUsed = 0; + private final LRUMap, StackElement> _map; private final LRUMap _timeLostToGetStorage; + private final LRUMap _reachableFrom; + private final LRUMap> _reentrancyWarnings; + private final LRUMap> _txOriginWarnings; - private final LRUMap _reachableFrom; - private final LRUMap> _eventOrderWarnings; - private final LRUMap> _uncheckedStateUpdateWarnings; - private final LRUMap> _uncheckedExternalInfluenceWarnings; - private final LRUMap> _timestampDependencyWarnings; - public final LRUMap> _eventsExitPoints; + private final LRUMap> _possibleTxOriginWarnings; + + private final LRUMap> _randomnessDependencyWarnings; + private final LRUMap> _possibleRandomnessDependencyWarnings; + + private final LRUMap> _vulnerabilityPerFunction; + private final LRUMap> _mapEventsFunctions; /** * Retrieves the singleton instance of the cache. @@ -40,23 +47,99 @@ public static MyCache getInstance() { _instance = new MyCache(); } } + ++_timesUsed; return _instance; } + /** + * Retrieves the number of times the cache has been used. + * + * @return the count of times the cache has been used as a long value + */ + public static long getTimesUsed() { + return _timesUsed; + } + /** * Private constructor to prevent instantiation. */ private MyCache() { this._map = new LRUMap, StackElement>(500); this._timeLostToGetStorage = new LRUMap(500); - this._reentrancyWarnings = new LRUMap>(1000); - this._txOriginWarnings = new LRUMap>(1000); - this._reachableFrom = new LRUMap(2000); - this._eventOrderWarnings = new LRUMap>(1000); - this._uncheckedStateUpdateWarnings = new LRUMap>(1000); - this._uncheckedExternalInfluenceWarnings = new LRUMap>(1000); - this._eventsExitPoints = new LRUMap>(2000); - this._timestampDependencyWarnings = new LRUMap>(1000); + this._reachableFrom = new LRUMap(5000); + + this._reentrancyWarnings = new LRUMap>(5000); + + this._txOriginWarnings = new LRUMap>(5000); + this._possibleTxOriginWarnings = new LRUMap>(5000); + + this._randomnessDependencyWarnings = new LRUMap>(5000); + this._possibleRandomnessDependencyWarnings = new LRUMap>(5000); + + this._vulnerabilityPerFunction = new LRUMap<>(10000); + this._mapEventsFunctions = new LRUMap<>(10000); + } + + public void addMapEventsFunctions(Signature event, Signature function) { + synchronized (_mapEventsFunctions) { + _mapEventsFunctions + .computeIfAbsent(event, k -> Collections.synchronizedSet(new HashSet<>())) + .add(function); + } + } + + public Set getMapEventsFunctions(Signature event) { + synchronized (_mapEventsFunctions) { + return (_mapEventsFunctions.get(event) != null) + ? _mapEventsFunctions.get(event) + : Set.of(); + } + } + + /** + * Records a vulnerability warning for a specific function identified by its + * key. + *

+ * This method ensures thread-safe access to the internal map, creating a + * new synchronized Set if none exists for the given key, and then adds the + * warning object to that set. + * + * @param key the identifier of the function (e.g., CFG hashcode or + * program counter) + * @param warning the vulnerability description or warning object to record + */ + public void addVulnerabilityPerFunction(Integer key, Object warning) { + synchronized (_vulnerabilityPerFunction) { + _vulnerabilityPerFunction + .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>())) + .add(warning); + } + } + + /** + * Retrieves all recorded vulnerability warnings for a specific function. + *

+ * This method performs a thread-safe lookup in the internal map. If no + * warnings have been recorded for the given key, it returns an empty + * JSONArray; otherwise, it returns a JSONArray containing all stored + * warnings. + * + * @param key the identifier of the function whose warnings are requested + * + * @return a JSONArray of warning objects for the given function key, or an + * empty JSONArray if none are present + */ + public JSONArray getVulnerabilityPerFunction(Integer key) { + synchronized (_vulnerabilityPerFunction) { + if (_vulnerabilityPerFunction.get(key) == null) + return new JSONArray(); + + JSONArray results = new JSONArray(); + for (Object warning : _vulnerabilityPerFunction.get(key)) { + results.put(warning); + } + return results; + } } /** @@ -77,7 +160,7 @@ public void put(Pair key, StackElement value) * * @param key the key, a {@link Pair} of {@link String} and * {@link it.unipr.analysis.Number}. - * + * * @return the value associated with the key, or {@code null} if the key is * not in the cache. */ @@ -151,7 +234,7 @@ public void addReentrancyWarning(Integer key, Object warning) { * * @param key the key identifying the smart contract or entity whose * warnings are to be retrieved - * + * * @return the number of warnings associated with the key, or 0 if none * exist */ @@ -161,157 +244,6 @@ public int getReentrancyWarnings(Integer key) { } } - /** - * Adds an event order warning for the specified key. If no warnings are - * associated with the key, a new set is created and the warning is added to - * it. This method is thread-safe. - * - * @param key the key identifying the smart contract or entity for which - * the warning applies - * @param warning the warning object to be added - */ - public void addEventOrderWarning(Integer key, Object warning) { - synchronized (_eventOrderWarnings) { - _eventOrderWarnings - .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>())) - .add(warning); - } - } - - /** - * Retrieves the number of event order warnings associated with the - * specified key. If no warnings are associated with the key, the method - * returns 0. This method is thread-safe. - * - * @param key the key identifying the smart contract or entity whose - * warnings are to be retrieved - * - * @return the number of warnings associated with the key, or 0 if none - * exist - */ - public int getEventOrderWarnings(Integer key) { - synchronized (_eventOrderWarnings) { - return (_eventOrderWarnings.get(key) != null) ? _eventOrderWarnings.get(key).size() : 0; - } - } - - /** - * Adds an unchecked state update warning for the specified key. If no - * warnings are associated with the key, a new set is created and the - * warning is added to it. This method is thread-safe. - * - * @param key the key identifying the smart contract or entity for which - * the warning applies - * @param warning the warning object to be added - */ - public void addUncheckedStateUpdateWarning(Integer key, Object warning) { - synchronized (_uncheckedStateUpdateWarnings) { - _uncheckedStateUpdateWarnings - .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>())) - .add(warning); - } - } - - /** - * Retrieves the number of unchecked state update warnings associated with - * the specified key. If no warnings are associated with the key, the method - * returns 0. This method is thread-safe. - * - * @param key the key identifying the smart contract or entity whose - * warnings are to be retrieved - * - * @return the number of warnings associated with the key, or 0 if none - * exist - */ - public int getUncheckedStateUpdateWarnings(Integer key) { - synchronized (_uncheckedStateUpdateWarnings) { - return (_uncheckedStateUpdateWarnings.get(key) != null) ? _uncheckedStateUpdateWarnings.get(key).size() : 0; - } - } - - /** - * Adds an unchecked external influence warning for the specified key. If no - * warnings are associated with the key, a new set is created and the - * warning is added to it. This method is thread-safe. - * - * @param key the key identifying the smart contract or entity for which - * the warning applies - * @param warning the warning object to be added - */ - public void addUncheckedExternalInfluenceWarning(Integer key, Object warning) { - synchronized (_uncheckedExternalInfluenceWarnings) { - _uncheckedExternalInfluenceWarnings - .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>())) - .add(warning); - } - } - - /** - * Retrieves the number of unchecked external influence warnings associated - * with the specified key. If no warnings are associated with the key, the - * method returns 0. This method is thread-safe. - * - * @param key the key identifying the smart contract or entity whose - * warnings are to be retrieved - * - * @return the number of warnings associated with the key, or 0 if none - * exist - */ - public int getUncheckedExternalInfluenceWarnings(Integer key) { - synchronized (_uncheckedExternalInfluenceWarnings) { - return (_uncheckedExternalInfluenceWarnings.get(key) != null) - ? _uncheckedExternalInfluenceWarnings.get(key).size() - : 0; - } - } - - /** - * Adds an event exit point associated with a given statement. If the - * statement does not already have an associated set of event exit points, a - * new synchronized HashSet is created. This method is thread-safe. - * - * @param key The statement representing the exit point. - * @param signature The event signature to associate with the statement. - */ - public void addEventExitPoint(Statement key, String signature) { - synchronized (_eventsExitPoints) { - _eventsExitPoints - .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>())) - .add(signature); - } - } - - /** - * Retrieves the set of event exit points associated with a given statement. - * If the statement has no associated exit points, an empty set is returned. - * This method is thread-safe. - * - * @param key The statement whose event exit points are being queried. - * - * @return A set of event signatures associated with the given statement, or - * an empty set if none exist. - */ - public Set getEventExitPoints(Statement key) { - synchronized (_eventsExitPoints) { - return _eventsExitPoints.get(key) == null ? new HashSet<>() : _eventsExitPoints.get(key); - } - } - - /** - * Checks whether there are event exit points associated with a given - * statement. - * - * @param key The statement to check. - * - * @return {@code true} if the statement has associated event exit points, - * {@code false} otherwise. - */ - public boolean containsEventExitPoints(Statement key) { - synchronized (_eventsExitPoints) { - return _eventsExitPoints.get(key) != null; - } - } - /** * Adds or updates the reachability status of a specific key in the * reachability map. @@ -330,10 +262,10 @@ public void addReachableFrom(String key, boolean isReachableFrom) { * Checks if a specific key is marked as reachable in the reachability map. * * @param key the key representing the element to check - * + * * @return {@code true} if the key is marked as reachable, {@code false} * otherwise - * + * * @throws NullPointerException if the key does not exist in the map */ public boolean isReachableFrom(String key) { @@ -346,7 +278,7 @@ public boolean isReachableFrom(String key) { * Checks if a specific key exists in the reachability map. * * @param key the key representing the element to check - * + * * @return {@code true} if the key exists in the map, {@code false} * otherwise */ @@ -380,7 +312,7 @@ public void addTxOriginWarning(Integer key, Object warning) { * * @param key the key identifying the smart contract or entity whose * warnings are to be retrieved - * + * * @return the number of warnings associated with the key, or 0 if none * exist */ @@ -391,7 +323,7 @@ public int getTxOriginWarnings(Integer key) { } /** - * Adds a timestamp dependency warning for the specified key. If no warnings + * Adds a possible tx origin warning for the specified key. If no warnings * are associated with the key, a new set is created and the warning is * added to it. This method is thread-safe. * @@ -399,28 +331,101 @@ public int getTxOriginWarnings(Integer key) { * the warning applies * @param warning the warning object to be added */ - public void addTimestampDependencyWarning(Integer key, Object warning) { - synchronized (_timestampDependencyWarnings) { - _timestampDependencyWarnings + public void addPossibleTxOriginWarning(Integer key, Object warning) { + synchronized (_possibleTxOriginWarnings) { + _possibleTxOriginWarnings .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>())) .add(warning); } } /** - * Retrieves the number of timestamp dependency warnings associated with the + * Retrieves the number of possible tx origin warnings associated with the * specified key. If no warnings are associated with the key, the method * returns 0. This method is thread-safe. * * @param key the key identifying the smart contract or entity whose * warnings are to be retrieved + * + * @return the number of possible warnings associated with the key, or 0 if + * none exist + */ + public int getPossibleTxOriginWarnings(Integer key) { + synchronized (_possibleTxOriginWarnings) { + return (_possibleTxOriginWarnings.get(key) != null) ? _possibleTxOriginWarnings.get(key).size() : 0; + } + } + + /** + * Adds a randomness dependency warning for the specified key. If no + * warnings are associated with the key, a new set is created and the + * warning is added to it. This method is thread-safe. + * + * @param key the key identifying the smart contract or entity for which + * the warning applies + * @param warning the warning object to be added + */ + public void addRandomnessDependencyWarning(Integer key, Object warning) { + synchronized (_randomnessDependencyWarnings) { + _randomnessDependencyWarnings + .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>())) + .add(warning); + } + } + + /** + * Retrieves the number of randomness dependency warnings associated with + * the specified key. If no warnings are associated with the key, the method + * returns 0. This method is thread-safe. * + * @param key the key identifying the smart contract or entity whose + * warnings are to be retrieved + * * @return the number of warnings associated with the key, or 0 if none * exist */ - public int getTimestampDependencyWarnings(Integer key) { - synchronized (_timestampDependencyWarnings) { - return (_timestampDependencyWarnings.get(key) != null) ? _timestampDependencyWarnings.get(key).size() : 0; + public int getRandomnessDependencyWarnings(Integer key) { + synchronized (_randomnessDependencyWarnings) { + return (_randomnessDependencyWarnings.get(key) != null) ? _randomnessDependencyWarnings.get(key).size() : 0; + } + } + + /** + * Adds a warning indicating a possible randomness dependency to the + * internal collection. The method ensures thread safety during modification + * of warnings collection. + * + * @param key an integer key representing the category or type of the + * warning + * @param warning an object representing the warning message or detail to be + * added + */ + public void addPossibleRandomnessDependencyWarning(Integer key, Object warning) { + synchronized (_possibleRandomnessDependencyWarnings) { + _possibleRandomnessDependencyWarnings + .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>())) + .add(warning); + } + } + + /** + * Retrieves the number of possible randomness dependency warnings + * associated with a given key. The method checks the internal map for the + * specified key and returns the size of the list of warnings if the key + * exists; otherwise, it returns zero. + * + * @param key the key used to identify the list of randomness dependency + * warnings in the internal map. It can be null. + * + * @return the number of randomness dependency warnings associated with the + * specified key. Returns 0 if the key is not present or there + * are no warnings associated with it. + */ + public int getPossibleRandomnessDependencyWarnings(Integer key) { + synchronized (_possibleRandomnessDependencyWarnings) { + return (_possibleRandomnessDependencyWarnings.get(key) != null) + ? _possibleRandomnessDependencyWarnings.get(key).size() + : 0; } } } \ No newline at end of file diff --git a/src/main/java/it/unipr/utils/VulnerabilitiesObject.java b/src/main/java/it/unipr/utils/VulnerabilitiesObject.java index ff6d0ffdd..c675ef633 100644 --- a/src/main/java/it/unipr/utils/VulnerabilitiesObject.java +++ b/src/main/java/it/unipr/utils/VulnerabilitiesObject.java @@ -1,80 +1,56 @@ package it.unipr.utils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import it.unipr.cfg.EVMCFG; import org.json.JSONObject; /** - * Represents an object that stores information about potential smart contract - * vulnerabilities, including reentrancy, timestamp dependency, and tx. origin - * usage. + * The {@code VulnerabilitiesObject} class represents a data model to hold + * various security vulnerability scores within a system. It provides methods to + * manage and build these scores, including utilities for JSON representation. */ public class VulnerabilitiesObject { - private static final Logger log = LogManager.getLogger(VulnerabilitiesObject.class); - private int reentrancy; - private int timestamp; + + private int randomness; + private int possibleRandomness; + private int txOrigin; + private int possibleTxOrigin; + private JSONObject json; - /** - * Creates a new {@code VulnerabilitiesObject} with default values (-1) for - * all vulnerabilities. - */ private VulnerabilitiesObject() { - this.reentrancy = -1; - this.timestamp = -1; - this.txOrigin = -1; + this.reentrancy = 0; + + this.randomness = 0; + this.possibleRandomness = 0; + + this.txOrigin = 0; + this.possibleTxOrigin = 0; + this.json = new JSONObject(); } - /** - * Creates a new {@code VulnerabilitiesObject} with specified values. - * - * @param reentrancy the reentrancy vulnerability score - * @param timestamp the timestamp dependency vulnerability score - * @param txOrigin the tx.origin vulnerability score - * @param json the JSON representation of the object - */ - private VulnerabilitiesObject(int reentrancy, int timestamp, int txOrigin, JSONObject json) { + private VulnerabilitiesObject(int reentrancy, + int randomness, int possibleRandomness, + int txOrigin, int possibleTxOrigin, + JSONObject json) { + this.reentrancy = reentrancy; - this.timestamp = timestamp; - this.txOrigin = txOrigin; - this.json = json; - if (reentrancy != -1) - this.json.put("reentrancy", this.reentrancy); - if (timestamp != -1) - this.json.put("timestamp_dependency", this.timestamp); - if (txOrigin != -1) - this.json.put("tx_origin", this.txOrigin); - } + this.randomness = randomness; + this.possibleRandomness = possibleRandomness; - /** - * Returns the reentrancy vulnerability score. - * - * @return the reentrancy score - */ - public int getReentrancy() { - return reentrancy; - } + this.txOrigin = txOrigin; + this.possibleTxOrigin = possibleTxOrigin; - /** - * Returns the timestamp dependency vulnerability score. - * - * @return the timestamp dependency score - */ - public int getTimestamp() { - return timestamp; - } + this.json = json; - /** - * Returns the tx .origin vulnerability score. - * - * @return the tx. origin score - */ - public int getTxOrigin() { - return txOrigin; + this.json.put("reentrancy", this.reentrancy); + this.json.put("randomness_dependency", this.randomness); + this.json.put("randomness_dependency_possible", this.possibleRandomness); + this.json.put("tx_origin", this.txOrigin); + this.json.put("tx_origin_possible", this.possibleTxOrigin); } /** @@ -99,14 +75,26 @@ public VulnerabilitiesObject reentrancy(int reentrancy) { } /** - * Sets the timestamp dependency vulnerability score. + * Sets the randomness dependency vulnerability score. + * + * @param randomness the randomness dependency score + * + * @return the updated {@code VulnerabilitiesObject} instance + */ + public VulnerabilitiesObject randomness(int randomness) { + this.randomness = randomness; + return this; + } + + /** + * Sets the possible randomness dependency vulnerability score. * - * @param timestamp the timestamp dependency score + * @param possibleRandomness the possible randomness dependency score * * @return the updated {@code VulnerabilitiesObject} instance */ - public VulnerabilitiesObject timestamp(int timestamp) { - this.timestamp = timestamp; + public VulnerabilitiesObject possibleRandomness(int possibleRandomness) { + this.possibleRandomness = possibleRandomness; return this; } @@ -122,13 +110,54 @@ public VulnerabilitiesObject txOrigin(int txOrigin) { return this; } + /** + * Sets the possible tx. origin vulnerability score. + * + * @param possibleTxOrigin the tx. origin score + * + * @return the updated {@code VulnerabilitiesObject} instance + */ + public VulnerabilitiesObject possibleTxOrigin(int possibleTxOrigin) { + this.possibleTxOrigin = possibleTxOrigin; + return this; + } + + /** + * Builds a {@link VulnerabilitiesObject} from the given EVM control-flow + * graph (CFG). This method retrieves various vulnerability warnings from + * the cache based on the CFG's hash code and compiles them into a + * {@link VulnerabilitiesObject}. + * + * @param cfg the EVM control-flow graph from which to extract vulnerability + * data + * + * @return a {@link VulnerabilitiesObject} containing detected + * vulnerabilities + */ + public static VulnerabilitiesObject buildFromCFG(EVMCFG cfg) { + return VulnerabilitiesObject.newVulnerabilitiesObject() + .reentrancy( + MyCache.getInstance().getReentrancyWarnings(cfg.hashCode())) + .txOrigin(MyCache.getInstance().getTxOriginWarnings(cfg.hashCode())) + .possibleTxOrigin(MyCache.getInstance() + .getPossibleTxOriginWarnings(cfg.hashCode())) + .randomness(MyCache.getInstance() + .getRandomnessDependencyWarnings(cfg.hashCode())) + .possibleRandomness(MyCache.getInstance() + .getPossibleRandomnessDependencyWarnings(cfg.hashCode())) + .build(); + } + /** * Builds a new {@code VulnerabilitiesObject} with the specified values. * * @return a new {@code VulnerabilitiesObject} instance */ public VulnerabilitiesObject build() { - return new VulnerabilitiesObject(reentrancy, timestamp, txOrigin, json); + return new VulnerabilitiesObject(reentrancy, + randomness, possibleRandomness, + txOrigin, possibleTxOrigin, + json); } /** @@ -150,14 +179,4 @@ public String toString() { return toJson().toString(4); } - /** - * Logs the vulnerability scores of the given {@code VulnerabilitiesObject}. - * - * @param vulnerabilities the vulnerabilities object to log - */ - public static void printVulnerabilities(VulnerabilitiesObject vulnerabilities) { - log.info("Reentrancy: {}", vulnerabilities.getReentrancy()); - log.info("Timestamp dependency: {}", vulnerabilities.getTimestamp()); - log.info("Tx.Origin: {}", vulnerabilities.getTxOrigin()); - } } \ No newline at end of file diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 4ac48d0d6..151325058 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -2,7 +2,7 @@ - + @@ -13,4 +13,4 @@ - \ No newline at end of file + diff --git a/src/test/java/it/unipr/analysis/cron/semantics/TimestampDependencyAbstractSemanticsTest.java b/src/test/java/it/unipr/analysis/cron/semantics/RandomnessDependencyAbstractSemanticsTest.java similarity index 97% rename from src/test/java/it/unipr/analysis/cron/semantics/TimestampDependencyAbstractSemanticsTest.java rename to src/test/java/it/unipr/analysis/cron/semantics/RandomnessDependencyAbstractSemanticsTest.java index c9eae4b12..ed629534e 100644 --- a/src/test/java/it/unipr/analysis/cron/semantics/TimestampDependencyAbstractSemanticsTest.java +++ b/src/test/java/it/unipr/analysis/cron/semantics/RandomnessDependencyAbstractSemanticsTest.java @@ -2,8 +2,8 @@ import it.unipr.analysis.cron.CronConfiguration; import it.unipr.analysis.cron.EVMBytecodeAnalysisExecutor; +import it.unipr.analysis.taint.RandomnessDependencyAbstractDomain; import it.unipr.analysis.taint.TaintAbstractDomain; -import it.unipr.analysis.taint.TimestampDependencyAbstractDomain; import it.unive.lisa.AnalysisSetupException; import it.unive.lisa.analysis.SimpleAbstractState; import it.unive.lisa.analysis.heap.MonolithicHeap; @@ -20,7 +20,7 @@ * as if, if-else, while, etc. Operations with orphan jumps, marked as NPBJ (No * Push Before Jump), are also tested in specific test cases. */ -public class TimestampDependencyAbstractSemanticsTest extends EVMBytecodeAnalysisExecutor { +public class RandomnessDependencyAbstractSemanticsTest extends EVMBytecodeAnalysisExecutor { private static final boolean GENERATE_CFG_FOR_ALL_TESTS = false; /** @@ -52,7 +52,7 @@ private static CronConfiguration createConfiguration(String testDir, String subD } conf.abstractState = new SimpleAbstractState>( - new MonolithicHeap(), new TimestampDependencyAbstractDomain(), + new MonolithicHeap(), new RandomnessDependencyAbstractDomain(), new TypeEnvironment<>(new InferredTypes())); conf.callGraph = new RTACallGraph(); conf.interproceduralAnalysis = new ModularWorstCaseAnalysis<>(); diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml index e13c8e911..5f31aa362 100644 --- a/src/test/resources/log4j2.xml +++ b/src/test/resources/log4j2.xml @@ -2,7 +2,7 @@ - +