Skip to content

Commit

Permalink
Moved code extraction logic outside of the template
Browse files Browse the repository at this point in the history
  • Loading branch information
forefy committed Mar 8, 2024
1 parent 97339f9 commit 9abe6e5
Show file tree
Hide file tree
Showing 31 changed files with 168 additions and 153 deletions.
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ install:
$(MAKE) uninstall
pip3 install .

.PHONY: install-local
install-local:
$(MAKE) uninstall-local
.PHONY: install-poetry
install-poetry:
$(MAKE) uninstall-poetry
poetry build
poetry run pip install eburger
poetry run pip install .

.PHONY: pytest
pytest:
Expand All @@ -17,6 +17,6 @@ pytest:
uninstall:
pip3 uninstall -y eburger

.PHONY: uninstall-local
uninstall-local:
.PHONY: uninstall-poetry
uninstall-poetry:
poetry run pip uninstall -y eburger
17 changes: 11 additions & 6 deletions eburger/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)
from eburger.utils.helpers import (
construct_solc_cmdline,
get_eburger_version,
get_filename_from_path,
is_valid_json,
run_command,
Expand All @@ -41,6 +42,10 @@


def main():
if args.version:
print(get_eburger_version())
sys.exit(0)

if not args.solidity_file_or_folder and not args.ast_json_file:
args.solidity_file_or_folder = "."

Expand Down Expand Up @@ -114,7 +119,7 @@ def main():
) # Protection against analysis of files inside the output path
with open(args.ast_json_file, "r") as f:
ast_json = json.load(f)
ast_json, src_file_list = reduce_json(ast_json)
ast_json, src_paths = reduce_json(ast_json)
output_path = settings.outputs_dir / f"{filename}.json"

save_as_json(output_path, ast_json)
Expand Down Expand Up @@ -161,7 +166,7 @@ def main():
sample_file_path = find_and_read_sol_file(args.solidity_file_or_folder)
filename, output_filename = get_filename_from_path(sample_file_path)
ast_json = get_foundry_ast_json(forge_out_dir)
ast_json, src_file_list = reduce_json(ast_json)
ast_json, src_paths = reduce_json(ast_json)
save_as_json(output_filename, ast_json)

# Hardhat compilation flow
Expand Down Expand Up @@ -211,7 +216,7 @@ def main():
filename, output_filename = get_filename_from_path(sample_file_path)

ast_json = get_hardhat_ast_json(hardhat_out_dir)
ast_json, src_file_list = reduce_json(ast_json)
ast_json, src_paths = reduce_json(ast_json)
save_as_json(output_filename, ast_json)

# solc compilation flow
Expand Down Expand Up @@ -243,7 +248,7 @@ def main():
if solc_cmdline is None:
log("error", "Error constructing solc command line")

solc_compile_res, _ = run_command(solc_cmdline, live_output=True)
solc_compile_res, _ = run_command(solc_cmdline, live_output=args.debug)

# We continue as long as solc compiled something
if not is_valid_json(solc_compile_res):
Expand All @@ -260,7 +265,7 @@ def main():
error_string,
)
solc_compile_res_parsed = json.loads("".join(solc_compile_res))
ast_json, src_file_list = reduce_json(solc_compile_res_parsed)
ast_json, src_paths = reduce_json(solc_compile_res_parsed)
save_as_json(output_filename, solc_compile_res_parsed)

# Parse AST
Expand All @@ -273,7 +278,7 @@ def main():
settings.templates_directories.append(Path(template_path))
log("info", f"Templates path: {Path(template_path)}")

insights = process_files_concurrently(ast_roots, src_file_list)
insights = process_files_concurrently(ast_roots, src_paths)

if insights:
analysis_output = {}
Expand Down
4 changes: 2 additions & 2 deletions eburger/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def extract_file_list_from_ast(ast_data):
return list(ast_data["sources"].keys())
return []

original_file_list = extract_file_list_from_ast(ast_json)
src_paths = extract_file_list_from_ast(ast_json)

# Function to remove keys in-place from a dictionary
def remove_keys_in_place(dictionary):
Expand All @@ -38,4 +38,4 @@ def remove_keys_in_place(dictionary):
if section in ast_json:
remove_keys_in_place(ast_json[section])

return ast_json, original_file_list
return ast_json, src_paths
69 changes: 0 additions & 69 deletions eburger/template_utils.py
Original file line number Diff line number Diff line change
@@ -1,74 +1,5 @@
from pathlib import Path
import re
from typing import Union
from eburger import settings
from eburger.utils.cli_args import args


