diff --git a/base_image.dockerfile b/base_image.dockerfile index 2da55d58..ed7668a3 100644 --- a/base_image.dockerfile +++ b/base_image.dockerfile @@ -6,7 +6,7 @@ RUN apt-get update -y && apt-get upgrade -y && \ apt-get install -y cmake z3 python3.10 python3.10-dev python3-distutils python3-pip python3-apt python3.10-venv pkg-config libcairo2-dev libjpeg-dev libgif-dev \ openjdk-8-jdk git maven wget && \ DEBIAN_FRONTEND=noninteractive \ - apt-get install -y --no-install-recommends --assume-yes build-essential libpq-dev unzip + apt-get install -y --no-install-recommends --assume-yes build-essential libpq-dev unzip libcairo2-dev pkg-config python3.10-dev RUN apt-get update && apt-get install -y nodejs npm FROM python-build AS ecstatic-build diff --git a/requirements.txt b/requirements.txt index f9ffc6a2..8a28fd0a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ pytest~=6.2.5 frozendict~=2.1.3 jsonschema~=4.3.2 networkx~=2.6.3 +requests==2.28.1 docker~=5.0.3 matplotlib~=3.5.1 jsonpickle~=2.1.0 diff --git a/src/ecstatic/debugging/JavaBenchmarkDeltaDebugger.py b/src/ecstatic/debugging/JavaBenchmarkDeltaDebugger.py new file mode 100644 index 00000000..e112e8ed --- /dev/null +++ b/src/ecstatic/debugging/JavaBenchmarkDeltaDebugger.py @@ -0,0 +1,66 @@ +# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration +# +# Copyright (c) 2022. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging +from typing import Iterable, Tuple + +from src.ecstatic.debugging.AbstractDeltaDebugger import DeltaDebuggingPredicate, GroundTruth +from src.ecstatic.debugging.BenchmarkDeltaDebugger import BenchmarkDeltaDebugger +from src.ecstatic.debugging.JavaDeltaDebugger import JavaDeltaDebugger +from src.ecstatic.util.PartialOrder import PartialOrderType +from src.ecstatic.util.PotentialViolation import PotentialViolation + +logger = logging.getLogger(__name__) + + +class JavaBenchmarkDeltaDebugger(BenchmarkDeltaDebugger, JavaDeltaDebugger): + def make_predicates(self, potential_violation: PotentialViolation) -> Iterable[Tuple[DeltaDebuggingPredicate, GroundTruth]]: + """ + Returns the predicates and ground truths for a potential violation. If the potential violation's main + partial order (i.e., in the case that there are two potential partial order violations, we want the partial + order that matches the order of the jobs, such that job1 has some relationship to job2). + :param potential_violation: The non-violation + :return: + """ + mp = potential_violation.get_main_partial_order() + if (not potential_violation.is_violation) and (len(potential_violation.expected_diffs) > 0) and \ + mp.is_explicit(): + match mp.type: + case PartialOrderType.MORE_SOUND_THAN: + # If it's a soundness partial order, then we are not sure how many of the additional edges + # produces by the more sound configuration + def predicate(pv: PotentialViolation): + return pv.expected_diffs <= potential_violation.expected_diffs + ground_truth = { + "partial_order": str(mp), + "left_preserve_at_least_one": [str(e) for e in potential_violation.expected_diffs] + } + yield predicate, ground_truth + case PartialOrderType.MORE_PRECISE_THAN: + # If it's a precision partial order, we create a new benchmark for each of the edges missing in the + # more precise result, since we can be confident they are all false positives. + for e in potential_violation.expected_diffs: + def predicate(pv: PotentialViolation): + return e in pv.expected_diffs + ground_truth = { + "partial_order": str(mp), + "left_preserve_all": [str(e)] + } + yield predicate, ground_truth + case _: + raise RuntimeError("Pattern matching on main partial order failed.") + diff --git a/src/ecstatic/dispatcher/DockerManager.py b/src/ecstatic/dispatcher/DockerManager.py index 51b7b2ff..68c80ff0 100644 --- a/src/ecstatic/dispatcher/DockerManager.py +++ b/src/ecstatic/dispatcher/DockerManager.py @@ -30,7 +30,6 @@ def build_image(tool: str, nocache: bool = False): env = os.environ - os.environ["DOCKER_DEFAULT_PLATFORM"] = "linux/amd64" if tool == 'base': logger.info("Creating base image") cmd = ['docker', 'build', '.', '-f', 'base_image.dockerfile', '-t', get_image_name(tool)] diff --git a/src/ecstatic/readers/AmanDroidReader.py b/src/ecstatic/readers/AmanDroidReader.py new file mode 100644 index 00000000..42c96c78 --- /dev/null +++ b/src/ecstatic/readers/AmanDroidReader.py @@ -0,0 +1,44 @@ +# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration +# +# Copyright (c) 2022. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging +from typing import List, Tuple + +from src.ecstatic.readers.AbstractReader import AbstractReader + + +logger = logging.getLogger(__name__) +class AmanDroidReader(AbstractReader): + + def import_file(self, file): + flows: List[Tuple[str, str]] = [] + isFlow = False + with open(file) as f: + lines = f.readlines() + for line in lines: + if isFlow: + if 'Source:' in line: + source = line.split(': ', 1)[1] + elif 'Sink:' in line: + sink = line.split(': ', 1)[1] + flows.append((source, sink)) + isFlow = isFlow == False + else: + if 'TaintPath:' in line: + isFlow = isFlow == False + return flows + \ No newline at end of file diff --git a/src/ecstatic/readers/DroidSafeReader.py b/src/ecstatic/readers/DroidSafeReader.py new file mode 100644 index 00000000..d152bf6b --- /dev/null +++ b/src/ecstatic/readers/DroidSafeReader.py @@ -0,0 +1,37 @@ +# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration +# +# Copyright (c) 2022. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging +from typing import List, Tuple + +from src.ecstatic.readers.AbstractReader import AbstractReader + + +logger = logging.getLogger(__name__) +class DroidSafeReader(AbstractReader): + + def import_file(self, file): + flows: List[Tuple[str, str]] = [] + with open(file) as f: + lines = f.readlines() + for line in lines: + if line.startswith('FLOW:'): + source = line.rsplit('|', 1)[-1].split('<=')[1] + sink = line.rsplit('|', 1)[-1].split('<=')[0] + flows.append((source, sink)) + return flows + \ No newline at end of file diff --git a/src/ecstatic/readers/ReaderFactory.py b/src/ecstatic/readers/ReaderFactory.py index 960f9b60..697fe112 100644 --- a/src/ecstatic/readers/ReaderFactory.py +++ b/src/ecstatic/readers/ReaderFactory.py @@ -22,6 +22,11 @@ from src.ecstatic.readers.callgraph.DOOPCallGraphReader import DOOPCallGraphReader from src.ecstatic.readers.callgraph.SOOTCallGraphReader import SOOTCallGraphReader from src.ecstatic.readers.callgraph.WALACallGraphReader import WALACallGraphReader +from src.ecstatic.readers.callgraph.TAJSCallGraphReader import TAJSCallGraphReader +from src.ecstatic.readers.callgraph.WALAJSCallGraphReader import WALAJSCallGraphReader +from src.ecstatic.readers.DroidSafeReader import DroidSafeReader +from src.ecstatic.readers.AmanDroidReader import AmanDroidReader + def get_reader_for_task_and_tool(task: str, name: str, *args) -> Any: match task.lower(): @@ -29,10 +34,14 @@ def get_reader_for_task_and_tool(task: str, name: str, *args) -> Any: match name.lower(): case "soot": return SOOTCallGraphReader(*args) case "wala": return WALACallGraphReader(*args) + case "wala-js": return WALAJSCallGraphReader(*args) case "doop": return DOOPCallGraphReader(*args) + case "tajs": return TAJSCallGraphReader(*args) case _: raise NotImplementedError(f"No support for task {task} on tool {name}") case "taint": match name.lower(): case "flowdroid": return FlowDroidFlowReader(*args) + case "droidsafe": return DroidSafeReader(*args) + case "amandroid": return AmanDroidReader(*args) case _: raise NotImplementedError(f"No support for task {task} on tool {name}") case _: raise NotImplementedError(f"No support for task {task}.") diff --git a/src/ecstatic/readers/callgraph/TAJSCallGraphReader.py b/src/ecstatic/readers/callgraph/TAJSCallGraphReader.py new file mode 100644 index 00000000..d9036071 --- /dev/null +++ b/src/ecstatic/readers/callgraph/TAJSCallGraphReader.py @@ -0,0 +1,45 @@ +from src.ecstatic.readers.callgraph.AbstractCallGraphReader import AbstractCallGraphReader +from src.ecstatic.util.CGCallSite import CGCallSite +from src.ecstatic.util.CGTarget import CGTarget + +from abc import ABC +from typing import Tuple, List, Any + +class TAJSCallGraphReader(AbstractCallGraphReader): + def import_file(self, file): + callgraph: List[Tuple[Any, Any]] = [] + edges = [] + nodes = {} + with open(file) as f: + lines = f.readlines() + for i in lines: + # print(i) + if "->" in i: + parts = i.strip().split(" -> ") + edges.append((parts[0], parts[1])) + print("edge:", i) + else: + parts = [] + node = [] + try: + parts = i.strip().split(" ") + node = parts[2].strip(" label=").strip('"]').strip('"]}').split("\\n") + except: + parts = i.strip().split(" ") + nodes[parts[0]] = node + print("node:", i) + for j in edges: + callid, targetid = j + call_funct = nodes[callid][0] + if (nodes[callid] != ['
']): + call_location = nodes[callid][1] + else: + call_location = '' + target_funct = nodes[targetid][0] + target_location = nodes[targetid][1] + # add tuple of CGCallsite and CGTarget to list + callsite = CGCallSite(call_funct, call_location, '') + target = CGTarget(target_location, '') + print(call_funct, call_location, target_location) + callgraph.append((callsite, target)) + return callgraph \ No newline at end of file diff --git a/src/ecstatic/readers/callgraph/WALAJSCallGraphReader.py b/src/ecstatic/readers/callgraph/WALAJSCallGraphReader.py new file mode 100644 index 00000000..a81bc284 --- /dev/null +++ b/src/ecstatic/readers/callgraph/WALAJSCallGraphReader.py @@ -0,0 +1,30 @@ +# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration +# +# Copyright (c) 2022. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +from typing import Tuple, Any + +from src.ecstatic.readers.callgraph.AbstractCallGraphReader import AbstractCallGraphReader +from src.ecstatic.util.CGCallSite import CGCallSite +from src.ecstatic.util.CGTarget import CGTarget + + +class WALAJSCallGraphReader(AbstractCallGraphReader): + + def process_line(self, line: str) -> Tuple[Any, Any]: + match line.split("\t"): + case [caller_function, caller_line, caller_context, callee_target, callee_context]: + return CGCallSite(caller_function, caller_line, caller_context), CGTarget(callee_target, callee_context) + case _: raise ValueError(f"Could not read line {line}") \ No newline at end of file diff --git a/src/ecstatic/runners/AmanDroidRunner.py b/src/ecstatic/runners/AmanDroidRunner.py new file mode 100644 index 00000000..f5eb186c --- /dev/null +++ b/src/ecstatic/runners/AmanDroidRunner.py @@ -0,0 +1,78 @@ +# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration +# +# Copyright (c) 2022. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public Licese for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import importlib +import logging +import os +import shutil +import subprocess +import uuid +from typing import Tuple, List + +from src.ecstatic.runners.CommandLineToolRunner import CommandLineToolRunner +from src.ecstatic.util.UtilClasses import BenchmarkRecord +from src.ecstatic.util import FuzzingJob + + +logger = logging.getLogger(__name__) + +class AmanDroidRunner (CommandLineToolRunner): + + def get_timeout_option(self) -> List[str]: + print("Amandroid does not support the timeout option. Sorry!") + exit(-1) + return [] + + def get_input_option(self, benchmark_record: BenchmarkRecord) -> List[str]: + return f"{benchmark_record.name}".split() + + def get_output_option(self, output_file: str) -> List[str]: + return f"-o {output_file}".split() + + def get_base_command(self) -> List[str]: + return "java -jar /amandroid/argus-saf-3.2.1-SNAPSHOT-assembly.jar t".split() + + def try_run_job(self, job: FuzzingJob, output_folder: str) -> Tuple[str, str]: + logging.info(f'Job configuration is {[(str(k), str(v)) for k, v in job.configuration.items()]}') + config_hash = self.dict_hash(job.configuration) + id = uuid.uuid1().hex + result_dir = f'/amandroid/{config_hash}_{id}/' + cmd = self.get_base_command() + cmd.extend(self.get_output_option(result_dir)) + cmd.extend(self.get_input_option(job.target)) + logging.info(f"Cmd is {cmd}") + print(f"Cmd is {cmd}") + + ps = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + logger.info(f"Stdout from command {' '.join(cmd)} is {ps.stdout}") + + try: + result_dir_full = os.path.join(result_dir, os.path.basename(job.target.name).replace('.apk', '')) + intermediate_file = os.path.join(result_dir_full, 'result/AppData.txt') + except UnboundLocalError: + raise RuntimeError(ps.stdout) + + output_file = self.get_output(output_folder, job) + shutil.move(intermediate_file, output_file) + logging.info(f'Moved {intermediate_file} to {output_file}') + + if not os.path.exists(output_file): + raise RuntimeError(ps.stdout) + return output_file, ps.stdout + + diff --git a/src/ecstatic/runners/DroidSafeRunner.py b/src/ecstatic/runners/DroidSafeRunner.py new file mode 100644 index 00000000..1cf10776 --- /dev/null +++ b/src/ecstatic/runners/DroidSafeRunner.py @@ -0,0 +1,69 @@ +# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration +# +# Copyright (c) 2022. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import importlib +import logging +import os +import shutil +import subprocess +import uuid +from typing import Tuple, List + +from src.ecstatic.runners.AbstractCommandLineToolRunner import AbstractCommandLineToolRunner +from src.ecstatic.util import FuzzingJob + + +logger = logging.getLogger(__name__) + + +class DroidSafeRunner(AbstractCommandLineToolRunner): + + def get_timeout_option(self) -> List[str]: + print("Droidsafe does not support the timeout option. Sorry!") + exit(-1) + return [] + + def try_run_job(self, job: FuzzingJob, output_folder: str) -> Tuple[str, str]: + target_basedir = os.path.join(os.getenv('DROIDSAFE_SRC_HOME'), 'runs') + app_name = os.path.basename(job.target.name).replace('.apk', '') + config_hash = self.dict_hash(job.configuration) + id = uuid.uuid1().hex + target_dir = os.path.join(target_basedir, f'{config_hash}_{app_name}_{id}') + + droidsafe_shell = importlib.resources.path(f"src.resources.tools.droidsafe", "droidsafe.sh") + cmd = [droidsafe_shell] + cmd.append(job.target.name) + cmd.append(f'{config_hash}_{app_name}_{id}') + cmd.append(self.dict_to_config_str(job.configuration)) + logger.info(f'Running job with configuration {self.dict_hash(job.configuration)} on apk {job.target.name}') + ps = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + logger.info(f'Stdout for cmd {" ".join([str(c) for c in cmd])} was {ps.stdout}') + logger.info(f'Job on configuration {self.dict_hash(job.configuration)} on apk {job.target.name} done.') + + output_file = self.get_output(output_folder, job) + + try: + target_dir_gen = os.path.join(target_dir, 'droidsafe-gen') + intermediate_file = os.path.join(target_dir_gen, "info-flow-results.txt") + except UnboundLocalError: + raise RuntimeError(ps.stdout) + shutil.copyfile(intermediate_file, output_file) + logger.info(f'Copied {intermediate_file} to {output_file}') + + return output_file, ps.stdout + diff --git a/src/ecstatic/runners/RunnerFactory.py b/src/ecstatic/runners/RunnerFactory.py index de9cd576..c960c4d3 100644 --- a/src/ecstatic/runners/RunnerFactory.py +++ b/src/ecstatic/runners/RunnerFactory.py @@ -22,7 +22,12 @@ from src.ecstatic.runners.DOOPRunner import DOOPRunner from src.ecstatic.runners.FlowDroidRunner import FlowDroidRunner from src.ecstatic.runners.SOOTRunner import SOOTRunner +from src.ecstatic.runners.WALAJSRunner import WALAJSRunner from src.ecstatic.runners.WALARunner import WALARunner +from src.ecstatic.runners.TAJSRunner import TAJSRunner +from src.ecstatic.runners.DroidSafeRunner import DroidSafeRunner +from src.ecstatic.runners.AmanDroidRunner import AmanDroidRunner + logger = logging.getLogger(__name__) @@ -33,4 +38,8 @@ def get_runner_for_tool(name: str, *args) -> AbstractCommandLineToolRunner: case "wala": return WALARunner(*args) case "doop": return DOOPRunner(*args) case "flowdroid": return FlowDroidRunner(*args) + case "wala-js": return WALAJSRunner(*args) + case "tajs": return TAJSRunner(*args) + case "droidsafe": return DroidSafeRunner(*args) + case "amandroid": return AmanDroidRunner(*args) case _ : raise NotImplementedError(f"No support for runner for {name}") diff --git a/src/ecstatic/runners/TAJSRunner.py b/src/ecstatic/runners/TAJSRunner.py new file mode 100644 index 00000000..4b463431 --- /dev/null +++ b/src/ecstatic/runners/TAJSRunner.py @@ -0,0 +1,84 @@ +import os.path +from typing import List, Dict + +from src.ecstatic.runners.CommandLineToolRunner import CommandLineToolRunner +from src.ecstatic.util.UtilClasses import BenchmarkRecord + + +class TAJSRunner (CommandLineToolRunner): + def get_timeout_option(self) -> List[str]: + if self.timeout is None: + return [] + else: + return f"-time-limit {self.timeout * 60}".split(" ") + + def get_whole_program(self) -> List[str]: + # shouldn't be necessary + return [] + + def get_input_option(self, benchmark_record: BenchmarkRecord) -> List[str]: + return f"{benchmark_record.name}".split() + + def get_output_option(self, output_file: str) -> List[str]: + # callgraph recieves file path argument + return f"-callgraph {output_file}".split() + + def get_task_option(self, task: str) -> List[str]: + # might need to add to this + if task == 'cg': + return [] + else: + raise NotImplementedError(f'TAJS does not support task {task}.') + + def dict_to_config_str(self, config_as_dict: Dict[str, str]) -> str: + """ + We need special handling of TAJS's options, because of unsound options and commands without level value + + + Parameters + ---------- + config_as_dict: The dictionary specifying the configuration. + Returns + ------- + The corresponding command-line string. + """ + config_as_str = "" + for k, v in config_as_dict.items(): + k: Option + v: Level + for t in k.tags: + if t.startswith('unsound'): + config_as_str = config_as_str + f"-unsound -{k.name} " + rest_of_config = "" + for k, v in config_as_dict.items(): + if len([t for t in k.tags if t.startswith('unsound')]) == 0: + k: Option + v: Level + if isinstance(v.level_name, int) or \ + v.level_name.lower() not in ['false', 'true']: + rest_of_config += f'-{k.name} {v.level_name} ' + elif v.level_name.lower() == 'true': + rest_of_config += f'-{k.name} ' +# + # Compute string for the rest of the options which are not unsound. + # this part may not work + return rest_of_config + config_as_str + #config_as_str = ""; + #for key in config_as_dict: + # if key.startswith('isunsound_'): + # #phase option, append like this -p 'phase' 'key':'value' + #format is "phase_'phase'_'key'" = VALUE + # config_as_str = config_as_str + f"-unsound -{key[key.find('_')+1:]} " + # else: + # if(config_as_dict[key] == 'true'): + # config_as_str = config_as_str + f"-{key} " + # elif(config_as_dict[key] == 'false'): + # config_as_str = config_as_str; #dumb options that are turned off by not giving them? + # else: + # config_as_str = config_as_str + f"-{key} {config_as_dict[key]} " + # + #return config_as_str; + + def get_base_command(self) -> List[str]: + return "java -jar /TAJS/dist/tajs-all.jar".split() + diff --git a/src/ecstatic/runners/WALAJSRunner.py b/src/ecstatic/runners/WALAJSRunner.py new file mode 100644 index 00000000..16651320 --- /dev/null +++ b/src/ecstatic/runners/WALAJSRunner.py @@ -0,0 +1,32 @@ +# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration +# +# Copyright (c) 2022. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +from typing import List + +from src.ecstatic.runners.WALARunner import WALARunner +from src.ecstatic.util.UtilClasses import BenchmarkRecord + + +class WALAJSRunner(WALARunner): + def get_input_option(self, benchmark_record: BenchmarkRecord) -> List[str]: + return f"--scripts {benchmark_record.name}".split(" ") + + def get_output_option(self, output_file: str) -> List[str]: + return f"--cgoutput {output_file}".split(" ") + + + def get_base_command(self) -> List[str]: + return "java -jar /WalaJSCallgraph/target/jscallgraph-0.0.1-SNAPSHOT-jar-with-dependencies.jar".split(" ") \ No newline at end of file diff --git a/src/ecstatic/violation_checkers/AndroidTaintViolationChecker.py b/src/ecstatic/violation_checkers/AndroidTaintViolationChecker.py new file mode 100644 index 00000000..9e691a64 --- /dev/null +++ b/src/ecstatic/violation_checkers/AndroidTaintViolationChecker.py @@ -0,0 +1,43 @@ +# ECSTATIC: Extensible, Customizable STatic Analysis Tester Informed by Configuration +# +# Copyright (c) 2022. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging + +from src.ecstatic.models.Flow import Flow +from src.ecstatic.violation_checkers.AbstractViolationChecker import AbstractViolationChecker +from src.ecstatic.readers.AbstractReader import AbstractReader +from pathlib import Path +from typing import Optional, TypeVar + +logger = logging.getLogger(__name__) +T = TypeVar('T') # Indicates the type of content in the results (e.g., call graph edges or flows) +class AndroidTaintViolationChecker(AbstractViolationChecker): + + def __init__(self, jobs: int, reader: AbstractReader, output_folder: Path, ground_truths: Optional[Path] = None, + write_to_files=True): + self.output_folder = output_folder + self.jobs: int = jobs + self.reader = reader + self.ground_truths: Path = None + self.write_to_files = write_to_files + logger.debug(f'Ground truths are {self.ground_truths}') + + def is_true_positive(self, raw_result: T) -> bool: + raise NotImplementedError("AndroidTaint Tools do not support ground truths yet.") + + def is_false_positive(self, raw_result: T) -> bool: + raise NotImplementedError("AndroidTaint Tools do not support ground truths yet.") diff --git a/src/ecstatic/violation_checkers/ViolationCheckerFactory.py b/src/ecstatic/violation_checkers/ViolationCheckerFactory.py index 3983373b..c486b0a5 100644 --- a/src/ecstatic/violation_checkers/ViolationCheckerFactory.py +++ b/src/ecstatic/violation_checkers/ViolationCheckerFactory.py @@ -19,6 +19,7 @@ from src.ecstatic.violation_checkers.AbstractViolationChecker import AbstractViolationChecker from src.ecstatic.violation_checkers.CallgraphViolationChecker import CallgraphViolationChecker from src.ecstatic.violation_checkers.FlowDroidFlowViolationChecker import FlowDroidFlowViolationChecker +from src.ecstatic.violation_checkers.AndroidTaintViolationChecker import AndroidTaintViolationChecker def get_violation_checker_for_task(task: str, tool: str, **kwargs) -> AbstractViolationChecker: @@ -26,5 +27,7 @@ def get_violation_checker_for_task(task: str, tool: str, **kwargs) -> AbstractVi return CallgraphViolationChecker(**kwargs) elif task.lower() == "taint" and tool.lower() == "flowdroid": return FlowDroidFlowViolationChecker(**kwargs) + elif "droidsafe" or "amandroid": + return AndroidTaintViolationChecker(**kwargs) else: raise ValueError(f"No violation checker exists for task {task} on tool {tool}.") \ No newline at end of file diff --git a/src/resources/configuration_spaces/amandroid_config.json b/src/resources/configuration_spaces/amandroid_config.json new file mode 100644 index 00000000..4e7cab3e --- /dev/null +++ b/src/resources/configuration_spaces/amandroid_config.json @@ -0,0 +1,20 @@ +{ + "name": "AmanDroid", + "options": [ + { + "name": "static_init", + "levels": [ + "FALSE", + "TRUE" + ], + "default": "FALSE", + "orders": [ + { + "left": "TRUE", + "order": "MPT", + "right": "FALSE" + } + ] + } + ] + } \ No newline at end of file diff --git a/src/resources/configuration_spaces/droidsafe_config.json b/src/resources/configuration_spaces/droidsafe_config.json new file mode 100644 index 00000000..81cef644 --- /dev/null +++ b/src/resources/configuration_spaces/droidsafe_config.json @@ -0,0 +1,50 @@ +{ + "name": "DroidSafe", + "options": [ + { + "name": "analyzestrings_unfiltered", + "levels": [ + "FALSE", + "TRUE" + ], + "default": "FALSE", + "orders": [ + { + "left": "TRUE", + "order": "MPT", + "right": "FALSE" + } + ] + }, + { + "name": "filetransforms", + "levels": [ + "FALSE", + "TRUE" + ], + "default": "FALSE", + "orders": [ + { + "left": "TRUE", + "order": "MST", + "right": "FALSE" + } + ] + }, + { + "name": "ignorenocontextflows", + "levels": [ + "TRUE", + "FALSE" + ], + "default": "FALSE", + "orders": [ + { + "left": "TRUE", + "order": "MST", + "right": "FALSE" + } + ] + } + ] + } \ No newline at end of file diff --git a/src/resources/grammars/amandroid.json b/src/resources/grammars/amandroid.json new file mode 100644 index 00000000..a34371e7 --- /dev/null +++ b/src/resources/grammars/amandroid.json @@ -0,0 +1,9 @@ +{ + "": [""], + "": ["