Fix execution paths generated by group-size detector
detect_missing_tx_field_validations and search_paths already address the issues with the generation of execution paths.
https://github.com/crytic/tealer/blob/c590caacfb33aa1f68498b2cb596701e7dd2fd18/tealer/detectors/utils.py#L51-L229
group-size detector implements a function to generate execution paths. This function does not address the recently uncovered issues.
https://github.com/crytic/tealer/blob/c590caacfb33aa1f68498b2cb596701e7dd2fd18/tealer/detectors/groupsize.py#L119-L164
The group-size could be updated to use the detect_missing_tx_field_validations
"""Detector for finding execution paths missing GroupSize check."""
from typing import List, TYPE_CHECKING
from functools import lru_cache
from tealer.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DetectorType,
)
from tealer.teal.basic_blocks import BasicBlock
from tealer.teal.instructions.instructions import (
Gtxn,
Gtxna,
Gtxnas,
Gtxns,
Gtxnsa,
Gtxnsas,
)
from tealer.teal.teal import Teal
from tealer.utils.algorand_constants import MAX_GROUP_SIZE
from tealer.utils.analyses import is_int_push_ins
from tealer.analyses.utils.stack_ast_builder import construct_stack_ast, UnknownStackValue
from tealer.detectors.utils import (
detect_missing_tx_field_validations,
detector_terminal_description
)
if TYPE_CHECKING:
from tealer.utils.output import SupportedOutput
from tealer.teal.instructions.instructions import Instruction
from tealer.teal.context.block_transaction_context import BlockTransactionContext
class MissingGroupSize(AbstractDetector): # pylint: disable=too-few-public-methods
# ...
@lru_cache(maxsize=None)
@staticmethod
def _accessed_using_absolute_index(bb: BasicBlock) -> bool:
"""Return True if a instruction in bb access a field using absolute index
a. gtxn t f, gtxna t f i, gtxnas t f,
b. gtxns f, gtxnsa f i, gtxnsas f
Instructions in (a) take transaction index as a immediate argument.
Return True if bb contains any one of those instructions.
Instructions in (b) take transaction index from the stack.
`gtxns f` and `gtxnsa f i` take only one argument and it is the transaction index.
`gtxnsas f` takes two arguments and transaction index is the first argument.
Return True if the transaction index is pushed by an int instruction.
"""
stack_gtxns_ins: List["Instruction"] = []
for ins in bb.instructions:
if isinstance(ins, (Gtxn, Gtxna, Gtxnas)):
return True
if isinstance(ins, (Gtxns, Gtxnsa, Gtxnsas)):
stack_gtxns_ins.append(ins)
if not stack_gtxns_ins:
return False
ast_values = construct_stack_ast(bb)
for ins in stack_gtxns_ins:
index_value = ast_values[ins].args[0]
if isinstance(index_value, UnknownStackValue):
continue
is_int, _ = is_int_push_ins(index_value.instruction)
if is_int:
return True
return False
def detect(self) -> "SupportedOutput":
"""Detect execution paths with missing GroupSize check.
Returns:
ExecutionPaths instance containing the list of vulnerable execution
paths along with name, check, impact, confidence and other detector
information.
"""
def checks_group_size(block_ctx: "BlockTransactionContext") -> bool:
# return True if group-size is checked in the path
# otherwise return False
return MAX_GROUP_SIZE not in block_ctx.group_sizes
paths_without_check: List[List[BasicBlock]] = detect_missing_tx_field_validations(
self.teal.bbs[0], checks_group_size
)
# paths_without_check contain all the execution paths not checking the GroupSize
# Only report paths which also use absolute_index
paths_to_report: List[List[BasicBlock]] = []
for path in paths_without_check:
for bb in path:
if self._accessed_using_absolute_index(bb):
paths_to_report.append(path)
break
description = detector_terminal_description(self)
filename = "missing_group_size"
results = self.generate_result(paths_to_report, description, filename)
construct_stack_ast.cache_clear()
return results
Above approach works as follows:
- Generate paths lacking group-size check
- Filter the result paths which uses the absolute index.
May be it's better to do the second step in the detect_missing_tx_field_validations itself for now.
Add an additional parameter to detect_missing_tx_field_validations.
def detect_missing_tx_field_validations(
entry_block: "BasicBlock", checks_field: Callable[["BlockTransactionContext"], bool], satisfies_report_condition: Callable[[List["BasicBlock"], bool],
) -> List[List["BasicBlock"]]:
which takes a path and returns True if that path is considered vulnerable or else returns False.