def parse_code_highlight(node: dict, src_file_list: list) -> tuple[str, str, str]:
"""
Extracts and highlights a specific code snippet from a source file based on a given AST node.
Parameters:
- node (dict): The AST node containing the 'src' attribute with location details.
- src_file_list (list): A list of source files associated with the AST.
Returns:
- Tuple (str, str, str): A tuple containing:
1. The file path of the source file where the code snippet is located.
2. A string representation of the line and column range in the file for the code snippet.
3. The extracted code snippet itself.
The 'src' attribute in the node is expected to be in the format 'start_offset:length:file_index'.
The function calculates the exact location of the code in the file and extracts it along with its
line and column position. If the location exceeds the content of the file or is not found, appropriate
messages and null values are returned.
"""

src_location = node.get("src", "")
file_index = int(src_location.split(":")[2])

if file_index < len(src_file_list):
project_relative_file_name = src_file_list[file_index]
else:
project_relative_file_name = src_file_list[0]

file_path = str(Path(settings.project_root / project_relative_file_name).resolve())

if args.relative_file_paths:
result_file_path_uri = project_relative_file_name
else:
result_file_path_uri = file_path

start_offset, length, _ = map(int, src_location.split(":"))

file_content = None
with open(file_path, "r") as file:
file_content = file.read()

if file_content is None:
return "File unreadable.", None, None

if start_offset + length > len(file_content):
return "The start offset and length exceed the file content.", None, None

vulnerable_code = file_content[start_offset : start_offset + length]
# Find the line number and character positions
current_offset = 0
line_number = 1
for line in file_content.split("\n"):
end_offset = current_offset + len(line)
if current_offset <= start_offset < end_offset:
start_char = start_offset - current_offset
end_char = min(start_char + length, len(line))
return (
result_file_path_uri,
f"Line {line_number} Columns {start_char + 1}-{end_char + 1}",
vulnerable_code,
)
current_offset = end_offset + 1 # +1 for the newline character
line_number += 1
return "Location not found in file", None, None


