mirror of
https://github.com/trailofbits/buttercup
synced 2026-06-21 14:11:39 +00:00
fuzzer_runner: split fuzzer's dependencies (#320)
Before this commit, the fuzzer dependend on clusterfuzz, which uses protobuf 3.20. Since fuzzer-bot depended on common subpackage as well and also common (and the other packages) require protobuf, we had to use protobuf 3.20 everywhere. This old dependency however means that a lot of packages can't be used in their "newer" versions, because they depend on newer protobuf versions. This commit splits the fuzzer into a separate fuzzer-runner that is executed in a separate process inside a separate venv. fuzzer-runner executes the clusterfuzz-heavy operations (e.g. fuzzing) and isolates the clusterfuzz dependency, so that the rest of the system can use newer protobuf version. * split `fuzzer-bot` in `fuzzer-runner` and `fuzzer-bot` * have a "full" optional dependency group in `common`, including `openlit` and `protobuf`, so that `fuzzer-runner` can use the lite version of `common` without bringing those heavy deps * move `FuzzConfiguration`/`BuildConfiguration` in a separate common file that doesn't require to load all protobuf files. Again, in this way other components can just depend on the `common` "lite" version and not require protobuf stuff * add `fuzzer-runner` as a separate venv inside the `fuzzer-bot` container * add `RunnerProxy` class in fuzzer package to provide an interface to interact with the fuzzer-runner binary.
This commit is contained in:
committed by
GitHub
parent
7739f7907d
commit
143a59c236
+1
-1
@@ -16,7 +16,7 @@
|
|||||||
**/.pdm-build/
|
**/.pdm-build/
|
||||||
**/.pdm-python
|
**/.pdm-python
|
||||||
**/pdm.lock
|
**/pdm.lock
|
||||||
fuzzer/dockerfiles/runner_image.Dockerfile
|
fuzzer/Dockerfile
|
||||||
orchestrator/Dockerfile
|
orchestrator/Dockerfile
|
||||||
patcher/Dockerfile
|
patcher/Dockerfile
|
||||||
seed-gen/Dockerfile
|
seed-gen/Dockerfile
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
- component: orchestrator
|
- component: orchestrator
|
||||||
dockerfile: ./orchestrator/Dockerfile
|
dockerfile: ./orchestrator/Dockerfile
|
||||||
- component: fuzzer
|
- component: fuzzer
|
||||||
dockerfile: ./fuzzer/dockerfiles/runner_image.Dockerfile
|
dockerfile: ./fuzzer/Dockerfile
|
||||||
- component: patcher
|
- component: patcher
|
||||||
dockerfile: ./patcher/Dockerfile
|
dockerfile: ./patcher/Dockerfile
|
||||||
- component: seed-gen
|
- component: seed-gen
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ send-libpng-task:
|
|||||||
# Development targets
|
# Development targets
|
||||||
lint:
|
lint:
|
||||||
@echo "Linting all Python code..."
|
@echo "Linting all Python code..."
|
||||||
@set -e; for component in common orchestrator fuzzer program-model seed-gen patcher; do \
|
@set -e; for component in common orchestrator fuzzer fuzzer_runner program-model seed-gen patcher; do \
|
||||||
make --no-print-directory lint-component COMPONENT=$$component; \
|
make --no-print-directory lint-component COMPONENT=$$component; \
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ lint-component:
|
|||||||
|
|
||||||
reformat:
|
reformat:
|
||||||
@echo "Reformatting all Python code..."
|
@echo "Reformatting all Python code..."
|
||||||
@for component in common orchestrator fuzzer program-model seed-gen patcher; do \
|
@for component in common orchestrator fuzzer fuzzer_runner program-model seed-gen patcher; do \
|
||||||
make --no-print-directory reformat-component COMPONENT=$$component; \
|
make --no-print-directory reformat-component COMPONENT=$$component; \
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -187,6 +187,10 @@ undeploy:
|
|||||||
@echo "Cleaning up deployment..."
|
@echo "Cleaning up deployment..."
|
||||||
cd deployment && make down
|
cd deployment && make down
|
||||||
|
|
||||||
|
undeploy-k8s:
|
||||||
|
@echo "Cleaning up kubernetes resources..."
|
||||||
|
cd deployment && make down-k8s
|
||||||
|
|
||||||
clean-local:
|
clean-local:
|
||||||
@echo "Cleaning up local environment..."
|
@echo "Cleaning up local environment..."
|
||||||
minikube delete || true
|
minikube delete || true
|
||||||
|
|||||||
+10
-7
@@ -6,20 +6,23 @@ authors = [{ name = "Trail of Bits", email = "opensource@trailofbits.com" }]
|
|||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
requires-python = ">=3.12,<3.13"
|
requires-python = ">=3.12,<3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"protobuf ==3.20.3", # Pin exactly for stability
|
|
||||||
"pydantic-settings ~=2.7.1",
|
"pydantic-settings ~=2.7.1",
|
||||||
"pymongo ~=4.10.1",
|
"pymongo ~=4.10.1",
|
||||||
"redis ~=5.2.1",
|
"redis ~=5.2.1",
|
||||||
"langchain-core ~=0.3.32",
|
"langchain-core ~=0.3.74",
|
||||||
"langchain-openai ~=0.3.2",
|
"langchain-openai ~=0.3.30",
|
||||||
|
"langchain ~=0.3.27",
|
||||||
"langfuse ~=2.59.2",
|
"langfuse ~=2.59.2",
|
||||||
"langchain ~=0.3.18",
|
|
||||||
"six ~=1.17.0",
|
"six ~=1.17.0",
|
||||||
"openlit ==1.32.12", # 1.33 currently fails to import opentelemetry.sdk._events
|
|
||||||
"langchain-community ~=0.3.20", # implicit dep of openlit if you instrument langchain
|
|
||||||
"langchain-text-splitters ~=0.3.7", # implicit dep of openlit if you instrument langchain
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
full = [
|
||||||
|
"protobuf>=5.0",
|
||||||
|
"openlit ==1.35.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Repository = "https://github.com/trailofbits/buttercup"
|
Repository = "https://github.com/trailofbits/buttercup"
|
||||||
Issues = "https://github.com/trailofbits/buttercup/issues"
|
Issues = "https://github.com/trailofbits/buttercup/issues"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -4,299 +4,315 @@ from google.protobuf import descriptor as _descriptor
|
|||||||
from google.protobuf import message as _message
|
from google.protobuf import message as _message
|
||||||
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
|
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
|
||||||
|
|
||||||
ACCEPTED: SubmissionResult
|
|
||||||
COVERAGE: BuildType
|
|
||||||
DEADLINE_EXCEEDED: SubmissionResult
|
|
||||||
DESCRIPTOR: _descriptor.FileDescriptor
|
DESCRIPTOR: _descriptor.FileDescriptor
|
||||||
ERRORED: SubmissionResult
|
|
||||||
FAILED: SubmissionResult
|
class BuildType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||||
|
__slots__ = ()
|
||||||
|
FUZZER: _ClassVar[BuildType]
|
||||||
|
COVERAGE: _ClassVar[BuildType]
|
||||||
|
TRACER_NO_DIFF: _ClassVar[BuildType]
|
||||||
|
PATCH: _ClassVar[BuildType]
|
||||||
|
|
||||||
|
class SubmissionResult(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||||
|
__slots__ = ()
|
||||||
|
NONE: _ClassVar[SubmissionResult]
|
||||||
|
ACCEPTED: _ClassVar[SubmissionResult]
|
||||||
|
PASSED: _ClassVar[SubmissionResult]
|
||||||
|
FAILED: _ClassVar[SubmissionResult]
|
||||||
|
DEADLINE_EXCEEDED: _ClassVar[SubmissionResult]
|
||||||
|
ERRORED: _ClassVar[SubmissionResult]
|
||||||
|
INCONCLUSIVE: _ClassVar[SubmissionResult]
|
||||||
FUZZER: BuildType
|
FUZZER: BuildType
|
||||||
INCONCLUSIVE: SubmissionResult
|
COVERAGE: BuildType
|
||||||
NONE: SubmissionResult
|
|
||||||
PASSED: SubmissionResult
|
|
||||||
PATCH: BuildType
|
|
||||||
TRACER_NO_DIFF: BuildType
|
TRACER_NO_DIFF: BuildType
|
||||||
|
PATCH: BuildType
|
||||||
|
NONE: SubmissionResult
|
||||||
|
ACCEPTED: SubmissionResult
|
||||||
|
PASSED: SubmissionResult
|
||||||
|
FAILED: SubmissionResult
|
||||||
|
DEADLINE_EXCEEDED: SubmissionResult
|
||||||
|
ERRORED: SubmissionResult
|
||||||
|
INCONCLUSIVE: SubmissionResult
|
||||||
|
|
||||||
class BuildOutput(_message.Message):
|
class Task(_message.Message):
|
||||||
__slots__ = ["apply_diff", "build_type", "engine", "internal_patch_id", "sanitizer", "task_dir", "task_id"]
|
__slots__ = ("message_id", "message_time", "task_id", "task_type", "sources", "deadline", "cancelled", "project_name", "focus", "metadata")
|
||||||
APPLY_DIFF_FIELD_NUMBER: _ClassVar[int]
|
class TaskType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||||
BUILD_TYPE_FIELD_NUMBER: _ClassVar[int]
|
__slots__ = ()
|
||||||
ENGINE_FIELD_NUMBER: _ClassVar[int]
|
TASK_TYPE_FULL: _ClassVar[Task.TaskType]
|
||||||
INTERNAL_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
TASK_TYPE_DELTA: _ClassVar[Task.TaskType]
|
||||||
SANITIZER_FIELD_NUMBER: _ClassVar[int]
|
TASK_TYPE_FULL: Task.TaskType
|
||||||
TASK_DIR_FIELD_NUMBER: _ClassVar[int]
|
TASK_TYPE_DELTA: Task.TaskType
|
||||||
|
class MetadataEntry(_message.Message):
|
||||||
|
__slots__ = ("key", "value")
|
||||||
|
KEY_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
VALUE_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
key: str
|
||||||
|
value: str
|
||||||
|
def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ...
|
||||||
|
MESSAGE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
MESSAGE_TIME_FIELD_NUMBER: _ClassVar[int]
|
||||||
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
apply_diff: bool
|
TASK_TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||||
build_type: BuildType
|
SOURCES_FIELD_NUMBER: _ClassVar[int]
|
||||||
engine: str
|
DEADLINE_FIELD_NUMBER: _ClassVar[int]
|
||||||
internal_patch_id: str
|
CANCELLED_FIELD_NUMBER: _ClassVar[int]
|
||||||
sanitizer: str
|
PROJECT_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||||
task_dir: str
|
FOCUS_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
METADATA_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
message_id: str
|
||||||
|
message_time: int
|
||||||
task_id: str
|
task_id: str
|
||||||
def __init__(self, engine: _Optional[str] = ..., sanitizer: _Optional[str] = ..., task_dir: _Optional[str] = ..., task_id: _Optional[str] = ..., build_type: _Optional[_Union[BuildType, str]] = ..., apply_diff: bool = ..., internal_patch_id: _Optional[str] = ...) -> None: ...
|
task_type: Task.TaskType
|
||||||
|
sources: _containers.RepeatedCompositeFieldContainer[SourceDetail]
|
||||||
class BuildRequest(_message.Message):
|
deadline: int
|
||||||
__slots__ = ["apply_diff", "build_type", "engine", "internal_patch_id", "patch", "sanitizer", "task_dir", "task_id"]
|
cancelled: bool
|
||||||
APPLY_DIFF_FIELD_NUMBER: _ClassVar[int]
|
project_name: str
|
||||||
BUILD_TYPE_FIELD_NUMBER: _ClassVar[int]
|
focus: str
|
||||||
ENGINE_FIELD_NUMBER: _ClassVar[int]
|
metadata: _containers.ScalarMap[str, str]
|
||||||
INTERNAL_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
def __init__(self, message_id: _Optional[str] = ..., message_time: _Optional[int] = ..., task_id: _Optional[str] = ..., task_type: _Optional[_Union[Task.TaskType, str]] = ..., sources: _Optional[_Iterable[_Union[SourceDetail, _Mapping]]] = ..., deadline: _Optional[int] = ..., cancelled: bool = ..., project_name: _Optional[str] = ..., focus: _Optional[str] = ..., metadata: _Optional[_Mapping[str, str]] = ...) -> None: ...
|
||||||
PATCH_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
SANITIZER_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_DIR_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
apply_diff: bool
|
|
||||||
build_type: BuildType
|
|
||||||
engine: str
|
|
||||||
internal_patch_id: str
|
|
||||||
patch: str
|
|
||||||
sanitizer: str
|
|
||||||
task_dir: str
|
|
||||||
task_id: str
|
|
||||||
def __init__(self, engine: _Optional[str] = ..., sanitizer: _Optional[str] = ..., task_dir: _Optional[str] = ..., task_id: _Optional[str] = ..., build_type: _Optional[_Union[BuildType, str]] = ..., apply_diff: bool = ..., patch: _Optional[str] = ..., internal_patch_id: _Optional[str] = ...) -> None: ...
|
|
||||||
|
|
||||||
class Bundle(_message.Message):
|
|
||||||
__slots__ = ["bundle_id", "competition_patch_id", "competition_pov_id", "competition_sarif_id", "task_id"]
|
|
||||||
BUNDLE_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
COMPETITION_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
COMPETITION_POV_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
COMPETITION_SARIF_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
bundle_id: str
|
|
||||||
competition_patch_id: str
|
|
||||||
competition_pov_id: str
|
|
||||||
competition_sarif_id: str
|
|
||||||
task_id: str
|
|
||||||
def __init__(self, task_id: _Optional[str] = ..., competition_pov_id: _Optional[str] = ..., competition_patch_id: _Optional[str] = ..., competition_sarif_id: _Optional[str] = ..., bundle_id: _Optional[str] = ...) -> None: ...
|
|
||||||
|
|
||||||
class ConfirmedVulnerability(_message.Message):
|
|
||||||
__slots__ = ["crashes", "internal_patch_id"]
|
|
||||||
CRASHES_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
INTERNAL_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
crashes: _containers.RepeatedCompositeFieldContainer[TracedCrash]
|
|
||||||
internal_patch_id: str
|
|
||||||
def __init__(self, crashes: _Optional[_Iterable[_Union[TracedCrash, _Mapping]]] = ..., internal_patch_id: _Optional[str] = ...) -> None: ...
|
|
||||||
|
|
||||||
class Crash(_message.Message):
|
|
||||||
__slots__ = ["crash_input_path", "crash_token", "harness_name", "stacktrace", "target"]
|
|
||||||
CRASH_INPUT_PATH_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
CRASH_TOKEN_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
HARNESS_NAME_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
STACKTRACE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TARGET_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
crash_input_path: str
|
|
||||||
crash_token: str
|
|
||||||
harness_name: str
|
|
||||||
stacktrace: str
|
|
||||||
target: BuildOutput
|
|
||||||
def __init__(self, target: _Optional[_Union[BuildOutput, _Mapping]] = ..., harness_name: _Optional[str] = ..., crash_input_path: _Optional[str] = ..., stacktrace: _Optional[str] = ..., crash_token: _Optional[str] = ...) -> None: ...
|
|
||||||
|
|
||||||
class CrashWithId(_message.Message):
|
|
||||||
__slots__ = ["competition_pov_id", "crash", "result"]
|
|
||||||
COMPETITION_POV_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
CRASH_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
RESULT_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
competition_pov_id: str
|
|
||||||
crash: TracedCrash
|
|
||||||
result: SubmissionResult
|
|
||||||
def __init__(self, crash: _Optional[_Union[TracedCrash, _Mapping]] = ..., competition_pov_id: _Optional[str] = ..., result: _Optional[_Union[SubmissionResult, str]] = ...) -> None: ...
|
|
||||||
|
|
||||||
class FunctionCoverage(_message.Message):
|
|
||||||
__slots__ = ["covered_lines", "function_name", "function_paths", "total_lines"]
|
|
||||||
COVERED_LINES_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
FUNCTION_NAME_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
FUNCTION_PATHS_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TOTAL_LINES_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
covered_lines: int
|
|
||||||
function_name: str
|
|
||||||
function_paths: _containers.RepeatedScalarFieldContainer[str]
|
|
||||||
total_lines: int
|
|
||||||
def __init__(self, function_name: _Optional[str] = ..., function_paths: _Optional[_Iterable[str]] = ..., total_lines: _Optional[int] = ..., covered_lines: _Optional[int] = ...) -> None: ...
|
|
||||||
|
|
||||||
class IndexOutput(_message.Message):
|
|
||||||
__slots__ = ["build_type", "package_name", "sanitizer", "task_dir", "task_id"]
|
|
||||||
BUILD_TYPE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
PACKAGE_NAME_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
SANITIZER_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_DIR_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
build_type: BuildType
|
|
||||||
package_name: str
|
|
||||||
sanitizer: str
|
|
||||||
task_dir: str
|
|
||||||
task_id: str
|
|
||||||
def __init__(self, build_type: _Optional[_Union[BuildType, str]] = ..., package_name: _Optional[str] = ..., sanitizer: _Optional[str] = ..., task_dir: _Optional[str] = ..., task_id: _Optional[str] = ...) -> None: ...
|
|
||||||
|
|
||||||
class IndexRequest(_message.Message):
|
|
||||||
__slots__ = ["build_type", "package_name", "sanitizer", "task_dir", "task_id"]
|
|
||||||
BUILD_TYPE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
PACKAGE_NAME_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
SANITIZER_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_DIR_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
build_type: BuildType
|
|
||||||
package_name: str
|
|
||||||
sanitizer: str
|
|
||||||
task_dir: str
|
|
||||||
task_id: str
|
|
||||||
def __init__(self, build_type: _Optional[_Union[BuildType, str]] = ..., package_name: _Optional[str] = ..., sanitizer: _Optional[str] = ..., task_dir: _Optional[str] = ..., task_id: _Optional[str] = ...) -> None: ...
|
|
||||||
|
|
||||||
class POVReproduceRequest(_message.Message):
|
|
||||||
__slots__ = ["harness_name", "internal_patch_id", "pov_path", "sanitizer", "task_id"]
|
|
||||||
HARNESS_NAME_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
INTERNAL_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
POV_PATH_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
SANITIZER_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
harness_name: str
|
|
||||||
internal_patch_id: str
|
|
||||||
pov_path: str
|
|
||||||
sanitizer: str
|
|
||||||
task_id: str
|
|
||||||
def __init__(self, task_id: _Optional[str] = ..., internal_patch_id: _Optional[str] = ..., harness_name: _Optional[str] = ..., sanitizer: _Optional[str] = ..., pov_path: _Optional[str] = ...) -> None: ...
|
|
||||||
|
|
||||||
class POVReproduceResponse(_message.Message):
|
|
||||||
__slots__ = ["did_crash", "request"]
|
|
||||||
DID_CRASH_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
REQUEST_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
did_crash: bool
|
|
||||||
request: POVReproduceRequest
|
|
||||||
def __init__(self, request: _Optional[_Union[POVReproduceRequest, _Mapping]] = ..., did_crash: bool = ...) -> None: ...
|
|
||||||
|
|
||||||
class Patch(_message.Message):
|
|
||||||
__slots__ = ["internal_patch_id", "patch", "task_id"]
|
|
||||||
INTERNAL_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
PATCH_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
internal_patch_id: str
|
|
||||||
patch: str
|
|
||||||
task_id: str
|
|
||||||
def __init__(self, task_id: _Optional[str] = ..., internal_patch_id: _Optional[str] = ..., patch: _Optional[str] = ...) -> None: ...
|
|
||||||
|
|
||||||
class SourceDetail(_message.Message):
|
class SourceDetail(_message.Message):
|
||||||
__slots__ = ["sha256", "source_type", "url"]
|
__slots__ = ("sha256", "source_type", "url")
|
||||||
class SourceType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
class SourceType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||||
__slots__ = []
|
__slots__ = ()
|
||||||
SHA256_FIELD_NUMBER: _ClassVar[int]
|
SOURCE_TYPE_REPO: _ClassVar[SourceDetail.SourceType]
|
||||||
SOURCE_TYPE_DIFF: SourceDetail.SourceType
|
SOURCE_TYPE_FUZZ_TOOLING: _ClassVar[SourceDetail.SourceType]
|
||||||
SOURCE_TYPE_FIELD_NUMBER: _ClassVar[int]
|
SOURCE_TYPE_DIFF: _ClassVar[SourceDetail.SourceType]
|
||||||
SOURCE_TYPE_FUZZ_TOOLING: SourceDetail.SourceType
|
|
||||||
SOURCE_TYPE_REPO: SourceDetail.SourceType
|
SOURCE_TYPE_REPO: SourceDetail.SourceType
|
||||||
|
SOURCE_TYPE_FUZZ_TOOLING: SourceDetail.SourceType
|
||||||
|
SOURCE_TYPE_DIFF: SourceDetail.SourceType
|
||||||
|
SHA256_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
SOURCE_TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||||
URL_FIELD_NUMBER: _ClassVar[int]
|
URL_FIELD_NUMBER: _ClassVar[int]
|
||||||
sha256: str
|
sha256: str
|
||||||
source_type: SourceDetail.SourceType
|
source_type: SourceDetail.SourceType
|
||||||
url: str
|
url: str
|
||||||
def __init__(self, sha256: _Optional[str] = ..., source_type: _Optional[_Union[SourceDetail.SourceType, str]] = ..., url: _Optional[str] = ...) -> None: ...
|
def __init__(self, sha256: _Optional[str] = ..., source_type: _Optional[_Union[SourceDetail.SourceType, str]] = ..., url: _Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
class SubmissionEntry(_message.Message):
|
|
||||||
__slots__ = ["bundles", "crashes", "patch_idx", "patch_submission_attempts", "patches", "stop"]
|
|
||||||
BUNDLES_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
CRASHES_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
PATCHES_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
PATCH_IDX_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
PATCH_SUBMISSION_ATTEMPTS_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
STOP_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
bundles: _containers.RepeatedCompositeFieldContainer[Bundle]
|
|
||||||
crashes: _containers.RepeatedCompositeFieldContainer[CrashWithId]
|
|
||||||
patch_idx: int
|
|
||||||
patch_submission_attempts: int
|
|
||||||
patches: _containers.RepeatedCompositeFieldContainer[SubmissionEntryPatch]
|
|
||||||
stop: bool
|
|
||||||
def __init__(self, stop: bool = ..., crashes: _Optional[_Iterable[_Union[CrashWithId, _Mapping]]] = ..., bundles: _Optional[_Iterable[_Union[Bundle, _Mapping]]] = ..., patches: _Optional[_Iterable[_Union[SubmissionEntryPatch, _Mapping]]] = ..., patch_idx: _Optional[int] = ..., patch_submission_attempts: _Optional[int] = ...) -> None: ...
|
|
||||||
|
|
||||||
class SubmissionEntryPatch(_message.Message):
|
|
||||||
__slots__ = ["build_outputs", "competition_patch_id", "internal_patch_id", "patch", "result"]
|
|
||||||
BUILD_OUTPUTS_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
COMPETITION_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
INTERNAL_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
PATCH_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
RESULT_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
build_outputs: _containers.RepeatedCompositeFieldContainer[BuildOutput]
|
|
||||||
competition_patch_id: str
|
|
||||||
internal_patch_id: str
|
|
||||||
patch: str
|
|
||||||
result: SubmissionResult
|
|
||||||
def __init__(self, patch: _Optional[str] = ..., internal_patch_id: _Optional[str] = ..., competition_patch_id: _Optional[str] = ..., build_outputs: _Optional[_Iterable[_Union[BuildOutput, _Mapping]]] = ..., result: _Optional[_Union[SubmissionResult, str]] = ...) -> None: ...
|
|
||||||
|
|
||||||
class Task(_message.Message):
|
|
||||||
__slots__ = ["cancelled", "deadline", "focus", "message_id", "message_time", "metadata", "project_name", "sources", "task_id", "task_type"]
|
|
||||||
class TaskType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
||||||
__slots__ = []
|
|
||||||
class MetadataEntry(_message.Message):
|
|
||||||
__slots__ = ["key", "value"]
|
|
||||||
KEY_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
VALUE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
key: str
|
|
||||||
value: str
|
|
||||||
def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ...
|
|
||||||
CANCELLED_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
DEADLINE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
FOCUS_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
MESSAGE_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
MESSAGE_TIME_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
METADATA_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
PROJECT_NAME_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
SOURCES_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_TYPE_DELTA: Task.TaskType
|
|
||||||
TASK_TYPE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_TYPE_FULL: Task.TaskType
|
|
||||||
cancelled: bool
|
|
||||||
deadline: int
|
|
||||||
focus: str
|
|
||||||
message_id: str
|
|
||||||
message_time: int
|
|
||||||
metadata: _containers.ScalarMap[str, str]
|
|
||||||
project_name: str
|
|
||||||
sources: _containers.RepeatedCompositeFieldContainer[SourceDetail]
|
|
||||||
task_id: str
|
|
||||||
task_type: Task.TaskType
|
|
||||||
def __init__(self, message_id: _Optional[str] = ..., message_time: _Optional[int] = ..., task_id: _Optional[str] = ..., task_type: _Optional[_Union[Task.TaskType, str]] = ..., sources: _Optional[_Iterable[_Union[SourceDetail, _Mapping]]] = ..., deadline: _Optional[int] = ..., cancelled: bool = ..., project_name: _Optional[str] = ..., focus: _Optional[str] = ..., metadata: _Optional[_Mapping[str, str]] = ...) -> None: ...
|
|
||||||
|
|
||||||
class TaskDelete(_message.Message):
|
|
||||||
__slots__ = ["all", "received_at", "task_id"]
|
|
||||||
ALL_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
RECEIVED_AT_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
all: bool
|
|
||||||
received_at: float
|
|
||||||
task_id: str
|
|
||||||
def __init__(self, task_id: _Optional[str] = ..., all: bool = ..., received_at: _Optional[float] = ...) -> None: ...
|
|
||||||
|
|
||||||
class TaskDownload(_message.Message):
|
class TaskDownload(_message.Message):
|
||||||
__slots__ = ["task"]
|
__slots__ = ("task",)
|
||||||
TASK_FIELD_NUMBER: _ClassVar[int]
|
TASK_FIELD_NUMBER: _ClassVar[int]
|
||||||
task: Task
|
task: Task
|
||||||
def __init__(self, task: _Optional[_Union[Task, _Mapping]] = ...) -> None: ...
|
def __init__(self, task: _Optional[_Union[Task, _Mapping]] = ...) -> None: ...
|
||||||
|
|
||||||
class TaskReady(_message.Message):
|
class TaskReady(_message.Message):
|
||||||
__slots__ = ["task"]
|
__slots__ = ("task",)
|
||||||
TASK_FIELD_NUMBER: _ClassVar[int]
|
TASK_FIELD_NUMBER: _ClassVar[int]
|
||||||
task: Task
|
task: Task
|
||||||
def __init__(self, task: _Optional[_Union[Task, _Mapping]] = ...) -> None: ...
|
def __init__(self, task: _Optional[_Union[Task, _Mapping]] = ...) -> None: ...
|
||||||
|
|
||||||
|
class TaskDelete(_message.Message):
|
||||||
|
__slots__ = ("task_id", "all", "received_at")
|
||||||
|
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
ALL_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
RECEIVED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
task_id: str
|
||||||
|
all: bool
|
||||||
|
received_at: float
|
||||||
|
def __init__(self, task_id: _Optional[str] = ..., all: bool = ..., received_at: _Optional[float] = ...) -> None: ...
|
||||||
|
|
||||||
|
class BuildRequest(_message.Message):
|
||||||
|
__slots__ = ("engine", "sanitizer", "task_dir", "task_id", "build_type", "apply_diff", "patch", "internal_patch_id")
|
||||||
|
ENGINE_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
SANITIZER_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
TASK_DIR_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
BUILD_TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
APPLY_DIFF_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
PATCH_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
INTERNAL_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
engine: str
|
||||||
|
sanitizer: str
|
||||||
|
task_dir: str
|
||||||
|
task_id: str
|
||||||
|
build_type: BuildType
|
||||||
|
apply_diff: bool
|
||||||
|
patch: str
|
||||||
|
internal_patch_id: str
|
||||||
|
def __init__(self, engine: _Optional[str] = ..., sanitizer: _Optional[str] = ..., task_dir: _Optional[str] = ..., task_id: _Optional[str] = ..., build_type: _Optional[_Union[BuildType, str]] = ..., apply_diff: bool = ..., patch: _Optional[str] = ..., internal_patch_id: _Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
|
class BuildOutput(_message.Message):
|
||||||
|
__slots__ = ("engine", "sanitizer", "task_dir", "task_id", "build_type", "apply_diff", "internal_patch_id")
|
||||||
|
ENGINE_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
SANITIZER_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
TASK_DIR_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
BUILD_TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
APPLY_DIFF_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
INTERNAL_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
engine: str
|
||||||
|
sanitizer: str
|
||||||
|
task_dir: str
|
||||||
|
task_id: str
|
||||||
|
build_type: BuildType
|
||||||
|
apply_diff: bool
|
||||||
|
internal_patch_id: str
|
||||||
|
def __init__(self, engine: _Optional[str] = ..., sanitizer: _Optional[str] = ..., task_dir: _Optional[str] = ..., task_id: _Optional[str] = ..., build_type: _Optional[_Union[BuildType, str]] = ..., apply_diff: bool = ..., internal_patch_id: _Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
|
class WeightedHarness(_message.Message):
|
||||||
|
__slots__ = ("weight", "package_name", "harness_name", "task_id")
|
||||||
|
WEIGHT_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
PACKAGE_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
HARNESS_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
weight: float
|
||||||
|
package_name: str
|
||||||
|
harness_name: str
|
||||||
|
task_id: str
|
||||||
|
def __init__(self, weight: _Optional[float] = ..., package_name: _Optional[str] = ..., harness_name: _Optional[str] = ..., task_id: _Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
|
class Crash(_message.Message):
|
||||||
|
__slots__ = ("target", "harness_name", "crash_input_path", "stacktrace", "crash_token")
|
||||||
|
TARGET_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
HARNESS_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
CRASH_INPUT_PATH_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
STACKTRACE_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
CRASH_TOKEN_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
target: BuildOutput
|
||||||
|
harness_name: str
|
||||||
|
crash_input_path: str
|
||||||
|
stacktrace: str
|
||||||
|
crash_token: str
|
||||||
|
def __init__(self, target: _Optional[_Union[BuildOutput, _Mapping]] = ..., harness_name: _Optional[str] = ..., crash_input_path: _Optional[str] = ..., stacktrace: _Optional[str] = ..., crash_token: _Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
class TracedCrash(_message.Message):
|
class TracedCrash(_message.Message):
|
||||||
__slots__ = ["crash", "tracer_stacktrace"]
|
__slots__ = ("crash", "tracer_stacktrace")
|
||||||
CRASH_FIELD_NUMBER: _ClassVar[int]
|
CRASH_FIELD_NUMBER: _ClassVar[int]
|
||||||
TRACER_STACKTRACE_FIELD_NUMBER: _ClassVar[int]
|
TRACER_STACKTRACE_FIELD_NUMBER: _ClassVar[int]
|
||||||
crash: Crash
|
crash: Crash
|
||||||
tracer_stacktrace: str
|
tracer_stacktrace: str
|
||||||
def __init__(self, crash: _Optional[_Union[Crash, _Mapping]] = ..., tracer_stacktrace: _Optional[str] = ...) -> None: ...
|
def __init__(self, crash: _Optional[_Union[Crash, _Mapping]] = ..., tracer_stacktrace: _Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
class WeightedHarness(_message.Message):
|
class ConfirmedVulnerability(_message.Message):
|
||||||
__slots__ = ["harness_name", "package_name", "task_id", "weight"]
|
__slots__ = ("crashes", "internal_patch_id")
|
||||||
HARNESS_NAME_FIELD_NUMBER: _ClassVar[int]
|
CRASHES_FIELD_NUMBER: _ClassVar[int]
|
||||||
PACKAGE_NAME_FIELD_NUMBER: _ClassVar[int]
|
INTERNAL_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
crashes: _containers.RepeatedCompositeFieldContainer[TracedCrash]
|
||||||
|
internal_patch_id: str
|
||||||
|
def __init__(self, crashes: _Optional[_Iterable[_Union[TracedCrash, _Mapping]]] = ..., internal_patch_id: _Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
|
class Patch(_message.Message):
|
||||||
|
__slots__ = ("task_id", "internal_patch_id", "patch")
|
||||||
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
WEIGHT_FIELD_NUMBER: _ClassVar[int]
|
INTERNAL_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
harness_name: str
|
PATCH_FIELD_NUMBER: _ClassVar[int]
|
||||||
package_name: str
|
|
||||||
task_id: str
|
task_id: str
|
||||||
weight: float
|
internal_patch_id: str
|
||||||
def __init__(self, weight: _Optional[float] = ..., package_name: _Optional[str] = ..., harness_name: _Optional[str] = ..., task_id: _Optional[str] = ...) -> None: ...
|
patch: str
|
||||||
|
def __init__(self, task_id: _Optional[str] = ..., internal_patch_id: _Optional[str] = ..., patch: _Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
class BuildType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
class IndexRequest(_message.Message):
|
||||||
__slots__ = []
|
__slots__ = ("build_type", "package_name", "sanitizer", "task_dir", "task_id")
|
||||||
|
BUILD_TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
PACKAGE_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
SANITIZER_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
TASK_DIR_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
build_type: BuildType
|
||||||
|
package_name: str
|
||||||
|
sanitizer: str
|
||||||
|
task_dir: str
|
||||||
|
task_id: str
|
||||||
|
def __init__(self, build_type: _Optional[_Union[BuildType, str]] = ..., package_name: _Optional[str] = ..., sanitizer: _Optional[str] = ..., task_dir: _Optional[str] = ..., task_id: _Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
class SubmissionResult(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
class IndexOutput(_message.Message):
|
||||||
__slots__ = []
|
__slots__ = ("build_type", "package_name", "sanitizer", "task_dir", "task_id")
|
||||||
|
BUILD_TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
PACKAGE_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
SANITIZER_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
TASK_DIR_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
build_type: BuildType
|
||||||
|
package_name: str
|
||||||
|
sanitizer: str
|
||||||
|
task_dir: str
|
||||||
|
task_id: str
|
||||||
|
def __init__(self, build_type: _Optional[_Union[BuildType, str]] = ..., package_name: _Optional[str] = ..., sanitizer: _Optional[str] = ..., task_dir: _Optional[str] = ..., task_id: _Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
|
class FunctionCoverage(_message.Message):
|
||||||
|
__slots__ = ("function_name", "function_paths", "total_lines", "covered_lines")
|
||||||
|
FUNCTION_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
FUNCTION_PATHS_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
TOTAL_LINES_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
COVERED_LINES_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
function_name: str
|
||||||
|
function_paths: _containers.RepeatedScalarFieldContainer[str]
|
||||||
|
total_lines: int
|
||||||
|
covered_lines: int
|
||||||
|
def __init__(self, function_name: _Optional[str] = ..., function_paths: _Optional[_Iterable[str]] = ..., total_lines: _Optional[int] = ..., covered_lines: _Optional[int] = ...) -> None: ...
|
||||||
|
|
||||||
|
class SubmissionEntryPatch(_message.Message):
|
||||||
|
__slots__ = ("patch", "internal_patch_id", "competition_patch_id", "build_outputs", "result")
|
||||||
|
PATCH_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
INTERNAL_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
COMPETITION_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
BUILD_OUTPUTS_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
RESULT_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
patch: str
|
||||||
|
internal_patch_id: str
|
||||||
|
competition_patch_id: str
|
||||||
|
build_outputs: _containers.RepeatedCompositeFieldContainer[BuildOutput]
|
||||||
|
result: SubmissionResult
|
||||||
|
def __init__(self, patch: _Optional[str] = ..., internal_patch_id: _Optional[str] = ..., competition_patch_id: _Optional[str] = ..., build_outputs: _Optional[_Iterable[_Union[BuildOutput, _Mapping]]] = ..., result: _Optional[_Union[SubmissionResult, str]] = ...) -> None: ...
|
||||||
|
|
||||||
|
class Bundle(_message.Message):
|
||||||
|
__slots__ = ("task_id", "competition_pov_id", "competition_patch_id", "competition_sarif_id", "bundle_id")
|
||||||
|
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
COMPETITION_POV_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
COMPETITION_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
COMPETITION_SARIF_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
BUNDLE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
task_id: str
|
||||||
|
competition_pov_id: str
|
||||||
|
competition_patch_id: str
|
||||||
|
competition_sarif_id: str
|
||||||
|
bundle_id: str
|
||||||
|
def __init__(self, task_id: _Optional[str] = ..., competition_pov_id: _Optional[str] = ..., competition_patch_id: _Optional[str] = ..., competition_sarif_id: _Optional[str] = ..., bundle_id: _Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
|
class CrashWithId(_message.Message):
|
||||||
|
__slots__ = ("crash", "competition_pov_id", "result")
|
||||||
|
CRASH_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
COMPETITION_POV_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
RESULT_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
crash: TracedCrash
|
||||||
|
competition_pov_id: str
|
||||||
|
result: SubmissionResult
|
||||||
|
def __init__(self, crash: _Optional[_Union[TracedCrash, _Mapping]] = ..., competition_pov_id: _Optional[str] = ..., result: _Optional[_Union[SubmissionResult, str]] = ...) -> None: ...
|
||||||
|
|
||||||
|
class SubmissionEntry(_message.Message):
|
||||||
|
__slots__ = ("stop", "crashes", "bundles", "patches", "patch_idx", "patch_submission_attempts")
|
||||||
|
STOP_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
CRASHES_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
BUNDLES_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
PATCHES_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
PATCH_IDX_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
PATCH_SUBMISSION_ATTEMPTS_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
stop: bool
|
||||||
|
crashes: _containers.RepeatedCompositeFieldContainer[CrashWithId]
|
||||||
|
bundles: _containers.RepeatedCompositeFieldContainer[Bundle]
|
||||||
|
patches: _containers.RepeatedCompositeFieldContainer[SubmissionEntryPatch]
|
||||||
|
patch_idx: int
|
||||||
|
patch_submission_attempts: int
|
||||||
|
def __init__(self, stop: bool = ..., crashes: _Optional[_Iterable[_Union[CrashWithId, _Mapping]]] = ..., bundles: _Optional[_Iterable[_Union[Bundle, _Mapping]]] = ..., patches: _Optional[_Iterable[_Union[SubmissionEntryPatch, _Mapping]]] = ..., patch_idx: _Optional[int] = ..., patch_submission_attempts: _Optional[int] = ...) -> None: ...
|
||||||
|
|
||||||
|
class POVReproduceRequest(_message.Message):
|
||||||
|
__slots__ = ("task_id", "internal_patch_id", "harness_name", "sanitizer", "pov_path")
|
||||||
|
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
INTERNAL_PATCH_ID_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
HARNESS_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
SANITIZER_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
POV_PATH_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
task_id: str
|
||||||
|
internal_patch_id: str
|
||||||
|
harness_name: str
|
||||||
|
sanitizer: str
|
||||||
|
pov_path: str
|
||||||
|
def __init__(self, task_id: _Optional[str] = ..., internal_patch_id: _Optional[str] = ..., harness_name: _Optional[str] = ..., sanitizer: _Optional[str] = ..., pov_path: _Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
|
class POVReproduceResponse(_message.Message):
|
||||||
|
__slots__ = ("request", "did_crash")
|
||||||
|
REQUEST_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
DID_CRASH_FIELD_NUMBER: _ClassVar[int]
|
||||||
|
request: POVReproduceRequest
|
||||||
|
did_crash: bool
|
||||||
|
def __init__(self, request: _Optional[_Union[POVReproduceRequest, _Mapping]] = ..., did_crash: bool = ...) -> None: ...
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from opentelemetry._logs import set_logger_provider
|
try:
|
||||||
|
from opentelemetry._logs import set_logger_provider
|
||||||
|
|
||||||
if os.environ.get("OTEL_EXPORTER_OTLP_PROTOCOL") == "grpc":
|
if os.environ.get("OTEL_EXPORTER_OTLP_PROTOCOL") == "grpc":
|
||||||
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
|
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
|
||||||
else:
|
else:
|
||||||
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter # type: ignore
|
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter # type: ignore
|
||||||
|
|
||||||
|
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
||||||
|
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
||||||
|
from opentelemetry.sdk.resources import Resource
|
||||||
|
|
||||||
|
_opentelemetry_enabled = True
|
||||||
|
except ImportError:
|
||||||
|
_opentelemetry_enabled = False
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
|
||||||
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
|
||||||
from opentelemetry.sdk.resources import Resource
|
|
||||||
|
|
||||||
from buttercup.common.telemetry import crs_instance_id, service_instance_id
|
from buttercup.common.telemetry import crs_instance_id, service_instance_id
|
||||||
|
|
||||||
_is_initialized = False
|
_is_initialized = False
|
||||||
@@ -48,26 +53,27 @@ def setup_package_logger(
|
|||||||
root.removeHandler(handler)
|
root.removeHandler(handler)
|
||||||
|
|
||||||
# Create resource with service and environment information
|
# Create resource with service and environment information
|
||||||
resource = Resource.create(
|
|
||||||
attributes={
|
|
||||||
"service.name": application_name,
|
|
||||||
"service.instance.id": service_instance_id,
|
|
||||||
"crs.instance.id": crs_instance_id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize the LoggerProvider with the created resource.
|
|
||||||
logger_provider = LoggerProvider(resource=resource)
|
|
||||||
|
|
||||||
# Configure the span exporter and processor based on whether the endpoint is effectively set.
|
|
||||||
otlp_handler = None
|
otlp_handler = None
|
||||||
if os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT"):
|
if _opentelemetry_enabled:
|
||||||
set_logger_provider(logger_provider)
|
resource = Resource.create(
|
||||||
exporter = OTLPLogExporter()
|
attributes={
|
||||||
|
"service.name": application_name,
|
||||||
|
"service.instance.id": service_instance_id,
|
||||||
|
"crs.instance.id": crs_instance_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# add the batch processors to the trace provider
|
# Initialize the LoggerProvider with the created resource.
|
||||||
logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter))
|
logger_provider = LoggerProvider(resource=resource)
|
||||||
otlp_handler = LoggingHandler(level=logging.DEBUG, logger_provider=logger_provider)
|
|
||||||
|
# Configure the span exporter and processor based on whether the endpoint is effectively set.
|
||||||
|
if os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT"):
|
||||||
|
set_logger_provider(logger_provider)
|
||||||
|
exporter = OTLPLogExporter()
|
||||||
|
|
||||||
|
# add the batch processors to the trace provider
|
||||||
|
logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter))
|
||||||
|
otlp_handler = LoggingHandler(level=logging.DEBUG, logger_provider=logger_provider)
|
||||||
|
|
||||||
persistent_log_dir = os.getenv("PERSISTENT_LOG_DIR", None)
|
persistent_log_dir = os.getenv("PERSISTENT_LOG_DIR", None)
|
||||||
|
|
||||||
|
|||||||
@@ -444,19 +444,3 @@ class QueueFactory:
|
|||||||
|
|
||||||
queue_args.update(kwargs)
|
queue_args.update(kwargs)
|
||||||
return ReliableQueue(**queue_args) # type: ignore[arg-type]
|
return ReliableQueue(**queue_args) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class FuzzConfiguration:
|
|
||||||
corpus_dir: str
|
|
||||||
target_path: str
|
|
||||||
engine: str
|
|
||||||
sanitizer: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class BuildConfiguration:
|
|
||||||
project_id: str
|
|
||||||
engine: str
|
|
||||||
sanitizer: str
|
|
||||||
source_path: str | None
|
|
||||||
|
|||||||
@@ -4,28 +4,22 @@ import uuid
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import openlit
|
|
||||||
import opentelemetry.attributes
|
|
||||||
from langchain_core.prompt_values import ChatPromptValue
|
|
||||||
from opentelemetry import trace
|
|
||||||
from opentelemetry.trace import Span, Status, StatusCode, Tracer
|
|
||||||
|
|
||||||
# Monkey patch the _clean_attribute function to handle ChatPromptValue
|
|
||||||
_clean_attribute_orig = opentelemetry.attributes._clean_attribute
|
|
||||||
|
|
||||||
|
|
||||||
def _clean_attribute_wrapper(key: str, value: Any, max_len: int | None = None) -> Any:
|
|
||||||
"""Wrapper around _clean_attribute to add custom behavior"""
|
|
||||||
if isinstance(value, ChatPromptValue):
|
|
||||||
value = value.to_string()
|
|
||||||
|
|
||||||
return _clean_attribute_orig(key, value, max_len)
|
|
||||||
|
|
||||||
|
|
||||||
opentelemetry.attributes._clean_attribute = _clean_attribute_wrapper
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import openlit
|
||||||
|
from opentelemetry import trace
|
||||||
|
from opentelemetry.trace import Span, Status, StatusCode, Tracer
|
||||||
|
|
||||||
|
_opentelemetry_enabled = True
|
||||||
|
except ImportError:
|
||||||
|
logger.warning("OpenTelemetry is not installed, skipping telemetry")
|
||||||
|
|
||||||
|
Span: type = Any # type: ignore[no-redef]
|
||||||
|
Tracer: type = Any # type: ignore[no-redef]
|
||||||
|
|
||||||
|
_opentelemetry_enabled = False
|
||||||
|
|
||||||
crs_instance_id = os.getenv("CRS_INSTANCE_ID", str(uuid.uuid4()))
|
crs_instance_id = os.getenv("CRS_INSTANCE_ID", str(uuid.uuid4()))
|
||||||
service_instance_id = str(uuid.uuid4())
|
service_instance_id = str(uuid.uuid4())
|
||||||
|
|
||||||
@@ -47,6 +41,9 @@ class CRSActionCategory(Enum):
|
|||||||
|
|
||||||
def init_telemetry(application_name: str) -> None:
|
def init_telemetry(application_name: str) -> None:
|
||||||
"""Initialize the telemetry for the application."""
|
"""Initialize the telemetry for the application."""
|
||||||
|
if not _opentelemetry_enabled:
|
||||||
|
return
|
||||||
|
|
||||||
logger.info("Initializing telemetry for %s", application_name)
|
logger.info("Initializing telemetry for %s", application_name)
|
||||||
if not os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT"):
|
if not os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT"):
|
||||||
logger.error("OTEL_EXPORTER_OTLP_ENDPOINT not set, disabling telemetry")
|
logger.error("OTEL_EXPORTER_OTLP_ENDPOINT not set, disabling telemetry")
|
||||||
@@ -75,6 +72,9 @@ def set_crs_attributes(
|
|||||||
task_metadata: dict,
|
task_metadata: dict,
|
||||||
extra_attributes: dict | None = None,
|
extra_attributes: dict | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if not _opentelemetry_enabled:
|
||||||
|
return
|
||||||
|
|
||||||
extra_attributes = extra_attributes or {}
|
extra_attributes = extra_attributes or {}
|
||||||
span.set_attribute("crs.action.category", crs_action_category.value)
|
span.set_attribute("crs.action.category", crs_action_category.value)
|
||||||
span.set_attribute("crs.action.name", crs_action_name)
|
span.set_attribute("crs.action.name", crs_action_name)
|
||||||
@@ -95,6 +95,9 @@ def log_crs_action_ok(
|
|||||||
task_metadata: dict,
|
task_metadata: dict,
|
||||||
extra_attributes: dict | None = None,
|
extra_attributes: dict | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if not _opentelemetry_enabled:
|
||||||
|
return
|
||||||
|
|
||||||
extra_attributes = extra_attributes or {}
|
extra_attributes = extra_attributes or {}
|
||||||
with tracer.start_as_current_span(crs_action_name) as span:
|
with tracer.start_as_current_span(crs_action_name) as span:
|
||||||
span.set_attribute("crs.action.category", crs_action_category.value)
|
span.set_attribute("crs.action.category", crs_action_category.value)
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FuzzConfiguration:
|
||||||
|
corpus_dir: str
|
||||||
|
target_path: str
|
||||||
|
engine: str
|
||||||
|
sanitizer: str
|
||||||
@@ -25,7 +25,9 @@ def copyanything(src: PathLike, dst: PathLike, **kwargs: Any) -> None:
|
|||||||
"""
|
"""
|
||||||
src, dst = Path(src), Path(dst)
|
src, dst = Path(src), Path(dst)
|
||||||
try:
|
try:
|
||||||
shutil.copytree(src, dst, dirs_exist_ok=True, **kwargs)
|
shutil.copytree(src, dst, dirs_exist_ok=True, ignore_dangling_symlinks=True, **kwargs)
|
||||||
|
except shutil.Error:
|
||||||
|
logger.exception(f"Some errors occurred while copying {src} to {dst}, continuing anyway...")
|
||||||
except OSError as exc: # python >2.5
|
except OSError as exc: # python >2.5
|
||||||
if exc.errno in (errno.ENOTDIR, errno.EINVAL):
|
if exc.errno in (errno.ENOTDIR, errno.EINVAL):
|
||||||
shutil.copy(src, dst)
|
shutil.copy(src, dst)
|
||||||
|
|||||||
Generated
+509
-405
File diff suppressed because it is too large
Load Diff
@@ -134,7 +134,7 @@ up() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
docker build $ORCHESTRATOR_BUILD_ARGS -f "$SCRIPT_DIR"/../orchestrator/Dockerfile -t localhost/orchestrator:latest "$SCRIPT_DIR"/..
|
docker build $ORCHESTRATOR_BUILD_ARGS -f "$SCRIPT_DIR"/../orchestrator/Dockerfile -t localhost/orchestrator:latest "$SCRIPT_DIR"/..
|
||||||
docker build $FUZZER_BUILD_ARGS -f "$SCRIPT_DIR"/../fuzzer/dockerfiles/runner_image.Dockerfile -t localhost/fuzzer:latest "$SCRIPT_DIR"/..
|
docker build $FUZZER_BUILD_ARGS -f "$SCRIPT_DIR"/../fuzzer/Dockerfile -t localhost/fuzzer:latest "$SCRIPT_DIR"/..
|
||||||
docker build $SEED_GEN_BUILD_ARGS -f "$SCRIPT_DIR"/../seed-gen/Dockerfile -t localhost/seed-gen:latest "$SCRIPT_DIR"/..
|
docker build $SEED_GEN_BUILD_ARGS -f "$SCRIPT_DIR"/../seed-gen/Dockerfile -t localhost/seed-gen:latest "$SCRIPT_DIR"/..
|
||||||
docker build $PATCHER_BUILD_ARGS -f "$SCRIPT_DIR"/../patcher/Dockerfile -t localhost/patcher:latest "$SCRIPT_DIR"/..
|
docker build $PATCHER_BUILD_ARGS -f "$SCRIPT_DIR"/../patcher/Dockerfile -t localhost/patcher:latest "$SCRIPT_DIR"/..
|
||||||
docker build $PROGRAM_MODEL_BUILD_ARGS -f "$SCRIPT_DIR"/../program-model/Dockerfile -t localhost/program-model:latest "$SCRIPT_DIR"/..
|
docker build $PROGRAM_MODEL_BUILD_ARGS -f "$SCRIPT_DIR"/../program-model/Dockerfile -t localhost/program-model:latest "$SCRIPT_DIR"/..
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ spec:
|
|||||||
- name: merger-bot
|
- name: merger-bot
|
||||||
image: "{{ .Values.global.fuzzerImage.repository }}:{{ .Values.global.fuzzerImage.tag }}"
|
image: "{{ .Values.global.fuzzerImage.repository }}:{{ .Values.global.fuzzerImage.tag }}"
|
||||||
imagePullPolicy: {{ .Values.global.fuzzerImage.pullPolicy }}
|
imagePullPolicy: {{ .Values.global.fuzzerImage.pullPolicy }}
|
||||||
command: ["buttercup-corpus-merger", "--crs_scratch_dir", "{{ include "buttercup.nodeLocalCrsScratchPath" . }}", "--redis_url", "{{ include "buttercup.core.redisUrl" . }}", "--timer", "{{ .Values.timer }}", "--timeout", "{{ .Values.timeout }}", "--crash_dir_count_limit", "{{ include "buttercup.core.crashDirCountLimit" . }}", "--max_local_files", "{{ .Values.max_local_files }}"]
|
command: ["buttercup-corpus-merger", "--crs_scratch_dir", "{{ include "buttercup.nodeLocalCrsScratchPath" . }}", "--redis_url", "{{ include "buttercup.core.redisUrl" . }}", "--timer", "{{ .Values.timer }}", "--timeout", "{{ .Values.timeout }}", "--crash_dir_count_limit", "{{ include "buttercup.core.crashDirCountLimit" . }}", "--max_local_files", "{{ .Values.max_local_files }}", "--runner_path", "/app/fuzzer_runner/runner.sh"]
|
||||||
env:
|
env:
|
||||||
{{- include "buttercup.env.nodeData" . | nindent 8 }}
|
{{- include "buttercup.env.nodeData" . | nindent 8 }}
|
||||||
{{- include "buttercup.env.telemetry" . | nindent 8 }}
|
{{- include "buttercup.env.telemetry" . | nindent 8 }}
|
||||||
|
|||||||
@@ -336,6 +336,8 @@ Service-specific environment variables that utilize the standardized variables
|
|||||||
value: "{{ include "buttercup.core.logMaxLineLength" . }}"
|
value: "{{ include "buttercup.core.logMaxLineLength" . }}"
|
||||||
- name: BUTTERCUP_FUZZER_MAX_POV_SIZE
|
- name: BUTTERCUP_FUZZER_MAX_POV_SIZE
|
||||||
value: {{ int .Values.global.maxPovSize | quote }}
|
value: {{ int .Values.global.maxPovSize | quote }}
|
||||||
|
- name: BUTTERCUP_FUZZER_RUNNER_PATH
|
||||||
|
value: "/app/fuzzer_runner/runner.sh"
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- define "buttercup.povReproducerEnv" }}
|
{{- define "buttercup.povReproducerEnv" }}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ services:
|
|||||||
stimulate-fuzzer-test:
|
stimulate-fuzzer-test:
|
||||||
build:
|
build:
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: ./fuzzer/dockerfiles/runner_image.Dockerfile
|
dockerfile: ./fuzzer/Dockerfile
|
||||||
command: /fuzzer/.venv/bin/python -m buttercup.fuzzing_infra.stimulate_build_bot --build_type fuzzer --ossfuzz /crs_scratch/oss-fuzz --engine libfuzzer --sanitizer address --target_package ${TARGET_PACKAGE} --redis_url redis://redis:6379 --source_path /crs_scratch/${TARGET_PACKAGE} --task_id 123e4567-e89b-12d3-a456-426614174000
|
command: /fuzzer/.venv/bin/python -m buttercup.fuzzing_infra.stimulate_build_bot --build_type fuzzer --ossfuzz /crs_scratch/oss-fuzz --engine libfuzzer --sanitizer address --target_package ${TARGET_PACKAGE} --redis_url redis://redis:6379 --source_path /crs_scratch/${TARGET_PACKAGE} --task_id 123e4567-e89b-12d3-a456-426614174000
|
||||||
profiles:
|
profiles:
|
||||||
- fuzzer-test
|
- fuzzer-test
|
||||||
@@ -63,7 +63,7 @@ services:
|
|||||||
orchestrator-sim:
|
orchestrator-sim:
|
||||||
build:
|
build:
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: ./fuzzer/dockerfiles/runner_image.Dockerfile
|
dockerfile: ./fuzzer/Dockerfile
|
||||||
command: /fuzzer/.venv/bin/python -m buttercup.fuzzing_infra.orchestrator --redis_url redis://redis:6379
|
command: /fuzzer/.venv/bin/python -m buttercup.fuzzing_infra.orchestrator --redis_url redis://redis:6379
|
||||||
profiles:
|
profiles:
|
||||||
- fuzzer-test
|
- fuzzer-test
|
||||||
@@ -140,7 +140,7 @@ services:
|
|||||||
command: ["buttercup-coverage-bot", "--wdir", "/node_data/crs_scratch", "--redis_url", "redis://redis:6379"]
|
command: ["buttercup-coverage-bot", "--wdir", "/node_data/crs_scratch", "--redis_url", "redis://redis:6379"]
|
||||||
build:
|
build:
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: ./fuzzer/dockerfiles/runner_image.Dockerfile
|
dockerfile: ./fuzzer/Dockerfile
|
||||||
volumes:
|
volumes:
|
||||||
- ../../crs_scratch:/crs_scratch
|
- ../../crs_scratch:/crs_scratch
|
||||||
- ../../tasks_storage:/tasks_storage
|
- ../../tasks_storage:/tasks_storage
|
||||||
@@ -156,7 +156,7 @@ services:
|
|||||||
command: ["buttercup-fuzzer-builder", "--wdir", "/node_data/crs_scratch", "--redis_url", "redis://redis:6379", "--timer", "5000"]
|
command: ["buttercup-fuzzer-builder", "--wdir", "/node_data/crs_scratch", "--redis_url", "redis://redis:6379", "--timer", "5000"]
|
||||||
build:
|
build:
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: ./fuzzer/dockerfiles/runner_image.Dockerfile
|
dockerfile: ./fuzzer/Dockerfile
|
||||||
volumes:
|
volumes:
|
||||||
- ../../crs_scratch:/crs_scratch
|
- ../../crs_scratch:/crs_scratch
|
||||||
- ../../tasks_storage:/tasks_storage
|
- ../../tasks_storage:/tasks_storage
|
||||||
@@ -172,7 +172,7 @@ services:
|
|||||||
command: ["buttercup-tracer-bot", "--wdir", "/node_data/crs_scratch", "--redis_url", "redis://redis:6379"]
|
command: ["buttercup-tracer-bot", "--wdir", "/node_data/crs_scratch", "--redis_url", "redis://redis:6379"]
|
||||||
build:
|
build:
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: ./fuzzer/dockerfiles/runner_image.Dockerfile
|
dockerfile: ./fuzzer/Dockerfile
|
||||||
volumes:
|
volumes:
|
||||||
- ../../crs_scratch:/crs_scratch
|
- ../../crs_scratch:/crs_scratch
|
||||||
- ../../tasks_storage:/tasks_storage
|
- ../../tasks_storage:/tasks_storage
|
||||||
@@ -187,8 +187,8 @@ services:
|
|||||||
fuzzer-bot:
|
fuzzer-bot:
|
||||||
build:
|
build:
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: ./fuzzer/dockerfiles/runner_image.Dockerfile
|
dockerfile: ./fuzzer/Dockerfile
|
||||||
command: ["buttercup-fuzzer", "--crs_scratch_dir", "/node_data/crs_scratch", "--redis_url", "redis://redis:6379", "--timeout", "900", "--timer", "5000", "--crash_dir_count_limit", "100"]
|
command: ["buttercup-fuzzer"]
|
||||||
env_file: env.dev.compose
|
env_file: env.dev.compose
|
||||||
volumes:
|
volumes:
|
||||||
- ../../crs_scratch:/crs_scratch
|
- ../../crs_scratch:/crs_scratch
|
||||||
@@ -312,7 +312,7 @@ services:
|
|||||||
dockerfile: ./orchestrator/Dockerfile
|
dockerfile: ./orchestrator/Dockerfile
|
||||||
command: ["buttercup-ui"]
|
command: ["buttercup-ui"]
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:1323:1323"
|
- "127.0.0.1:31323:1323"
|
||||||
env_file: env.dev.compose
|
env_file: env.dev.compose
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -46,10 +46,15 @@ BUTTERCUP_PROGRAM_MODEL_LOG_LEVEL=debug
|
|||||||
BUTTERCUP_PROGRAM_MODEL_SCRATCH_DIR=/node_data/crs_scratch
|
BUTTERCUP_PROGRAM_MODEL_SCRATCH_DIR=/node_data/crs_scratch
|
||||||
BUTTERCUP_PROGRAM_MODEL_GRAPHDB_ENABLED=false
|
BUTTERCUP_PROGRAM_MODEL_GRAPHDB_ENABLED=false
|
||||||
|
|
||||||
|
BUTTERCUP_FUZZER_REDIS_URL=redis://redis:6379
|
||||||
BUTTERCUP_FUZZER_WDIR=/node_data/crs_scratch
|
BUTTERCUP_FUZZER_WDIR=/node_data/crs_scratch
|
||||||
|
BUTTERCUP_FUZZER_CRS_SCRATCH_DIR=/node_data/crs_scratch
|
||||||
|
BUTTERCUP_FUZZER_CRASH_DIR_COUNT_LIMIT=100
|
||||||
BUTTERCUP_FUZZER_SAMPLE_SIZE=200
|
BUTTERCUP_FUZZER_SAMPLE_SIZE=200
|
||||||
BUTTERCUP_FUZZER_LOG_LEVEL=debug
|
BUTTERCUP_FUZZER_LOG_LEVEL=debug
|
||||||
BUTTERCUP_FUZZER_MAX_POV_SIZE=2097152
|
BUTTERCUP_FUZZER_MAX_POV_SIZE=2097152
|
||||||
|
BUTTERCUP_FUZZER_RUNNER_PATH=/app/fuzzer_runner/runner.sh
|
||||||
|
BUTTERCUP_FUZZER_TIMEOUT=60
|
||||||
|
|
||||||
BUTTERCUP_SEED_GEN_LOG_LEVEL=debug
|
BUTTERCUP_SEED_GEN_LOG_LEVEL=debug
|
||||||
BUTTERCUP_SEED_GEN_SERVER__REDIS_URL=redis://redis:6379
|
BUTTERCUP_SEED_GEN_SERVER__REDIS_URL=redis://redis:6379
|
||||||
@@ -86,6 +91,7 @@ OTEL_EXPORTER_OTLP_PROTOCOL="grpc"
|
|||||||
OSS_FUZZ_CONTAINER_ORG=aixcc-afc
|
OSS_FUZZ_CONTAINER_ORG=aixcc-afc
|
||||||
|
|
||||||
BUTTERCUP_UI_HOST=buttercup-ui
|
BUTTERCUP_UI_HOST=buttercup-ui
|
||||||
|
BUTTERCUP_UI_EXTERNAL_HOST=buttercup-ui
|
||||||
BUTTERCUP_UI_CRS_BASE_URL="http://task-server:8000"
|
BUTTERCUP_UI_CRS_BASE_URL="http://task-server:8000"
|
||||||
BUTTERCUP_UI_STORAGE_DIR="/tmp/buttercup-ui"
|
BUTTERCUP_UI_STORAGE_DIR="/tmp/buttercup-ui"
|
||||||
BUTTERCUP_UI_LOG_LEVEL="info"
|
BUTTERCUP_UI_LOG_LEVEL="info"
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
ARG BASE_IMAGE=gcr.io/oss-fuzz-base/base-runner
|
||||||
|
|
||||||
|
FROM $BASE_IMAGE AS base-image
|
||||||
|
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:0.5.20 /uv /uvx /bin/
|
||||||
|
|
||||||
|
ENV UV_LINK_MODE=copy
|
||||||
|
ENV UV_COMPILE_BYTECODE=1
|
||||||
|
ENV UV_PYTHON_DOWNLOADS=manual
|
||||||
|
|
||||||
|
RUN uv python install python3.12
|
||||||
|
|
||||||
|
FROM base-image AS runner-base
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
|
||||||
|
&& DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
RUN install -m 0755 -d /etc/apt/keyrings
|
||||||
|
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||||
|
RUN chmod a+r /etc/apt/keyrings/docker.asc
|
||||||
|
|
||||||
|
# Add the repository to Apt sources:
|
||||||
|
RUN echo \
|
||||||
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
||||||
|
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
||||||
|
tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
|
||||||
|
&& DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
FROM base-image AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies for fuzzer-bot
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
|
--mount=type=bind,source=common/uv.lock,target=common/uv.lock \
|
||||||
|
--mount=type=bind,source=common/pyproject.toml,target=common/pyproject.toml \
|
||||||
|
--mount=type=bind,source=common/README.md,target=common/README.md \
|
||||||
|
--mount=type=bind,source=fuzzer/uv.lock,target=fuzzer/uv.lock \
|
||||||
|
--mount=type=bind,source=fuzzer/pyproject.toml,target=fuzzer/pyproject.toml \
|
||||||
|
--mount=type=bind,source=fuzzer_runner/uv.lock,target=fuzzer_runner/uv.lock \
|
||||||
|
--mount=type=bind,source=fuzzer_runner/pyproject.toml,target=fuzzer_runner/pyproject.toml \
|
||||||
|
cd fuzzer && uv sync --frozen --no-install-project --no-editable && cd .. \
|
||||||
|
&& cd fuzzer_runner && uv sync --frozen --no-install-project --no-editable && cd ..
|
||||||
|
|
||||||
|
ADD common /app/common
|
||||||
|
ADD fuzzer /app/fuzzer
|
||||||
|
ADD fuzzer_runner /app/fuzzer_runner
|
||||||
|
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
|
cd fuzzer && uv sync --frozen --no-editable
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
|
cd fuzzer_runner && uv sync --frozen --no-editable
|
||||||
|
|
||||||
|
FROM runner-base AS runtime
|
||||||
|
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
|
apt-get install -y git \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=builder --chown=app:app /app/fuzzer/.venv /app/fuzzer/.venv
|
||||||
|
COPY --from=builder --chown=app:app /app/fuzzer_runner/.venv /app/fuzzer_runner/.venv
|
||||||
|
COPY common/container-entrypoint.sh /container-entrypoint.sh
|
||||||
|
COPY fuzzer_runner/runner.sh /app/fuzzer_runner/runner.sh
|
||||||
|
ENV PATH=/app/fuzzer/.venv/bin:$PATH
|
||||||
|
|
||||||
|
ENTRYPOINT ["/container-entrypoint.sh"]
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
ARG BASE_IMAGE=gcr.io/oss-fuzz-base/base-runner
|
|
||||||
|
|
||||||
FROM $BASE_IMAGE AS base-image
|
|
||||||
|
|
||||||
COPY --from=ghcr.io/astral-sh/uv:0.5.20 /uv /uvx /bin/
|
|
||||||
|
|
||||||
ENV UV_LINK_MODE=copy
|
|
||||||
ENV UV_COMPILE_BYTECODE=1
|
|
||||||
ENV UV_PYTHON_DOWNLOADS=manual
|
|
||||||
|
|
||||||
RUN uv python install python3.12
|
|
||||||
|
|
||||||
FROM base-image AS runner-base
|
|
||||||
RUN apt-get update
|
|
||||||
# TODO(Ian): maybe we should have a different base image for the builder
|
|
||||||
RUN apt-get install -y ca-certificates curl
|
|
||||||
RUN install -m 0755 -d /etc/apt/keyrings
|
|
||||||
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
|
||||||
RUN chmod a+r /etc/apt/keyrings/docker.asc
|
|
||||||
|
|
||||||
# Add the repository to Apt sources:
|
|
||||||
RUN echo \
|
|
||||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
|
||||||
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
|
||||||
tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
|
||||||
|
|
||||||
FROM base-image AS builder
|
|
||||||
|
|
||||||
WORKDIR /fuzzer
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
||||||
--mount=type=bind,source=common/uv.lock,target=/common/uv.lock \
|
|
||||||
--mount=type=bind,source=common/pyproject.toml,target=/common/pyproject.toml \
|
|
||||||
--mount=type=bind,source=common/README.md,target=/common/README.md \
|
|
||||||
--mount=type=bind,source=fuzzer/uv.lock,target=/fuzzer/uv.lock \
|
|
||||||
--mount=type=bind,source=fuzzer/pyproject.toml,target=/fuzzer/pyproject.toml \
|
|
||||||
cd /fuzzer && uv sync --frozen --no-install-project --no-editable
|
|
||||||
|
|
||||||
ADD ./common /common
|
|
||||||
ADD ./fuzzer /fuzzer
|
|
||||||
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
||||||
uv sync --frozen --no-editable
|
|
||||||
|
|
||||||
FROM runner-base AS runtime
|
|
||||||
|
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
|
||||||
apt-get install -y git \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
COPY --from=builder --chown=app:app /fuzzer/.venv /fuzzer/.venv
|
|
||||||
COPY common/container-entrypoint.sh /container-entrypoint.sh
|
|
||||||
ENV PATH=/fuzzer/.venv/bin:$PATH
|
|
||||||
|
|
||||||
ENTRYPOINT ["/container-entrypoint.sh"]
|
|
||||||
@@ -6,10 +6,8 @@ authors = [{ name = "Trail of Bits", email = "opensource@trailofbits.com" }]
|
|||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
requires-python = ">=3.12,<3.13"
|
requires-python = ">=3.12,<3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"protobuf ==3.20.3", # Pin exactly for stability
|
"common[full]",
|
||||||
"redis ~=5.2.1",
|
"redis ~=5.2.1",
|
||||||
"clusterfuzz ==2.6.0", # Pin exactly for stability
|
|
||||||
"common",
|
|
||||||
"pydantic-settings ~=2.7.1",
|
"pydantic-settings ~=2.7.1",
|
||||||
"beautifulsoup4 ~=4.13.3",
|
"beautifulsoup4 ~=4.13.3",
|
||||||
"lxml ~=5.3.1",
|
"lxml ~=5.3.1",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import random
|
|||||||
import shutil
|
import shutil
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from os import PathLike
|
from os import PathLike
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from opentelemetry import trace
|
from opentelemetry import trace
|
||||||
from opentelemetry.trace import Status, StatusCode
|
from opentelemetry.trace import Status, StatusCode
|
||||||
@@ -21,7 +22,7 @@ from buttercup.common.maps import BuildMap, HarnessWeights
|
|||||||
from buttercup.common.sets import MERGING_LOCK_TIMEOUT_SECONDS, FailedToAcquireLock, MergedCorpusSetLock
|
from buttercup.common.sets import MERGING_LOCK_TIMEOUT_SECONDS, FailedToAcquireLock, MergedCorpusSetLock
|
||||||
from buttercup.common.telemetry import CRSActionCategory, init_telemetry, set_crs_attributes
|
from buttercup.common.telemetry import CRSActionCategory, init_telemetry, set_crs_attributes
|
||||||
from buttercup.common.utils import serve_loop, setup_periodic_zombie_reaper
|
from buttercup.common.utils import serve_loop, setup_periodic_zombie_reaper
|
||||||
from buttercup.fuzzing_infra.runner import Conf, FuzzConfiguration, Runner
|
from buttercup.fuzzing_infra.runner_proxy import Conf, FuzzConfiguration, RunnerProxy
|
||||||
from buttercup.fuzzing_infra.settings import FuzzerBotSettings
|
from buttercup.fuzzing_infra.settings import FuzzerBotSettings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -184,10 +185,11 @@ class MergerBot:
|
|||||||
timeout_seconds: int,
|
timeout_seconds: int,
|
||||||
python: str,
|
python: str,
|
||||||
crs_scratch_dir: str,
|
crs_scratch_dir: str,
|
||||||
|
runner_path: PathLike[str],
|
||||||
max_local_files: int = 500,
|
max_local_files: int = 500,
|
||||||
):
|
):
|
||||||
self.redis = redis
|
self.redis = redis
|
||||||
self.runner = Runner(Conf(timeout_seconds))
|
self.runner = RunnerProxy(Conf(timeout_seconds, Path(runner_path)))
|
||||||
self.python = python
|
self.python = python
|
||||||
self.crs_scratch_dir = crs_scratch_dir
|
self.crs_scratch_dir = crs_scratch_dir
|
||||||
self.harness_weights = HarnessWeights(redis)
|
self.harness_weights = HarnessWeights(redis)
|
||||||
@@ -401,6 +403,7 @@ def main() -> None:
|
|||||||
args.timeout,
|
args.timeout,
|
||||||
args.python,
|
args.python,
|
||||||
args.crs_scratch_dir,
|
args.crs_scratch_dir,
|
||||||
|
args.runner_path,
|
||||||
args.max_local_files,
|
args.max_local_files,
|
||||||
)
|
)
|
||||||
merger.run()
|
merger.run()
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from clusterfuzz.fuzz import engine
|
|
||||||
from opentelemetry import trace
|
from opentelemetry import trace
|
||||||
from opentelemetry.trace.status import Status, StatusCode
|
from opentelemetry.trace.status import Status, StatusCode
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
@@ -18,10 +18,14 @@ from buttercup.common.node_local import scratch_dir
|
|||||||
from buttercup.common.queues import QueueFactory, QueueNames
|
from buttercup.common.queues import QueueFactory, QueueNames
|
||||||
from buttercup.common.stack_parsing import CrashSet
|
from buttercup.common.stack_parsing import CrashSet
|
||||||
from buttercup.common.telemetry import CRSActionCategory, init_telemetry, set_crs_attributes
|
from buttercup.common.telemetry import CRSActionCategory, init_telemetry, set_crs_attributes
|
||||||
|
from buttercup.common.types import FuzzConfiguration
|
||||||
from buttercup.common.utils import setup_periodic_zombie_reaper
|
from buttercup.common.utils import setup_periodic_zombie_reaper
|
||||||
from buttercup.fuzzing_infra.runner import Conf, FuzzConfiguration, Runner
|
from buttercup.fuzzing_infra.runner_proxy import Conf, RunnerProxy
|
||||||
from buttercup.fuzzing_infra.settings import FuzzerBotSettings
|
from buttercup.fuzzing_infra.settings import FuzzerBotSettings
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from clusterfuzz.fuzz import engine
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -35,8 +39,9 @@ class FuzzerBot(TaskLoop):
|
|||||||
crs_scratch_dir: str,
|
crs_scratch_dir: str,
|
||||||
crash_dir_count_limit: int | None,
|
crash_dir_count_limit: int | None,
|
||||||
max_pov_size: int,
|
max_pov_size: int,
|
||||||
|
runner_path: Path,
|
||||||
):
|
):
|
||||||
self.runner = Runner(Conf(timeout_seconds))
|
self.runner = RunnerProxy(Conf(timeout_seconds, runner_path))
|
||||||
self.output_q = QueueFactory(redis).create(QueueNames.CRASH)
|
self.output_q = QueueFactory(redis).create(QueueNames.CRASH)
|
||||||
self.python = python
|
self.python = python
|
||||||
self.crs_scratch_dir = crs_scratch_dir
|
self.crs_scratch_dir = crs_scratch_dir
|
||||||
@@ -141,6 +146,7 @@ def main() -> None:
|
|||||||
setup_periodic_zombie_reaper()
|
setup_periodic_zombie_reaper()
|
||||||
|
|
||||||
logger.info(f"Starting fuzzer (crs_scratch_dir: {args.crs_scratch_dir})")
|
logger.info(f"Starting fuzzer (crs_scratch_dir: {args.crs_scratch_dir})")
|
||||||
|
logger.debug("Args: %s", args)
|
||||||
|
|
||||||
seconds_sleep = args.timer // 1000
|
seconds_sleep = args.timer // 1000
|
||||||
fuzzer = FuzzerBot(
|
fuzzer = FuzzerBot(
|
||||||
@@ -151,6 +157,7 @@ def main() -> None:
|
|||||||
args.crs_scratch_dir,
|
args.crs_scratch_dir,
|
||||||
crash_dir_count_limit=(args.crash_dir_count_limit if args.crash_dir_count_limit > 0 else None),
|
crash_dir_count_limit=(args.crash_dir_count_limit if args.crash_dir_count_limit > 0 else None),
|
||||||
max_pov_size=args.max_pov_size,
|
max_pov_size=args.max_pov_size,
|
||||||
|
runner_path=args.runner_path,
|
||||||
)
|
)
|
||||||
fuzzer.run()
|
fuzzer.run()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,215 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from buttercup.common.types import FuzzConfiguration
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Crash:
|
||||||
|
input_path: str
|
||||||
|
stacktrace: str
|
||||||
|
reproduce_args: list[str]
|
||||||
|
crash_time: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FuzzResult:
|
||||||
|
"""Result from a fuzzing operation"""
|
||||||
|
|
||||||
|
logs: str
|
||||||
|
command: str
|
||||||
|
crashes: list[Crash]
|
||||||
|
stats: dict
|
||||||
|
time_executed: float
|
||||||
|
timed_out: bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Conf:
|
||||||
|
# in seconds
|
||||||
|
timeout: int
|
||||||
|
runner_path: Path
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RunnerProxy:
|
||||||
|
conf: Conf
|
||||||
|
_timeout: int = field(init=False, default=5)
|
||||||
|
|
||||||
|
def _kill_process(self, process: subprocess.Popen) -> None:
|
||||||
|
try:
|
||||||
|
process.kill()
|
||||||
|
try:
|
||||||
|
process.wait(timeout=self._timeout)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
logger.error("Process did not terminate after kill within 5 seconds")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error killing process: {e}")
|
||||||
|
|
||||||
|
def _kill_process_group(self, process: subprocess.Popen) -> None:
|
||||||
|
# Kill the entire process group
|
||||||
|
try:
|
||||||
|
if os.name != "nt": # Unix-like systems
|
||||||
|
# Kill the process group
|
||||||
|
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
|
||||||
|
# Wait a bit for graceful termination
|
||||||
|
try:
|
||||||
|
process.wait(timeout=self._timeout)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
# Force kill if graceful termination failed
|
||||||
|
os.killpg(os.getpgid(process.pid), signal.SIGKILL)
|
||||||
|
try:
|
||||||
|
process.wait(timeout=self._timeout)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
logger.error("Process did not terminate after kill within 5 seconds")
|
||||||
|
else: # Windows
|
||||||
|
self._kill_process(process)
|
||||||
|
except (ProcessLookupError, OSError) as e:
|
||||||
|
logger.warning(f"Error killing process group: {e}")
|
||||||
|
self._kill_process(process)
|
||||||
|
|
||||||
|
def _run_subprocess_task(self, cmd: list[str], timeout: int, task_type: str) -> dict[str, Any]:
|
||||||
|
"""Run subprocess task and return result"""
|
||||||
|
# Enforce a timeout of runner_timeout + 5 minutes (in seconds)
|
||||||
|
subprocess_timeout = timeout + 300
|
||||||
|
|
||||||
|
try:
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
start_new_session=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
stdout, stderr = process.communicate(timeout=subprocess_timeout)
|
||||||
|
if process.returncode != 0:
|
||||||
|
error_msg = stderr.decode("utf-8") if stderr else "Unknown subprocess error"
|
||||||
|
logger.error(f"{task_type} task failed: {error_msg}")
|
||||||
|
return {
|
||||||
|
"status": "failed",
|
||||||
|
"error": f"Task failed: {error_msg}",
|
||||||
|
}
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
logger.error(f"{task_type} task timed out after {subprocess_timeout} seconds")
|
||||||
|
if process.poll() is None:
|
||||||
|
self._kill_process_group(process)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "failed",
|
||||||
|
"error": f"Task timed out after {subprocess_timeout} seconds",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse the JSON output from the subprocess
|
||||||
|
try:
|
||||||
|
output_str = stdout.decode("utf-8").strip()
|
||||||
|
logger.debug(f"Subprocess output: {output_str}")
|
||||||
|
|
||||||
|
return json.loads(output_str) # type: ignore[no-any-return]
|
||||||
|
except json.JSONDecodeError as parse_error:
|
||||||
|
logger.error(f"Failed to parse JSON output for task {task_type}: {parse_error}")
|
||||||
|
logger.error(f"Raw output: {output_str}")
|
||||||
|
return {
|
||||||
|
"status": "failed",
|
||||||
|
"error": f"Failed to parse JSON output: {parse_error}",
|
||||||
|
}
|
||||||
|
except Exception as parse_error:
|
||||||
|
logger.error(f"Failed to parse subprocess output for task {task_type}: {parse_error}")
|
||||||
|
return {
|
||||||
|
"status": "failed",
|
||||||
|
"error": f"Failed to parse output: {parse_error}",
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to start subprocess for task {task_type}: {e}")
|
||||||
|
return {
|
||||||
|
"status": "failed",
|
||||||
|
"error": f"Failed to start subprocess: {e}",
|
||||||
|
}
|
||||||
|
|
||||||
|
def run_fuzzer(self, conf: FuzzConfiguration) -> FuzzResult:
|
||||||
|
"""Run fuzzer via HTTP server and wait for completion"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"Starting fuzzer task {conf.engine} | {conf.sanitizer} | {conf.target_path}")
|
||||||
|
runner_timeout = self.conf.timeout if self.conf.timeout else 1000
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
str(self.conf.runner_path),
|
||||||
|
"--timeout",
|
||||||
|
str(runner_timeout),
|
||||||
|
"--corpusdir",
|
||||||
|
str(conf.corpus_dir),
|
||||||
|
"--engine",
|
||||||
|
conf.engine,
|
||||||
|
"--sanitizer",
|
||||||
|
conf.sanitizer,
|
||||||
|
str(conf.target_path),
|
||||||
|
"fuzz",
|
||||||
|
]
|
||||||
|
|
||||||
|
result = self._run_subprocess_task(cmd, runner_timeout, "fuzz")
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Fuzzer task {conf.engine} | {conf.sanitizer} | {conf.target_path} failed: {str(e)}")
|
||||||
|
result = {
|
||||||
|
"status": "failed",
|
||||||
|
"error": str(e),
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._dict_to_fuzz_result(result)
|
||||||
|
|
||||||
|
def merge_corpus(self, conf: FuzzConfiguration, output_dir: str) -> None:
|
||||||
|
"""Merge corpus via HTTP server and wait for completion"""
|
||||||
|
try:
|
||||||
|
logger.info(f"Starting merge corpus task {conf.engine} | {conf.sanitizer} | {conf.target_path}")
|
||||||
|
runner_timeout = self.conf.timeout if self.conf.timeout else 1000
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
str(self.conf.runner_path),
|
||||||
|
"--timeout",
|
||||||
|
str(runner_timeout),
|
||||||
|
"--corpusdir",
|
||||||
|
str(conf.corpus_dir),
|
||||||
|
"--engine",
|
||||||
|
conf.engine,
|
||||||
|
"--sanitizer",
|
||||||
|
conf.sanitizer,
|
||||||
|
str(conf.target_path),
|
||||||
|
"merge",
|
||||||
|
"--output-dir",
|
||||||
|
str(output_dir),
|
||||||
|
]
|
||||||
|
|
||||||
|
self._run_subprocess_task(cmd, runner_timeout, "merge")
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(
|
||||||
|
f"Merge corpus task {conf.engine} | {conf.sanitizer} | {conf.target_path} failed: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _dict_to_fuzz_result(self, result_dict: dict[str, Any]) -> FuzzResult:
|
||||||
|
"""Convert dictionary result back to FuzzResult object"""
|
||||||
|
# Handle both "logs" and "error" keys for backward compatibility
|
||||||
|
logs = result_dict.get("logs", result_dict.get("error", ""))
|
||||||
|
return FuzzResult(
|
||||||
|
logs=logs,
|
||||||
|
crashes=[
|
||||||
|
Crash(
|
||||||
|
input_path=crash.get("input_path", ""),
|
||||||
|
stacktrace=crash.get("stacktrace", ""),
|
||||||
|
reproduce_args=crash.get("reproduce_args", []),
|
||||||
|
crash_time=crash.get("crash_time", 0.0),
|
||||||
|
)
|
||||||
|
for crash in result_dict.get("crashes", [])
|
||||||
|
],
|
||||||
|
stats=result_dict.get("stats", {}),
|
||||||
|
time_executed=result_dict.get("time_executed", 0.0),
|
||||||
|
timed_out=result_dict.get("timed_out", False),
|
||||||
|
command=result_dict.get("command", ""),
|
||||||
|
)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from pathlib import Path
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
@@ -44,6 +45,7 @@ class FuzzerBotSettings(WorkerSettings):
|
|||||||
crash_dir_count_limit: Annotated[int, Field(default=0)]
|
crash_dir_count_limit: Annotated[int, Field(default=0)]
|
||||||
max_local_files: Annotated[int, Field(default=500)]
|
max_local_files: Annotated[int, Field(default=500)]
|
||||||
max_pov_size: Annotated[int, Field(default=2 * 1024 * 1024)] # 2 MiB
|
max_pov_size: Annotated[int, Field(default=2 * 1024 * 1024)] # 2 MiB
|
||||||
|
runner_path: Path
|
||||||
|
|
||||||
|
|
||||||
class CoverageBotSettings(WorkerSettings, BuilderSettings):
|
class CoverageBotSettings(WorkerSettings, BuilderSettings):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, Mock, patch
|
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||||
|
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
|
|
||||||
@@ -14,6 +14,8 @@ class TestMergerBot(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.redis_mock = Mock(spec=Redis)
|
self.redis_mock = Mock(spec=Redis)
|
||||||
self.runner_mock = Mock()
|
self.runner_mock = Mock()
|
||||||
|
# Make merge_corpus an async mock since it's now an async method
|
||||||
|
self.runner_mock.merge_corpus = AsyncMock()
|
||||||
self.corpus_mock = Mock()
|
self.corpus_mock = Mock()
|
||||||
self.lock_mock = MagicMock()
|
self.lock_mock = MagicMock()
|
||||||
|
|
||||||
@@ -25,13 +27,14 @@ class TestMergerBot(unittest.TestCase):
|
|||||||
self.max_local_files = 500
|
self.max_local_files = 500
|
||||||
|
|
||||||
# Create the MergerBot instance with mocked runner
|
# Create the MergerBot instance with mocked runner
|
||||||
with patch("buttercup.fuzzing_infra.corpus_merger.Runner") as runner_class_mock:
|
with patch("buttercup.fuzzing_infra.corpus_merger.RunnerProxy") as runner_class_mock:
|
||||||
runner_class_mock.return_value = self.runner_mock
|
runner_class_mock.return_value = self.runner_mock
|
||||||
self.merger_bot = MergerBot(
|
self.merger_bot = MergerBot(
|
||||||
self.redis_mock,
|
self.redis_mock,
|
||||||
self.timeout_seconds,
|
self.timeout_seconds,
|
||||||
self.python,
|
self.python,
|
||||||
self.crs_scratch_dir,
|
self.crs_scratch_dir,
|
||||||
|
"/path/to/runner",
|
||||||
self.max_local_files,
|
self.max_local_files,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from buttercup.common.types import FuzzConfiguration
|
||||||
|
from buttercup.fuzzing_infra.runner_proxy import Conf, Crash, FuzzResult, RunnerProxy
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fuzz_config():
|
||||||
|
return FuzzConfiguration(
|
||||||
|
corpus_dir="/path/to/corpus", target_path="/path/to/target", engine="libfuzzer", sanitizer="address"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch("buttercup.fuzzing_infra.runner_proxy.subprocess.Popen")
|
||||||
|
def test_run_fuzzer_success(mock_popen, fuzz_config):
|
||||||
|
"""Test successful fuzzer execution via subprocess"""
|
||||||
|
# Create runner proxy
|
||||||
|
conf = Conf(
|
||||||
|
timeout=100,
|
||||||
|
runner_path="/path/to/runner",
|
||||||
|
)
|
||||||
|
runner_proxy = RunnerProxy(conf)
|
||||||
|
|
||||||
|
# Mock subprocess result
|
||||||
|
mock_result = {
|
||||||
|
"logs": "test logs",
|
||||||
|
"crashes": [
|
||||||
|
{
|
||||||
|
"input_path": "input1",
|
||||||
|
"stacktrace": "stacktrace1",
|
||||||
|
"reproduce_args": ["arg1", "arg2"],
|
||||||
|
"crash_time": 1.0,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stats": {"execs_per_sec": 1000},
|
||||||
|
"time_executed": 10.0,
|
||||||
|
"timed_out": False,
|
||||||
|
"command": "test command",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mock the subprocess
|
||||||
|
mock_process = Mock()
|
||||||
|
mock_process.communicate.return_value = (json.dumps(mock_result).encode(), b"")
|
||||||
|
mock_process.returncode = 0
|
||||||
|
mock_process.pid = 12345
|
||||||
|
mock_popen.return_value = mock_process
|
||||||
|
|
||||||
|
# Run fuzzer
|
||||||
|
result = runner_proxy.run_fuzzer(fuzz_config)
|
||||||
|
|
||||||
|
# Verify subprocess was called with correct arguments
|
||||||
|
expected_cmd = [
|
||||||
|
"/path/to/runner",
|
||||||
|
"--timeout",
|
||||||
|
"100",
|
||||||
|
"--corpusdir",
|
||||||
|
"/path/to/corpus",
|
||||||
|
"--engine",
|
||||||
|
"libfuzzer",
|
||||||
|
"--sanitizer",
|
||||||
|
"address",
|
||||||
|
"/path/to/target",
|
||||||
|
"fuzz",
|
||||||
|
]
|
||||||
|
mock_popen.assert_called_once()
|
||||||
|
call_args = mock_popen.call_args[0][0] # First positional argument (cmd)
|
||||||
|
assert call_args == expected_cmd
|
||||||
|
|
||||||
|
# Verify result is a FuzzResult instance
|
||||||
|
assert isinstance(result, FuzzResult)
|
||||||
|
assert result.logs == "test logs"
|
||||||
|
assert result.crashes == [
|
||||||
|
Crash(
|
||||||
|
input_path="input1",
|
||||||
|
stacktrace="stacktrace1",
|
||||||
|
reproduce_args=["arg1", "arg2"],
|
||||||
|
crash_time=1.0,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
assert result.stats == {"execs_per_sec": 1000}
|
||||||
|
assert result.time_executed == 10.0
|
||||||
|
assert not result.timed_out
|
||||||
|
assert result.command == "test command"
|
||||||
|
|
||||||
|
|
||||||
|
@patch("buttercup.fuzzing_infra.runner_proxy.subprocess.Popen")
|
||||||
|
def test_run_fuzzer_failure(mock_popen, fuzz_config):
|
||||||
|
"""Test fuzzer execution failure via subprocess"""
|
||||||
|
# Create runner proxy
|
||||||
|
conf = Conf(
|
||||||
|
timeout=100,
|
||||||
|
runner_path="/path/to/runner",
|
||||||
|
)
|
||||||
|
runner_proxy = RunnerProxy(conf)
|
||||||
|
|
||||||
|
# Mock the subprocess to return non-zero exit code
|
||||||
|
mock_process = Mock()
|
||||||
|
mock_process.communicate.return_value = (b"", b"Fuzzer crashed")
|
||||||
|
mock_process.returncode = 1
|
||||||
|
mock_process.pid = 12345
|
||||||
|
mock_popen.return_value = mock_process
|
||||||
|
|
||||||
|
# Run fuzzer and expect failure
|
||||||
|
res = runner_proxy.run_fuzzer(fuzz_config)
|
||||||
|
assert "Task failed: Fuzzer crashed" in res.logs
|
||||||
|
assert res.crashes == []
|
||||||
|
assert res.command == ""
|
||||||
|
|
||||||
|
|
||||||
|
@patch("buttercup.fuzzing_infra.runner_proxy.subprocess.Popen")
|
||||||
|
def test_run_fuzzer_timeout(mock_popen, fuzz_config):
|
||||||
|
"""Test fuzzer execution timeout"""
|
||||||
|
# Create runner proxy with very short timeout for testing
|
||||||
|
conf = Conf(
|
||||||
|
timeout=1, # 1 second timeout
|
||||||
|
runner_path="/path/to/runner",
|
||||||
|
)
|
||||||
|
runner_proxy = RunnerProxy(conf)
|
||||||
|
|
||||||
|
# Mock the subprocess to timeout
|
||||||
|
mock_process = Mock()
|
||||||
|
mock_process.communicate.side_effect = subprocess.TimeoutExpired("test", 1)
|
||||||
|
mock_process.returncode = None
|
||||||
|
mock_process.poll.return_value = None
|
||||||
|
mock_process.pid = 12345
|
||||||
|
mock_popen.return_value = mock_process
|
||||||
|
|
||||||
|
# Run fuzzer and expect timeout
|
||||||
|
start_time = time.time()
|
||||||
|
res = runner_proxy.run_fuzzer(fuzz_config)
|
||||||
|
|
||||||
|
assert "Task timed out" in res.logs
|
||||||
|
assert res.crashes == []
|
||||||
|
assert res.command == ""
|
||||||
|
|
||||||
|
# Verify it didn't take too long (should timeout quickly in test environment)
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
assert elapsed < 3.0 # Should timeout within 3 seconds
|
||||||
|
|
||||||
|
|
||||||
|
@patch("buttercup.fuzzing_infra.runner_proxy.subprocess.Popen")
|
||||||
|
def test_merge_corpus_success(mock_popen, fuzz_config):
|
||||||
|
"""Test successful corpus merge via subprocess"""
|
||||||
|
# Create runner proxy
|
||||||
|
conf = Conf(
|
||||||
|
timeout=100,
|
||||||
|
runner_path="/path/to/runner",
|
||||||
|
)
|
||||||
|
runner_proxy = RunnerProxy(conf)
|
||||||
|
|
||||||
|
# Mock the subprocess
|
||||||
|
mock_process = Mock()
|
||||||
|
mock_process.communicate.return_value = (b"", b"")
|
||||||
|
mock_process.returncode = 0
|
||||||
|
mock_process.pid = 12345
|
||||||
|
mock_popen.return_value = mock_process
|
||||||
|
|
||||||
|
# Run merge corpus
|
||||||
|
runner_proxy.merge_corpus(fuzz_config, "/path/to/output")
|
||||||
|
|
||||||
|
# Verify subprocess was called with correct arguments
|
||||||
|
expected_cmd = [
|
||||||
|
"/path/to/runner",
|
||||||
|
"--timeout",
|
||||||
|
"100",
|
||||||
|
"--corpusdir",
|
||||||
|
"/path/to/corpus",
|
||||||
|
"--engine",
|
||||||
|
"libfuzzer",
|
||||||
|
"--sanitizer",
|
||||||
|
"address",
|
||||||
|
"/path/to/target",
|
||||||
|
"merge",
|
||||||
|
"--output-dir",
|
||||||
|
"/path/to/output",
|
||||||
|
]
|
||||||
|
mock_popen.assert_called_once()
|
||||||
|
call_args = mock_popen.call_args[0][0] # First positional argument (cmd)
|
||||||
|
assert call_args == expected_cmd
|
||||||
|
|
||||||
|
|
||||||
|
@patch("buttercup.fuzzing_infra.runner_proxy.subprocess.Popen")
|
||||||
|
def test_subprocess_error_handling(mock_popen, fuzz_config):
|
||||||
|
"""Test subprocess error handling"""
|
||||||
|
# Create runner proxy
|
||||||
|
conf = Conf(
|
||||||
|
timeout=100,
|
||||||
|
runner_path="/path/to/runner",
|
||||||
|
)
|
||||||
|
runner_proxy = RunnerProxy(conf)
|
||||||
|
|
||||||
|
# Mock subprocess to raise an exception
|
||||||
|
mock_popen.side_effect = FileNotFoundError("Runner not found")
|
||||||
|
|
||||||
|
# Run fuzzer and expect error
|
||||||
|
res = runner_proxy.run_fuzzer(fuzz_config)
|
||||||
|
assert "Runner not found" in res.logs
|
||||||
|
assert res.crashes == []
|
||||||
|
assert res.command == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_runner_proxy_initialization():
|
||||||
|
"""Test that RunnerProxy initializes correctly"""
|
||||||
|
conf = Conf(timeout=100, runner_path="/path/to/runner")
|
||||||
|
proxy = RunnerProxy(conf)
|
||||||
|
|
||||||
|
# Verify configuration is set correctly
|
||||||
|
assert proxy.conf.timeout == 100
|
||||||
|
assert proxy.conf.runner_path == "/path/to/runner"
|
||||||
|
assert proxy._timeout == 5 # Default internal timeout
|
||||||
|
|
||||||
|
|
||||||
|
def test_fuzz_result_creation():
|
||||||
|
"""Test FuzzResult dataclass creation"""
|
||||||
|
result = FuzzResult(
|
||||||
|
logs="test logs",
|
||||||
|
command="fuzzer command",
|
||||||
|
crashes=[
|
||||||
|
Crash(
|
||||||
|
input_path="input1",
|
||||||
|
stacktrace="stacktrace1",
|
||||||
|
reproduce_args=["arg1", "arg2"],
|
||||||
|
crash_time=1.0,
|
||||||
|
),
|
||||||
|
Crash(
|
||||||
|
input_path="input2",
|
||||||
|
stacktrace="stacktrace2",
|
||||||
|
reproduce_args=["arg3", "arg4"],
|
||||||
|
crash_time=2.0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
stats={"execs_per_sec": 1000},
|
||||||
|
time_executed=5.5,
|
||||||
|
timed_out=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.logs == "test logs"
|
||||||
|
assert result.crashes == [
|
||||||
|
Crash(
|
||||||
|
input_path="input1",
|
||||||
|
stacktrace="stacktrace1",
|
||||||
|
reproduce_args=["arg1", "arg2"],
|
||||||
|
crash_time=1.0,
|
||||||
|
),
|
||||||
|
Crash(
|
||||||
|
input_path="input2",
|
||||||
|
stacktrace="stacktrace2",
|
||||||
|
reproduce_args=["arg3", "arg4"],
|
||||||
|
crash_time=2.0,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
assert result.stats == {"execs_per_sec": 1000}
|
||||||
|
assert result.time_executed == 5.5
|
||||||
|
assert not result.timed_out
|
||||||
|
assert result.command == "fuzzer command"
|
||||||
|
|
||||||
|
|
||||||
|
def test_conf_defaults():
|
||||||
|
"""Test Conf dataclass default values"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
conf = Conf(timeout=60, runner_path=Path("/path/to/runner"))
|
||||||
|
|
||||||
|
assert conf.timeout == 60
|
||||||
|
assert conf.runner_path == Path("/path/to/runner")
|
||||||
Generated
+503
-850
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
|||||||
|
fuzzing_infra/datastructures/fuzzer_msg*
|
||||||
|
*.pyc
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Fuzzer Runner
|
||||||
|
|
||||||
|
A libfuzzer-based fuzzer runner that can be used as a command-line tool.
|
||||||
|
It is useful to separate the clusterfuzz dependency (and its older indirect dependencies like protobuf==3.20) from the rest of the system.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Run fuzzers with various engines and sanitizers
|
||||||
|
- Merge corpus files
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
[project]
|
||||||
|
name = "fuzzing-runner"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "libfuzzer-based fuzzer runner"
|
||||||
|
authors = [{ name = "Trail of Bits", email = "opensource@trailofbits.com" }]
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
requires-python = ">=3.12,<3.13"
|
||||||
|
dependencies = [
|
||||||
|
"common",
|
||||||
|
"clusterfuzz ==2.6.0",
|
||||||
|
"pydantic ~=2.11.7",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Repository = "https://github.com/trailofbits/buttercup"
|
||||||
|
Issues = "https://github.com/trailofbits/buttercup/issues"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
buttercup-fuzzer-runner = "buttercup.fuzzer_runner.runner:main"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["src/buttercup"]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
common = { path = "../common", editable = true }
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
# Testing tools
|
||||||
|
"pytest ~=8.3.4",
|
||||||
|
"pytest-cov ~=6.0.0",
|
||||||
|
"httpx ~=0.27.0", # For FastAPI testing
|
||||||
|
# Linting and type checking
|
||||||
|
"ruff ~=0.12.8",
|
||||||
|
"mypy ~=1.17.1",
|
||||||
|
# Type stubs
|
||||||
|
"types-redis ~=4.6.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 120
|
||||||
|
target-version = "py312"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "I", "W", "UP"]
|
||||||
|
ignore = [
|
||||||
|
"COM812", "ISC001", # Formatter conflicts
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"tests/**/*.py" = ["S101", "D"] # Allow asserts, no docstrings in tests
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
plugins = ["pydantic.mypy"]
|
||||||
|
mypy_path = "src"
|
||||||
|
packages = "buttercup"
|
||||||
|
allow_redefinition = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
ignore_missing_imports = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
show_error_codes = true
|
||||||
|
sqlite_cache = true
|
||||||
|
strict_equality = true
|
||||||
|
warn_no_return = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unreachable = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
exclude = []
|
||||||
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(dirname "$0")
|
||||||
|
|
||||||
|
if [ -d "$SCRIPT_DIR/.venv" ]; then
|
||||||
|
. "$SCRIPT_DIR/.venv/bin/activate"
|
||||||
|
else
|
||||||
|
echo "Virtual environment not found in $SCRIPT_DIR/.venv"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
buttercup-fuzzer-runner $@ # we use $@ to pass all the arguments to the python script
|
||||||
+60
-4
@@ -1,4 +1,5 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import typing
|
import typing
|
||||||
@@ -10,8 +11,8 @@ from clusterfuzz.fuzz.engine import Engine, FuzzOptions, FuzzResult
|
|||||||
|
|
||||||
from buttercup.common.logger import setup_package_logger
|
from buttercup.common.logger import setup_package_logger
|
||||||
from buttercup.common.node_local import scratch_dir
|
from buttercup.common.node_local import scratch_dir
|
||||||
from buttercup.common.queues import FuzzConfiguration
|
from buttercup.common.types import FuzzConfiguration
|
||||||
from buttercup.fuzzing_infra.temp_dir import patched_temp_dir, scratch_cwd
|
from buttercup.fuzzer_runner.temp_dir import patched_temp_dir, scratch_cwd
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -67,21 +68,76 @@ class Runner:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_fuzzer_command(args: argparse.Namespace, runner: Runner, fuzzconf: FuzzConfiguration) -> dict[str, typing.Any]:
|
||||||
|
"""Run fuzzer command"""
|
||||||
|
result = runner.run_fuzzer(fuzzconf)
|
||||||
|
result_dict = {
|
||||||
|
"logs": result.logs,
|
||||||
|
"command": result.command,
|
||||||
|
"crashes": [
|
||||||
|
{
|
||||||
|
"input_path": crash.input_path,
|
||||||
|
"stacktrace": crash.stacktrace,
|
||||||
|
"reproduce_args": crash.reproduce_args,
|
||||||
|
"crash_time": crash.crash_time,
|
||||||
|
}
|
||||||
|
for crash in result.crashes
|
||||||
|
],
|
||||||
|
"stats": result.stats,
|
||||||
|
"time_executed": result.time_executed,
|
||||||
|
"timed_out": result.timed_out,
|
||||||
|
}
|
||||||
|
return result_dict
|
||||||
|
|
||||||
|
|
||||||
|
def merge_corpus_command(
|
||||||
|
args: argparse.Namespace, runner: Runner, fuzzconf: FuzzConfiguration
|
||||||
|
) -> dict[str, typing.Any]:
|
||||||
|
"""Run merge corpus command"""
|
||||||
|
runner.merge_corpus(fuzzconf, args.output_dir)
|
||||||
|
|
||||||
|
result_dict = {
|
||||||
|
"status": "completed",
|
||||||
|
"output_dir": args.output_dir,
|
||||||
|
"message": "Corpus merge completed successfully",
|
||||||
|
}
|
||||||
|
return result_dict
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
prsr = argparse.ArgumentParser("Fuzzer runner")
|
prsr = argparse.ArgumentParser("Fuzzer runner")
|
||||||
|
|
||||||
prsr.add_argument("--timeout", required=True, type=int)
|
prsr.add_argument("--timeout", required=True, type=int)
|
||||||
prsr.add_argument("--corpusdir", required=True)
|
prsr.add_argument("--corpusdir", required=True)
|
||||||
prsr.add_argument("--engine", required=True)
|
prsr.add_argument("--engine", required=True)
|
||||||
prsr.add_argument("--sanitizer", required=True)
|
prsr.add_argument("--sanitizer", required=True)
|
||||||
prsr.add_argument("target")
|
prsr.add_argument("target")
|
||||||
args = prsr.parse_args()
|
|
||||||
|
subparsers = prsr.add_subparsers(dest="command", help="Available commands")
|
||||||
|
|
||||||
|
# Fuzzer command
|
||||||
|
fuzzer_parser = subparsers.add_parser("fuzz", help="Run fuzzer")
|
||||||
|
fuzzer_parser.set_defaults(func=run_fuzzer_command)
|
||||||
|
|
||||||
|
# Merge corpus command
|
||||||
|
merge_parser = subparsers.add_parser("merge", help="Merge corpus")
|
||||||
|
merge_parser.add_argument("--output-dir", required=True)
|
||||||
|
merge_parser.set_defaults(func=merge_corpus_command)
|
||||||
|
|
||||||
setup_package_logger("fuzzer-runner", __name__, "DEBUG", None)
|
setup_package_logger("fuzzer-runner", __name__, "DEBUG", None)
|
||||||
|
|
||||||
|
args = prsr.parse_args()
|
||||||
|
|
||||||
|
if not hasattr(args, "func"):
|
||||||
|
prsr.print_help()
|
||||||
|
return
|
||||||
|
|
||||||
conf = Conf(args.timeout)
|
conf = Conf(args.timeout)
|
||||||
fuzzconf = FuzzConfiguration(args.corpusdir, args.target, args.engine, args.sanitizer)
|
fuzzconf = FuzzConfiguration(args.corpusdir, args.target, args.engine, args.sanitizer)
|
||||||
runner = Runner(conf)
|
runner = Runner(conf)
|
||||||
print(runner.run_fuzzer(fuzzconf))
|
|
||||||
|
result_dict = args.func(args, runner, fuzzconf)
|
||||||
|
print(json.dumps(result_dict))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -5,7 +5,7 @@ import time
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from buttercup.fuzzing_infra.temp_dir import _scratch_path_var, get_temp_dir, patched_temp_dir
|
from buttercup.fuzzer_runner.temp_dir import _scratch_path_var, get_temp_dir, patched_temp_dir
|
||||||
|
|
||||||
|
|
||||||
class TestGetTempDir(unittest.TestCase):
|
class TestGetTempDir(unittest.TestCase):
|
||||||
@@ -20,7 +20,7 @@ class TestGetTempDir(unittest.TestCase):
|
|||||||
"""Test get_temp_dir uses scratch path when available."""
|
"""Test get_temp_dir uses scratch path when available."""
|
||||||
test_scratch_path = "/test/scratch/path"
|
test_scratch_path = "/test/scratch/path"
|
||||||
|
|
||||||
with patch("buttercup.fuzzing_infra.temp_dir.shell.create_directory") as mock_create_dir:
|
with patch("buttercup.fuzzer_runner.temp_dir.shell.create_directory") as mock_create_dir:
|
||||||
# Set the context variable
|
# Set the context variable
|
||||||
token = _scratch_path_var.set(test_scratch_path)
|
token = _scratch_path_var.set(test_scratch_path)
|
||||||
|
|
||||||
@@ -39,8 +39,8 @@ class TestGetTempDir(unittest.TestCase):
|
|||||||
test_fuzz_inputs_path = "/test/fuzz/inputs"
|
test_fuzz_inputs_path = "/test/fuzz/inputs"
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("buttercup.fuzzing_infra.temp_dir.environment.get_value") as mock_get_value,
|
patch("buttercup.fuzzer_runner.temp_dir.environment.get_value") as mock_get_value,
|
||||||
patch("buttercup.fuzzing_infra.temp_dir.shell.create_directory") as mock_create_dir,
|
patch("buttercup.fuzzer_runner.temp_dir.shell.create_directory") as mock_create_dir,
|
||||||
):
|
):
|
||||||
mock_get_value.return_value = test_fuzz_inputs_path
|
mock_get_value.return_value = test_fuzz_inputs_path
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ class TestGetTempDir(unittest.TestCase):
|
|||||||
|
|
||||||
def test_get_temp_dir_fallback_without_fuzz_inputs_disk(self):
|
def test_get_temp_dir_fallback_without_fuzz_inputs_disk(self):
|
||||||
"""Test get_temp_dir falls back to system temp when use_fuzz_inputs_disk=False."""
|
"""Test get_temp_dir falls back to system temp when use_fuzz_inputs_disk=False."""
|
||||||
with patch("buttercup.fuzzing_infra.temp_dir.shell.create_directory") as mock_create_dir:
|
with patch("buttercup.fuzzer_runner.temp_dir.shell.create_directory") as mock_create_dir:
|
||||||
result = get_temp_dir(use_fuzz_inputs_disk=False)
|
result = get_temp_dir(use_fuzz_inputs_disk=False)
|
||||||
|
|
||||||
expected_path = os.path.join(tempfile.gettempdir(), f"temp-{os.getpid()}")
|
expected_path = os.path.join(tempfile.gettempdir(), f"temp-{os.getpid()}")
|
||||||
@@ -63,8 +63,8 @@ class TestGetTempDir(unittest.TestCase):
|
|||||||
def test_get_temp_dir_fallback_to_system_temp(self):
|
def test_get_temp_dir_fallback_to_system_temp(self):
|
||||||
"""Test get_temp_dir falls back to system temp when FUZZ_INPUTS_DISK not set."""
|
"""Test get_temp_dir falls back to system temp when FUZZ_INPUTS_DISK not set."""
|
||||||
with (
|
with (
|
||||||
patch("buttercup.fuzzing_infra.temp_dir.environment.get_value") as mock_get_value,
|
patch("buttercup.fuzzer_runner.temp_dir.environment.get_value") as mock_get_value,
|
||||||
patch("buttercup.fuzzing_infra.temp_dir.shell.create_directory") as mock_create_dir,
|
patch("buttercup.fuzzer_runner.temp_dir.shell.create_directory") as mock_create_dir,
|
||||||
):
|
):
|
||||||
# Return system temp dir when FUZZ_INPUTS_DISK is not set
|
# Return system temp dir when FUZZ_INPUTS_DISK is not set
|
||||||
mock_get_value.return_value = tempfile.gettempdir()
|
mock_get_value.return_value = tempfile.gettempdir()
|
||||||
@@ -81,7 +81,7 @@ class TestPatchedTempDir(unittest.TestCase):
|
|||||||
|
|
||||||
def test_patched_temp_dir_basic_functionality(self):
|
def test_patched_temp_dir_basic_functionality(self):
|
||||||
"""Test basic functionality of patched_temp_dir context manager."""
|
"""Test basic functionality of patched_temp_dir context manager."""
|
||||||
with patch("buttercup.fuzzing_infra.temp_dir.scratch_dir") as mock_scratch_dir:
|
with patch("buttercup.fuzzer_runner.temp_dir.scratch_dir") as mock_scratch_dir:
|
||||||
# Mock scratch_dir context manager
|
# Mock scratch_dir context manager
|
||||||
mock_scratch = MagicMock()
|
mock_scratch = MagicMock()
|
||||||
mock_scratch.path = "/mock/scratch/path"
|
mock_scratch.path = "/mock/scratch/path"
|
||||||
@@ -97,7 +97,7 @@ class TestPatchedTempDir(unittest.TestCase):
|
|||||||
|
|
||||||
def test_patched_temp_dir_patches_clusterfuzz_function(self):
|
def test_patched_temp_dir_patches_clusterfuzz_function(self):
|
||||||
"""Test that patched_temp_dir properly patches the clusterfuzz function."""
|
"""Test that patched_temp_dir properly patches the clusterfuzz function."""
|
||||||
with patch("buttercup.fuzzing_infra.temp_dir.scratch_dir") as mock_scratch_dir:
|
with patch("buttercup.fuzzer_runner.temp_dir.scratch_dir") as mock_scratch_dir:
|
||||||
# Mock scratch_dir context manager
|
# Mock scratch_dir context manager
|
||||||
mock_scratch = MagicMock()
|
mock_scratch = MagicMock()
|
||||||
mock_scratch.path = "/mock/scratch/path"
|
mock_scratch.path = "/mock/scratch/path"
|
||||||
@@ -106,7 +106,7 @@ class TestPatchedTempDir(unittest.TestCase):
|
|||||||
|
|
||||||
# Test that the patching works by calling the function directly
|
# Test that the patching works by calling the function directly
|
||||||
with patched_temp_dir():
|
with patched_temp_dir():
|
||||||
with patch("buttercup.fuzzing_infra.temp_dir.shell.create_directory") as mock_create_dir:
|
with patch("buttercup.fuzzer_runner.temp_dir.shell.create_directory") as mock_create_dir:
|
||||||
# Import and call the clusterfuzz function - it should be patched to use our implementation
|
# Import and call the clusterfuzz function - it should be patched to use our implementation
|
||||||
from clusterfuzz._internal.bot.fuzzers import utils
|
from clusterfuzz._internal.bot.fuzzers import utils
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ class TestPatchedTempDir(unittest.TestCase):
|
|||||||
|
|
||||||
def test_patched_temp_dir_context_variable_cleanup(self):
|
def test_patched_temp_dir_context_variable_cleanup(self):
|
||||||
"""Test that context variable is properly cleaned up after context exit."""
|
"""Test that context variable is properly cleaned up after context exit."""
|
||||||
with patch("buttercup.fuzzing_infra.temp_dir.scratch_dir") as mock_scratch_dir:
|
with patch("buttercup.fuzzer_runner.temp_dir.scratch_dir") as mock_scratch_dir:
|
||||||
# Mock scratch_dir context manager
|
# Mock scratch_dir context manager
|
||||||
mock_scratch = MagicMock()
|
mock_scratch = MagicMock()
|
||||||
mock_scratch.path = "/mock/scratch/path"
|
mock_scratch.path = "/mock/scratch/path"
|
||||||
@@ -138,7 +138,7 @@ class TestPatchedTempDir(unittest.TestCase):
|
|||||||
|
|
||||||
def test_patched_temp_dir_nested_contexts(self):
|
def test_patched_temp_dir_nested_contexts(self):
|
||||||
"""Test that nested patched_temp_dir contexts work correctly."""
|
"""Test that nested patched_temp_dir contexts work correctly."""
|
||||||
with patch("buttercup.fuzzing_infra.temp_dir.scratch_dir") as mock_scratch_dir:
|
with patch("buttercup.fuzzer_runner.temp_dir.scratch_dir") as mock_scratch_dir:
|
||||||
# Mock different scratch directories for nested contexts
|
# Mock different scratch directories for nested contexts
|
||||||
mock_scratch1 = MagicMock()
|
mock_scratch1 = MagicMock()
|
||||||
mock_scratch1.path = "/mock/scratch/path1"
|
mock_scratch1.path = "/mock/scratch/path1"
|
||||||
@@ -162,7 +162,7 @@ class TestPatchedTempDir(unittest.TestCase):
|
|||||||
|
|
||||||
def test_patched_temp_dir_exception_handling(self):
|
def test_patched_temp_dir_exception_handling(self):
|
||||||
"""Test that context variable is cleaned up even when exceptions occur."""
|
"""Test that context variable is cleaned up even when exceptions occur."""
|
||||||
with patch("buttercup.fuzzing_infra.temp_dir.scratch_dir") as mock_scratch_dir:
|
with patch("buttercup.fuzzer_runner.temp_dir.scratch_dir") as mock_scratch_dir:
|
||||||
mock_scratch = MagicMock()
|
mock_scratch = MagicMock()
|
||||||
mock_scratch.path = "/mock/scratch/path"
|
mock_scratch.path = "/mock/scratch/path"
|
||||||
mock_scratch_dir.return_value.__enter__.return_value = mock_scratch
|
mock_scratch_dir.return_value.__enter__.return_value = mock_scratch
|
||||||
@@ -189,7 +189,7 @@ class TestThreadSafety(unittest.TestCase):
|
|||||||
|
|
||||||
def thread_worker(thread_id: int):
|
def thread_worker(thread_id: int):
|
||||||
try:
|
try:
|
||||||
with patch("buttercup.fuzzing_infra.temp_dir.scratch_dir") as mock_scratch_dir:
|
with patch("buttercup.fuzzer_runner.temp_dir.scratch_dir") as mock_scratch_dir:
|
||||||
mock_scratch = MagicMock()
|
mock_scratch = MagicMock()
|
||||||
mock_scratch.path = f"/mock/scratch/thread{thread_id}"
|
mock_scratch.path = f"/mock/scratch/thread{thread_id}"
|
||||||
mock_scratch_dir.return_value.__enter__.return_value = mock_scratch
|
mock_scratch_dir.return_value.__enter__.return_value = mock_scratch
|
||||||
Generated
+1488
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ license = "AGPL-3.0-only"
|
|||||||
requires-python = ">=3.12,<3.13"
|
requires-python = ">=3.12,<3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2-cffi ~=21.3.0",
|
"argon2-cffi ~=21.3.0",
|
||||||
"common",
|
"common[full]",
|
||||||
"fastapi ~=0.115.6",
|
"fastapi ~=0.115.6",
|
||||||
"pydantic ~=2.10.5",
|
"pydantic ~=2.10.5",
|
||||||
"pydantic-settings ~=2.7.1",
|
"pydantic-settings ~=2.7.1",
|
||||||
|
|||||||
Generated
+464
-407
File diff suppressed because it is too large
Load Diff
@@ -7,12 +7,11 @@ authors = [{ name = "Trail of Bits", email = "opensource@trailofbits.com" }]
|
|||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
requires-python = ">=3.12,<3.13"
|
requires-python = ">=3.12,<3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"common",
|
"common[full]",
|
||||||
"langchain ~=0.3.20",
|
"langchain-community ~=0.3.27",
|
||||||
"langchain-community ~=0.3.16",
|
"langgraph ~=0.6.6",
|
||||||
"langgraph ~=0.5.2",
|
"langgraph-checkpoint ~=2.1.1",
|
||||||
"langgraph-checkpoint ~=2.1.0",
|
"openai ~=1.100.2",
|
||||||
"openai ~=1.60.2",
|
|
||||||
"program-model",
|
"program-model",
|
||||||
"pydantic-settings ~=2.7.1",
|
"pydantic-settings ~=2.7.1",
|
||||||
"python-dotenv ~=1.0.1",
|
"python-dotenv ~=1.0.1",
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class PatcherLeaderAgent(PatcherAgentBase):
|
|||||||
tasks_storage: Path
|
tasks_storage: Path
|
||||||
model_name: str | None = None
|
model_name: str | None = None
|
||||||
|
|
||||||
def _init_patch_team(self) -> StateGraph[PatcherAgentState, PatcherAgentState, PatcherAgentState]:
|
def _init_patch_team(self) -> StateGraph[PatcherAgentState, PatcherConfig, PatcherAgentState, PatcherAgentState]:
|
||||||
rootcause_agent = RootCauseAgent(self.challenge, self.input, chain_call=self.chain_call)
|
rootcause_agent = RootCauseAgent(self.challenge, self.input, chain_call=self.chain_call)
|
||||||
swe_agent = SWEAgent(self.challenge, self.input, chain_call=self.chain_call)
|
swe_agent = SWEAgent(self.challenge, self.input, chain_call=self.chain_call)
|
||||||
qe_agent = QEAgent(self.challenge, self.input, chain_call=self.chain_call)
|
qe_agent = QEAgent(self.challenge, self.input, chain_call=self.chain_call)
|
||||||
@@ -69,7 +69,7 @@ class PatcherLeaderAgent(PatcherAgentBase):
|
|||||||
workflow.add_node(PatcherAgentName.RUN_POV.value, qe_agent.run_pov_node)
|
workflow.add_node(PatcherAgentName.RUN_POV.value, qe_agent.run_pov_node)
|
||||||
workflow.add_node(PatcherAgentName.RUN_TESTS.value, qe_agent.run_tests_node)
|
workflow.add_node(PatcherAgentName.RUN_TESTS.value, qe_agent.run_tests_node)
|
||||||
workflow.add_node(PatcherAgentName.REFLECTION.value, reflection_agent.reflect_on_patch)
|
workflow.add_node(PatcherAgentName.REFLECTION.value, reflection_agent.reflect_on_patch)
|
||||||
workflow.add_node(PatcherAgentName.CONTEXT_RETRIEVER.value, context_retriever_agent.retrieve_context) # type: ignore[call-overload]
|
workflow.add_node(PatcherAgentName.CONTEXT_RETRIEVER.value, context_retriever_agent.retrieve_context)
|
||||||
workflow.add_node(PatcherAgentName.PATCH_VALIDATION.value, qe_agent.validate_patch_node)
|
workflow.add_node(PatcherAgentName.PATCH_VALIDATION.value, qe_agent.validate_patch_node)
|
||||||
|
|
||||||
workflow.set_entry_point(PatcherAgentName.INPUT_PROCESSING.value)
|
workflow.set_entry_point(PatcherAgentName.INPUT_PROCESSING.value)
|
||||||
|
|||||||
Generated
+548
-191
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,7 @@ authors = [{ name = "Trail of Bits", email = "opensource@trailofbits.com" }]
|
|||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
requires-python = ">=3.12,<3.13"
|
requires-python = ">=3.12,<3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"common",
|
"common[full]",
|
||||||
"protobuf ==3.20.3", # Pin exactly for stability
|
|
||||||
"tree-sitter ~=0.24.0",
|
"tree-sitter ~=0.24.0",
|
||||||
"tree-sitter-language-pack ~=0.9.0",
|
"tree-sitter-language-pack ~=0.9.0",
|
||||||
"rapidfuzz ~=3.12.2",
|
"rapidfuzz ~=3.12.2",
|
||||||
|
|||||||
Generated
+523
-409
File diff suppressed because it is too large
Load Diff
@@ -8,14 +8,13 @@ authors = [
|
|||||||
]
|
]
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"common[full]",
|
||||||
"redis ~=5.2.1",
|
"redis ~=5.2.1",
|
||||||
"common",
|
|
||||||
"wasmtime ~=29.0.0",
|
"wasmtime ~=29.0.0",
|
||||||
"program-model",
|
"program-model",
|
||||||
"numpy ~=2.2.3",
|
"numpy ~=2.2.3",
|
||||||
"rapidfuzz ~=3.12.2",
|
"rapidfuzz ~=3.12.2",
|
||||||
"langgraph ~=0.5.2",
|
"langgraph ~=0.6.6",
|
||||||
"langchain ~=0.3.20",
|
|
||||||
"python-dotenv ~=1.0.1",
|
"python-dotenv ~=1.0.1",
|
||||||
]
|
]
|
||||||
requires-python = ">=3.12,<3.13"
|
requires-python = ">=3.12,<3.13"
|
||||||
|
|||||||
@@ -256,13 +256,12 @@ class VulnBaseTask(Task):
|
|||||||
ctoken,
|
ctoken,
|
||||||
)
|
)
|
||||||
|
|
||||||
crash = Crash(
|
crash = Crash()
|
||||||
target=build,
|
crash.target.CopyFrom(build)
|
||||||
harness_name=self.harness_name,
|
crash.harness_name = self.harness_name
|
||||||
crash_input_path=dst,
|
crash.crash_input_path = dst
|
||||||
stacktrace=stacktrace,
|
crash.stacktrace = stacktrace
|
||||||
crash_token=ctoken,
|
crash.crash_token = ctoken
|
||||||
)
|
|
||||||
self.crash_submit.crash_queue.push(crash)
|
self.crash_submit.crash_queue.push(crash)
|
||||||
|
|
||||||
logger.debug("PoV stdout: %s", result.command_result.output)
|
logger.debug("PoV stdout: %s", result.command_result.output)
|
||||||
@@ -277,8 +276,8 @@ class VulnBaseTask(Task):
|
|||||||
workflow.add_node("tools", tool_node)
|
workflow.add_node("tools", tool_node)
|
||||||
workflow.add_node("analyze_bug", self._analyze_bug)
|
workflow.add_node("analyze_bug", self._analyze_bug)
|
||||||
workflow.add_node("write_pov", self._write_pov)
|
workflow.add_node("write_pov", self._write_pov)
|
||||||
workflow.add_node("execute_python_funcs", self._exec_python_funcs_current) # type: ignore[call-overload]
|
workflow.add_node("execute_python_funcs", self._exec_python_funcs_current)
|
||||||
workflow.add_node("test_povs", self._test_povs) # type: ignore[call-overload]
|
workflow.add_node("test_povs", self._test_povs)
|
||||||
|
|
||||||
workflow.set_entry_point("gather_context")
|
workflow.set_entry_point("gather_context")
|
||||||
workflow.add_edge("gather_context", "tools")
|
workflow.add_edge("gather_context", "tools")
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ def test_do_task_valid_pov(
|
|||||||
# Mock reproduce_multiple to return crashes on 2nd iteration
|
# Mock reproduce_multiple to return crashes on 2nd iteration
|
||||||
def mock_get_crashes(pov_path, harness_name):
|
def mock_get_crashes(pov_path, harness_name):
|
||||||
if "iter1_" in pov_path.name:
|
if "iter1_" in pov_path.name:
|
||||||
build = MagicMock(spec=BuildOutput)
|
build = BuildOutput()
|
||||||
build.sanitizer = "asan"
|
build.sanitizer = "asan"
|
||||||
|
|
||||||
result = MagicMock(spec=ReproduceResult)
|
result = MagicMock(spec=ReproduceResult)
|
||||||
|
|||||||
Generated
+535
-404
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user