def join_lists_unique(list1: list, list2: list) -> list:
Expand Down
6 changes: 3 additions & 3 deletions eburger/templates/emit_after_external_call.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 1.0.0
version: 1.0.5
name: "Emit After External Call"
severity: "Low"
precision: "Medium"
Expand Down Expand Up @@ -58,7 +58,7 @@ python: |
if emitted_event_vars and function_call_found:
for emitted_event_var in emitted_event_vars:
if emitted_event_var in mutable_state_variables:
file_path, lines, vuln_code = parse_code_highlight(stmt, src_file_list)
results.append({"file": file_path, "lines": lines, "code": vuln_code})
results.append(stmt)
# reset preceding function call check
function_call_found = False
5 changes: 2 additions & 3 deletions eburger/templates/missing_reentrancy_guard.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 1.0.0
version: 1.0.5
name: "Missing Reentracy Guard"
severity: "Low"
precision: "Low"
Expand Down Expand Up @@ -41,5 +41,4 @@ python: |
call_nodes = get_nodes_by_signature(function_node_body, "function (bytes memory) payable returns (bool,bytes memory)")
if call_nodes:
file_path, lines, vuln_code = parse_code_highlight(function_node, src_file_list)
results.append({"file": file_path, "lines": lines, "code": vuln_code})
results.append(function_node)
5 changes: 2 additions & 3 deletions eburger/templates/missing_zero_address_check.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 1.0.0
version: 1.0.5
name: "Missing Zero Address Check"
severity: "Low"
precision: "Medium"
Expand Down Expand Up @@ -55,5 +55,4 @@ python: |
break
if not zero_address_check_found:
file_path, lines, vuln_code = parse_code_highlight(address_node, src_file_list)
results.append({"file": file_path, "lines": lines, "code": vuln_code})
results.append(address_node)
5 changes: 2 additions & 3 deletions eburger/templates/modifier_without_proper_enforcement.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 1.0.0
version: 1.0.5
name: "Modifier Without Proper Enforcement"
severity: "High"
precision: "Low"
Expand Down Expand Up @@ -42,5 +42,4 @@ python: |
break
if not enforcement_found:
file_path, lines, vuln_code = parse_code_highlight(mod_node, src_file_list)
results.append({"file": file_path, "lines": lines, "code": vuln_code})
results.append(mod_node)
5 changes: 2 additions & 3 deletions eburger/templates/tx_origin_used_for_access_control.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 1.0.0
version: 1.0.5
name: "tx.origin Used for Access Control"
severity: "Low"
precision: "Medium"
Expand Down Expand Up @@ -33,5 +33,4 @@ python: |
and right_expr.get("expression", {}).get("name") == "tx"
and right_expr.get("memberName") == "origin"
):
file_path, lines, vuln_code = parse_code_highlight(function_call_node, src_file_list)
results.append({"file": file_path, "lines": lines, "code": vuln_code})
results.append(function_call_node)
5 changes: 2 additions & 3 deletions eburger/templates/unbounded_loop.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 1.0.0
version: 1.0.5
name: "Unbounded Loop"
severity: "Low"
precision: "Low"
Expand Down Expand Up @@ -47,5 +47,4 @@ python: |
is_state_variable = True
break
if is_state_variable:
file_path, lines, vuln_code = parse_code_highlight(loop, src_file_list)
results.append({"file": file_path, "lines": lines, "code": vuln_code})
results.append(loop)
5 changes: 2 additions & 3 deletions eburger/templates/unchecked_call_return.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 1.0.0
version: 1.0.5
name: "Unchecked Call Return"
severity: "Low"
precision: "Low"
Expand Down Expand Up @@ -59,5 +59,4 @@ python: |
call_node_vulnerable = False
if call_node_vulnerable:
file_path, lines, vuln_code = parse_code_highlight(call_node, src_file_list)
results.append({"file": file_path, "lines": lines, "code": vuln_code})
results.append(call_node)
5 changes: 2 additions & 3 deletions eburger/templates/unchecked_chainlink_oracle_price.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 1.0.4
version: 1.0.5
name: "Unchecked Chainlink Oracle Price"
severity: "Medium"
precision: "Medium"
Expand All @@ -21,5 +21,4 @@ python: |
continue
if not function_def_has_following_check_statements(function_def, id_key="memberName"):
file_path, lines, vuln_code = parse_code_highlight(function_def, src_file_list)
results.append({"file": file_path, "lines": lines, "code": vuln_code})
results.append(member_access_node)
4 changes: 2 additions & 2 deletions eburger/templates/use_of_approve_with_max_allowance.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
version: 1.0.5
name: "Use of approve with Max Allowance"
severity: "Low"
precision: "High"
Expand Down Expand Up @@ -31,5 +32,4 @@ python: |
continue
# if we got here, its vulnerable
file_path, lines, vuln_code = parse_code_highlight(function_call, src_file_list)
results.append({"file": file_path, "lines": lines, "code": vuln_code})
results.append(function_call)
5 changes: 2 additions & 3 deletions eburger/templates/use_of_transfer_or_send_on_payable.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 1.0.0
version: 1.0.5
name: "Use of transfer or send on a payable address"
severity: "Medium"
precision: "Medium"
Expand Down Expand Up @@ -26,5 +26,4 @@ python: |
# Ensure the call is on a payable address
if function_call_expr_node.get("expression", {}).get("typeDescriptions", {}).get("typeString") == "address payable":
file_path, lines, vuln_code = parse_code_highlight(function_call_node, src_file_list)
results.append({"file": file_path, "lines": lines, "code": vuln_code})
results.append(function_call_node)
5 changes: 2 additions & 3 deletions eburger/templates/use_of_unsafe_mint.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 1.0.0
version: 1.0.5
name: "Usage of unsafe _mint"
severity: "Medium"
precision: "High"
Expand All @@ -23,5 +23,4 @@ python: |
if erc721_imports:
for function_call_node in mint_function_calls:
if function_call_node.get("expression", {}).get("name") == "_mint":
file_path, lines, vuln_code = parse_code_highlight(function_call_node, src_file_list)
results.append({"file": file_path, "lines": lines, "code": vuln_code})
results.append(function_call_node)
8 changes: 8 additions & 0 deletions eburger/utils/cli_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@
help="Output file paths in relative format rather than full paths",
)

parser.add_argument(
"-v",
"--version",
dest="version",
action="store_true",
help="Print current eburger version",
)

# Only here to allow "." as an argument, ignored in the rest of the code.
parser.add_argument(
"default_solidity_file_or_folder",
Expand Down
Loading

0 comments on commit 9abe6e5

Please sign in to comment.