mirror of
https://github.com/trailofbits/buttercup
synced 2026-06-21 14:11:39 +00:00
Program model integrate (#13)
* Add documentation * Add janusgraph custom configuration. Simply graph creation. * Add properties to edges. Index on uri property. * reformat * Create libpng graph in a more reasonable amount of time. * Update README.md * Wrap indexing into function * updating... * update proto * fix command * Update settings for serve and processing indexing * Add dockerfile to docker container * Add trigger script * Fix error with loading graphml file * reformat * Remove resource constraints for program model * Change permissions of temp dir * Update readme * Update dockerignore --------- Co-authored-by: Evan Downing <2077950+evandowning@users.noreply.github.com>
This commit is contained in:
+4
-1
@@ -16,7 +16,10 @@
|
||||
**/.pdm-build/
|
||||
**/.pdm-python
|
||||
**/pdm.lock
|
||||
**/Dockerfile
|
||||
fuzzer/dockerfiles/runner_image.Dockerfile
|
||||
orchestrator/Dockerfile
|
||||
patcher/Dockerfile
|
||||
seed-gen/Dockerfile
|
||||
**/.mypy_cache/
|
||||
**/.ruff_cache/
|
||||
|
||||
|
||||
@@ -90,3 +90,21 @@ message Patch {
|
||||
string vulnerability_id = 2;
|
||||
string patch = 3;
|
||||
}
|
||||
|
||||
message IndexRequest {
|
||||
string package_name = 1;
|
||||
string sanitizer = 2;
|
||||
string ossfuzz = 3;
|
||||
string source_path = 4;
|
||||
string task_id = 5;
|
||||
string build_type = 6;
|
||||
}
|
||||
|
||||
message IndexOutput {
|
||||
string package_name = 1;
|
||||
string sanitizer = 2;
|
||||
string ossfuzz = 3;
|
||||
string source_path = 4;
|
||||
string task_id = 5;
|
||||
string build_type = 6;
|
||||
}
|
||||
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tmsg.proto\x12\x06msgpb2\"\xec\x01\n\x04Task\x12\x12\n\nmessage_id\x18\x01 \x01(\t\x12\x14\n\x0cmessage_time\x18\x02 \x01(\x03\x12\x0f\n\x07task_id\x18\x03 \x01(\t\x12(\n\ttask_type\x18\x04 \x01(\x0e\x32\x15.msgpb2.Task.TaskType\x12%\n\x07sources\x18\x05 \x03(\x0b\x32\x14.msgpb2.SourceDetail\x12\x10\n\x08\x64\x65\x61\x64line\x18\x06 \x01(\x03\x12\x11\n\tcancelled\x18\x07 \x01(\x08\"3\n\x08TaskType\x12\x12\n\x0eTASK_TYPE_FULL\x10\x00\x12\x13\n\x0fTASK_TYPE_DELTA\x10\x01\"\xb9\x01\n\x0cSourceDetail\x12\x0e\n\x06sha256\x18\x01 \x01(\t\x12\x34\n\x0bsource_type\x18\x02 \x01(\x0e\x32\x1f.msgpb2.SourceDetail.SourceType\x12\x0b\n\x03url\x18\x03 \x01(\t\"V\n\nSourceType\x12\x14\n\x10SOURCE_TYPE_REPO\x10\x00\x12\x1c\n\x18SOURCE_TYPE_FUZZ_TOOLING\x10\x01\x12\x14\n\x10SOURCE_TYPE_DIFF\x10\x02\"*\n\x0cTaskDownload\x12\x1a\n\x04task\x18\x01 \x01(\x0b\x32\x0c.msgpb2.Task\"\'\n\tTaskReady\x12\x1a\n\x04task\x18\x01 \x01(\x0b\x32\x0c.msgpb2.Task\"2\n\nTaskDelete\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x13\n\x0breceived_at\x18\x02 \x01(\x02\"\x92\x01\n\x0c\x42uildRequest\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0e\n\x06\x65ngine\x18\x02 \x01(\t\x12\x11\n\tsanitizer\x18\x03 \x01(\t\x12\x10\n\x08task_dir\x18\x04 \x01(\t\x12\x0f\n\x07task_id\x18\x05 \x01(\t\x12\x12\n\nbuild_type\x18\x06 \x01(\t\x12\x12\n\napply_diff\x18\x07 \x01(\x08\"\x91\x01\n\x0b\x42uildOutput\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0e\n\x06\x65ngine\x18\x02 \x01(\t\x12\x11\n\tsanitizer\x18\x03 \x01(\t\x12\x10\n\x08task_dir\x18\x04 \x01(\t\x12\x0f\n\x07task_id\x18\x05 \x01(\t\x12\x12\n\nbuild_type\x18\x06 \x01(\t\x12\x12\n\napply_diff\x18\x07 \x01(\x08\"^\n\x0fWeightedHarness\x12\x0e\n\x06weight\x18\x01 \x01(\x02\x12\x14\n\x0cpackage_name\x18\x02 \x01(\t\x12\x14\n\x0charness_name\x18\x03 \x01(\t\x12\x0f\n\x07task_id\x18\x04 \x01(\t\"p\n\x05\x43rash\x12#\n\x06target\x18\x01 \x01(\x0b\x32\x13.msgpb2.BuildOutput\x12\x14\n\x0charness_name\x18\x02 \x01(\t\x12\x18\n\x10\x63rash_input_path\x18\x03 \x01(\t\x12\x12\n\nstacktrace\x18\x04 \x01(\t\"G\n\x16\x43onfirmedVulnerability\x12\x1c\n\x05\x63rash\x18\x01 \x01(\x0b\x32\r.msgpb2.Crash\x12\x0f\n\x07vuln_id\x18\x02 \x01(\t\"A\n\x05Patch\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x18\n\x10vulnerability_id\x18\x02 \x01(\t\x12\r\n\x05patch\x18\x03 \x01(\tb\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tmsg.proto\x12\x06msgpb2\"\xec\x01\n\x04Task\x12\x12\n\nmessage_id\x18\x01 \x01(\t\x12\x14\n\x0cmessage_time\x18\x02 \x01(\x03\x12\x0f\n\x07task_id\x18\x03 \x01(\t\x12(\n\ttask_type\x18\x04 \x01(\x0e\x32\x15.msgpb2.Task.TaskType\x12%\n\x07sources\x18\x05 \x03(\x0b\x32\x14.msgpb2.SourceDetail\x12\x10\n\x08\x64\x65\x61\x64line\x18\x06 \x01(\x03\x12\x11\n\tcancelled\x18\x07 \x01(\x08\"3\n\x08TaskType\x12\x12\n\x0eTASK_TYPE_FULL\x10\x00\x12\x13\n\x0fTASK_TYPE_DELTA\x10\x01\"\xb9\x01\n\x0cSourceDetail\x12\x0e\n\x06sha256\x18\x01 \x01(\t\x12\x34\n\x0bsource_type\x18\x02 \x01(\x0e\x32\x1f.msgpb2.SourceDetail.SourceType\x12\x0b\n\x03url\x18\x03 \x01(\t\"V\n\nSourceType\x12\x14\n\x10SOURCE_TYPE_REPO\x10\x00\x12\x1c\n\x18SOURCE_TYPE_FUZZ_TOOLING\x10\x01\x12\x14\n\x10SOURCE_TYPE_DIFF\x10\x02\"*\n\x0cTaskDownload\x12\x1a\n\x04task\x18\x01 \x01(\x0b\x32\x0c.msgpb2.Task\"\'\n\tTaskReady\x12\x1a\n\x04task\x18\x01 \x01(\x0b\x32\x0c.msgpb2.Task\"2\n\nTaskDelete\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x13\n\x0breceived_at\x18\x02 \x01(\x02\"\x92\x01\n\x0c\x42uildRequest\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0e\n\x06\x65ngine\x18\x02 \x01(\t\x12\x11\n\tsanitizer\x18\x03 \x01(\t\x12\x10\n\x08task_dir\x18\x04 \x01(\t\x12\x0f\n\x07task_id\x18\x05 \x01(\t\x12\x12\n\nbuild_type\x18\x06 \x01(\t\x12\x12\n\napply_diff\x18\x07 \x01(\x08\"\x91\x01\n\x0b\x42uildOutput\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0e\n\x06\x65ngine\x18\x02 \x01(\t\x12\x11\n\tsanitizer\x18\x03 \x01(\t\x12\x10\n\x08task_dir\x18\x04 \x01(\t\x12\x0f\n\x07task_id\x18\x05 \x01(\t\x12\x12\n\nbuild_type\x18\x06 \x01(\t\x12\x12\n\napply_diff\x18\x07 \x01(\x08\"^\n\x0fWeightedHarness\x12\x0e\n\x06weight\x18\x01 \x01(\x02\x12\x14\n\x0cpackage_name\x18\x02 \x01(\t\x12\x14\n\x0charness_name\x18\x03 \x01(\t\x12\x0f\n\x07task_id\x18\x04 \x01(\t\"p\n\x05\x43rash\x12#\n\x06target\x18\x01 \x01(\x0b\x32\x13.msgpb2.BuildOutput\x12\x14\n\x0charness_name\x18\x02 \x01(\t\x12\x18\n\x10\x63rash_input_path\x18\x03 \x01(\t\x12\x12\n\nstacktrace\x18\x04 \x01(\t\"G\n\x16\x43onfirmedVulnerability\x12\x1c\n\x05\x63rash\x18\x01 \x01(\x0b\x32\r.msgpb2.Crash\x12\x0f\n\x07vuln_id\x18\x02 \x01(\t\"A\n\x05Patch\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x18\n\x10vulnerability_id\x18\x02 \x01(\t\x12\r\n\x05patch\x18\x03 \x01(\t\"\x82\x01\n\x0cIndexRequest\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x11\n\tsanitizer\x18\x02 \x01(\t\x12\x0f\n\x07ossfuzz\x18\x03 \x01(\t\x12\x13\n\x0bsource_path\x18\x04 \x01(\t\x12\x0f\n\x07task_id\x18\x05 \x01(\t\x12\x12\n\nbuild_type\x18\x06 \x01(\t\"\x81\x01\n\x0bIndexOutput\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x11\n\tsanitizer\x18\x02 \x01(\t\x12\x0f\n\x07ossfuzz\x18\x03 \x01(\t\x12\x13\n\x0bsource_path\x18\x04 \x01(\t\x12\x0f\n\x07task_id\x18\x05 \x01(\t\x12\x12\n\nbuild_type\x18\x06 \x01(\tb\x06proto3')
|
||||
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'msg_pb2', globals())
|
||||
@@ -46,4 +46,8 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
_CONFIRMEDVULNERABILITY._serialized_end=1163
|
||||
_PATCH._serialized_start=1165
|
||||
_PATCH._serialized_end=1230
|
||||
_INDEXREQUEST._serialized_start=1233
|
||||
_INDEXREQUEST._serialized_end=1363
|
||||
_INDEXOUTPUT._serialized_start=1366
|
||||
_INDEXOUTPUT._serialized_end=1495
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
@@ -62,6 +62,38 @@ class Crash(_message.Message):
|
||||
target: BuildOutput
|
||||
def __init__(self, target: _Optional[_Union[BuildOutput, _Mapping]] = ..., harness_name: _Optional[str] = ..., crash_input_path: _Optional[str] = ..., stacktrace: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class IndexOutput(_message.Message):
|
||||
__slots__ = ["build_type", "ossfuzz", "package_name", "sanitizer", "source_path", "task_id"]
|
||||
BUILD_TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||
OSSFUZZ_FIELD_NUMBER: _ClassVar[int]
|
||||
PACKAGE_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||
SANITIZER_FIELD_NUMBER: _ClassVar[int]
|
||||
SOURCE_PATH_FIELD_NUMBER: _ClassVar[int]
|
||||
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
build_type: str
|
||||
ossfuzz: str
|
||||
package_name: str
|
||||
sanitizer: str
|
||||
source_path: str
|
||||
task_id: str
|
||||
def __init__(self, package_name: _Optional[str] = ..., sanitizer: _Optional[str] = ..., ossfuzz: _Optional[str] = ..., source_path: _Optional[str] = ..., task_id: _Optional[str] = ..., build_type: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class IndexRequest(_message.Message):
|
||||
__slots__ = ["build_type", "ossfuzz", "package_name", "sanitizer", "source_path", "task_id"]
|
||||
BUILD_TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||
OSSFUZZ_FIELD_NUMBER: _ClassVar[int]
|
||||
PACKAGE_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||
SANITIZER_FIELD_NUMBER: _ClassVar[int]
|
||||
SOURCE_PATH_FIELD_NUMBER: _ClassVar[int]
|
||||
TASK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
build_type: str
|
||||
ossfuzz: str
|
||||
package_name: str
|
||||
sanitizer: str
|
||||
source_path: str
|
||||
task_id: str
|
||||
def __init__(self, package_name: _Optional[str] = ..., sanitizer: _Optional[str] = ..., ossfuzz: _Optional[str] = ..., source_path: _Optional[str] = ..., task_id: _Optional[str] = ..., build_type: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class Patch(_message.Message):
|
||||
__slots__ = ["patch", "task_id", "vulnerability_id"]
|
||||
PATCH_FIELD_NUMBER: _ClassVar[int]
|
||||
|
||||
@@ -13,6 +13,8 @@ from buttercup.common.datastructures.msg_pb2 import (
|
||||
TaskDelete,
|
||||
Patch,
|
||||
ConfirmedVulnerability,
|
||||
IndexRequest,
|
||||
IndexOutput,
|
||||
)
|
||||
import logging
|
||||
from typing import Type, Generic, TypeVar, Literal, overload
|
||||
@@ -31,6 +33,8 @@ class QueueNames(str, Enum):
|
||||
READY_TASKS = "tasks_ready_queue"
|
||||
DELETE_TASK = "orchestrator_delete_task_queue"
|
||||
PATCHES = "patches_queue"
|
||||
INDEX = "index_queue"
|
||||
INDEX_OUTPUT = "index_output_queue"
|
||||
|
||||
|
||||
class GroupNames(str, Enum):
|
||||
@@ -42,6 +46,7 @@ class GroupNames(str, Enum):
|
||||
SCHEDULER_BUILD_OUTPUT = "scheduler_build_output_group"
|
||||
UNIQUE_VULNERABILITIES = "unique_vulnerabilities_group"
|
||||
CONFIRMED_VULNERABILITIES = "confirmed_vulnerabilities_group"
|
||||
INDEX = "index_group"
|
||||
|
||||
|
||||
class HashNames(str, Enum):
|
||||
@@ -57,7 +62,8 @@ CRASH_TASK_TIMEOUT_MS = 10 * 60 * 1000
|
||||
PATCH_TASK_TIMEOUT_MS = 10 * 60 * 1000
|
||||
UNIQUE_VULNERABILITIES_TASK_TIMEOUT_MS = 10 * 60 * 1000
|
||||
CONFIRMED_VULNERABILITIES_TASK_TIMEOUT_MS = 10 * 60 * 1000
|
||||
|
||||
INDEX_TASK_TIMEOUT_MS = 10 * 60 * 1000
|
||||
INDEX_OUTPUT_TASK_TIMEOUT_MS = 10 * 60 * 1000
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -312,6 +318,18 @@ class QueueFactory:
|
||||
PATCH_TASK_TIMEOUT_MS,
|
||||
[GroupNames.ORCHESTRATOR],
|
||||
),
|
||||
QueueNames.INDEX: QueueConfig(
|
||||
QueueNames.INDEX,
|
||||
IndexRequest,
|
||||
INDEX_TASK_TIMEOUT_MS,
|
||||
[GroupNames.INDEX],
|
||||
),
|
||||
QueueNames.INDEX_OUTPUT: QueueConfig(
|
||||
QueueNames.INDEX_OUTPUT,
|
||||
IndexOutput,
|
||||
INDEX_OUTPUT_TASK_TIMEOUT_MS,
|
||||
[GroupNames.INDEX],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -360,6 +378,16 @@ class QueueFactory:
|
||||
self, queue_name: Literal[QueueNames.PATCHES], group_name: GroupNames, **kwargs: Any
|
||||
) -> ReliableQueue[Patch]: ...
|
||||
|
||||
@overload
|
||||
def create(
|
||||
self, queue_name: Literal[QueueNames.INDEX], group_name: GroupNames, **kwargs: Any
|
||||
) -> ReliableQueue[IndexRequest]: ...
|
||||
|
||||
@overload
|
||||
def create(
|
||||
self, queue_name: Literal[QueueNames.INDEX_OUTPUT], group_name: GroupNames, **kwargs: Any
|
||||
) -> ReliableQueue[IndexOutput]: ...
|
||||
|
||||
def create(
|
||||
self, queue_name: QueueNames, group_name: GroupNames | None = None, **kwargs: Any
|
||||
) -> ReliableQueue[MsgType]:
|
||||
|
||||
+46
-17
@@ -31,12 +31,6 @@ services:
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
graphdb:
|
||||
image: janusgraph/janusgraph:1.2.0-20250113-082101.7f6ffcf
|
||||
restart: always
|
||||
ports:
|
||||
- '127.0.0.1:8182:8182'
|
||||
|
||||
stimulate-fuzzer-test:
|
||||
build:
|
||||
context: ./
|
||||
@@ -73,27 +67,62 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
indexer-run:
|
||||
image: program-model-image
|
||||
command: -m buttercup.program_model.indexer.oss_fuzz_indexer --scriptdir /app/scripts --url ws://graphdb:8182/gremlin --allow_pull --oss_fuzz_dir /crs_scratch/ossfuzz --base_image_url gcr.io/oss-fuzz --package_name libpng --wdir /crs_scratch/ --kythe_dir /app/kythe-v0.0.67 --python /app/.venv/bin/python
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./program-model/upload_worker.Dockerfile
|
||||
profiles:
|
||||
- indexer-run
|
||||
graphdb:
|
||||
image: janusgraph/janusgraph:1.2.0-20250126-150810.46a971a
|
||||
environment:
|
||||
- janusgraph.storage.backend=cql
|
||||
- janusgraph.storage.hostname=jg-cassandra
|
||||
- janusgraph.storage.cql.keyspace=janusgraph
|
||||
- _JAVA_OPTIONS=-Xms4g -Xmx6g
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "bin/gremlin.sh", "-e", "scripts/remote-connect.groovy"]
|
||||
interval: 10s
|
||||
timeout: 60s
|
||||
retries: 4
|
||||
volumes:
|
||||
- ./crs_scratch:/crs_scratch
|
||||
- ./program-model/conf/janusgraph-server.yaml:/opt/janusgraph/conf/janusgraph-server.yaml
|
||||
- ./program-model/load_data.groovy:/opt/janusgraph/load_data.groovy
|
||||
depends_on:
|
||||
graphdb-storage:
|
||||
condition: service_healthy
|
||||
|
||||
graphdb-storage:
|
||||
image: cassandra:4.0.6
|
||||
container_name: jg-cassandra
|
||||
environment:
|
||||
# These values will be modified automatically at competition time
|
||||
- CASSANDRA_START_RPC=true
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "nodetool", "status"]
|
||||
interval: 10s
|
||||
timeout: 60s
|
||||
retries: 4
|
||||
|
||||
program-model:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./program-model/Dockerfile
|
||||
command: [
|
||||
"buttercup-program-model",
|
||||
"--log_level", "debug",
|
||||
"serve",
|
||||
"--wdir", "/crs_scratch",
|
||||
"--sleep_time", "5",
|
||||
"--redis_url", "redis://redis:6379",
|
||||
]
|
||||
environment:
|
||||
- BUTTERCUP_PROGRAM_MODEL_MOCK_MODE=true
|
||||
- DOCKER_HOST=tcp://dind:2375
|
||||
restart: no
|
||||
volumes:
|
||||
- ./crs_scratch:/crs_scratch
|
||||
- ./tasks_storage:/tasks_storage
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
dind:
|
||||
condition: service_started
|
||||
graphdb:
|
||||
condition: service_started
|
||||
condition: service_healthy
|
||||
|
||||
coverage-bot:
|
||||
command: ["buttercup-coverage-bot", "--wdir", "/crs_scratch", "--redis_url", "redis://redis:6379"]
|
||||
|
||||
@@ -12,7 +12,8 @@ download-kythe:
|
||||
set -euxo pipefail
|
||||
if [[ ! -f "program-model/scripts/gzs/kythe-v0.0.67.tar.gz" ]]
|
||||
then
|
||||
curl -o program-model/scripts/gzs/kythe-v0.0.67.tar.gz https://github.com/trailofbits/aixcc-kythe/releases/download/v0.0.2/kythe-v0.0.67.tar.gz
|
||||
mkdir -p program-model/scripts/gzs/
|
||||
gh release download v0.0.2 -R github.com/trailofbits/aixcc-kythe -D program-model/scripts/gzs/
|
||||
fi
|
||||
|
||||
build-indexer-image:
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
scripts/gzs
|
||||
*.pyc
|
||||
program_model/data/kythe/proto
|
||||
program_model/services/kythe/proto
|
||||
kythe_builder/build_out
|
||||
kythe_builder/build_out
|
||||
|
||||
@@ -14,33 +14,28 @@ ENV UV_LINK_MODE=copy \
|
||||
UV_PYTHON_DOWNLOADS=never \
|
||||
UV_PYTHON=python3.10
|
||||
|
||||
WORKDIR /app
|
||||
WORKDIR /program-model
|
||||
|
||||
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=program-model/uv.lock,target=uv.lock \
|
||||
--mount=type=bind,source=program-model/pyproject.toml,target=pyproject.toml \
|
||||
uv sync --frozen --no-install-project --no-editable
|
||||
--mount=type=bind,source=common/README.md,target=/common/README.md \
|
||||
--mount=type=bind,source=program-model/uv.lock,target=/program-model/uv.lock \
|
||||
--mount=type=bind,source=program-model/pyproject.toml,target=/program-model/pyproject.toml \
|
||||
--mount=type=bind,source=program-model/README.md,target=/program-model/README.md \
|
||||
cd /program-model && uv sync --frozen --no-install-project --no-editable
|
||||
|
||||
ADD ./common /common
|
||||
|
||||
COPY program-model/src /app/src
|
||||
COPY program-model/pyproject.toml /app/pyproject.toml
|
||||
COPY program-model/uv.lock /app/uv.lock
|
||||
COPY program-model/scripts /app/scripts
|
||||
COPY ./common /common
|
||||
COPY ./program-model /program-model
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-editable
|
||||
|
||||
|
||||
FROM base AS runtime
|
||||
WORKDIR /app
|
||||
WORKDIR /program-model
|
||||
|
||||
COPY --from=builder --chown=app:app /app/scripts /app/scripts
|
||||
RUN tar -xvf /app/scripts/gzs/kythe-v0.0.67.tar.gz
|
||||
COPY --from=builder --chown=app:app /app/.venv /app/.venv
|
||||
COPY ./program-model/scripts /program-model/scripts
|
||||
|
||||
|
||||
ENTRYPOINT ["/app/.venv/bin/python"]
|
||||
COPY --from=builder --chown=app:app /program-model/.venv /program-model/.venv
|
||||
ENV PATH=/program-model/.venv/bin:$PATH
|
||||
+158
-41
@@ -1,53 +1,170 @@
|
||||
# Program Model
|
||||
|
||||
This is a demo project that shows how to index an oss-fuzz project (reliablyish) with kythe and upload the results in a graph db
|
||||
Indexes a program into a graph database.
|
||||
|
||||
The components are listed and discussed below:
|
||||
## Requirements
|
||||
|
||||
## kythe_builder
|
||||
`n2-highmem-8` [instance](https://cloud.google.com/compute/docs/general-purpose-machines#n2-high-mem).
|
||||
|
||||
This is a docker image responsible for building kythe within the base-clang image. This should be built first and produces a tar.gz of kythe that can be injected in oss-fuzz docker images.
|
||||
## Setup
|
||||
|
||||
## scripts
|
||||
Set up working directory and tasks directory.
|
||||
|
||||
These scripts are used to create a Docker image that is dervived from a project builder e.g. gcr.io/oss-fuzz/libpng.
|
||||
```shell
|
||||
cd afc-crs-trail-of-bits/
|
||||
|
||||
The Dockerfile is parameterized over the base image used. So one can build an oss-fuzz project with kythe as follows:
|
||||
* use oss-fuzz helper.py to build the project
|
||||
* build the scripts/Dockerfile with the project as the base
|
||||
cp env.template .env
|
||||
|
||||
`cd scripts && docker build --build-arg BASE_IMAGE=gcr.io/oss-fuzz/libpng .`
|
||||
Then you should be able to run compile as normal for an oss-fuzz image and generate kzips (indexed versions of the object files).
|
||||
sudo mkdir /crs_scratch/ && sudo chown `whoami`:`whoami` /crs_scratch && sudo mount --bind ./crs_scratch /crs_scratch
|
||||
|
||||
## kythe
|
||||
|
||||
Once you have a kzip for each compilation action then you need to merge these to a global kzip using
|
||||
`kythe/tools/kzip merge --output $KYTHE_OUTPUT_DIRECTORY/merged.kzip $KYTHE_OUTPUT_DIRECTORY/*.kzip`
|
||||
|
||||
Once you have a bunch of kzips you need to index these with the cxx_indexer in kythe.
|
||||
|
||||
e.g.
|
||||
|
||||
`kythe/indexers/cxx_indexer --ignore_unimplemented <merged.kzip>`
|
||||
|
||||
This produces a stream of protobuf on stdout. Specifically each protobuf record is an entry described in this spec [text](https://kythe.io/docs/kythe-storage.html#_entry)
|
||||
|
||||
## program_model
|
||||
|
||||
This pyhon script specifically program_model/indexer/entries_into_db.py reads the protobuf entries from kythe and uploads them to JanusGraph.
|
||||
|
||||
This is invoked (after entering poetry shell) with
|
||||
`python program_model/indexer/entries_into_db --url <janus_graph_url>`
|
||||
|
||||
janus graph can be run via docker with
|
||||
|
||||
`docker run janusgraph/janusgraph`
|
||||
|
||||
The url can be determined with
|
||||
|
||||
```
|
||||
docker inspect \
|
||||
-f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <janus container>
|
||||
sudo mkdir /tasks_storage && sudo chown `whoami`:`whoami` /tasks_storage && sudo mount --bind ./tasks_storage /tasks_storage
|
||||
```
|
||||
|
||||
this will get you the ip, then the url is `ws://<IP address>:8182/gremlin`
|
||||
Set up GitHub token.
|
||||
|
||||
```shell
|
||||
gh auth login
|
||||
GitHub.com
|
||||
SSH
|
||||
No
|
||||
Paste an authentication token
|
||||
|
||||
echo <token> | docker login ghcr.io -u <username> --password-stdin
|
||||
```
|
||||
|
||||
Download Kythe.
|
||||
|
||||
```shell
|
||||
cd afc-crs-trail-of-bits/
|
||||
|
||||
mkdir -p program-model/scripts/gzs/kythe/
|
||||
|
||||
gh release download v0.0.2 -R github.com/trailofbits/aixcc-kythe -D program-model/scripts/gzs/
|
||||
|
||||
tar -xvzf program-model/scripts/gzs/kythe-v0.0.67.tar.gz -C program-model/scripts/gzs/kythe/ --strip-components=1
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Prepare example challenge project.
|
||||
|
||||
```shell
|
||||
cd afc-crs-trail-of-bits/
|
||||
|
||||
mkdir -p tasks_storage/example-libpng/src/ && mkdir -p tasks_storage/example-libpng/fuzz-tooling/
|
||||
|
||||
git clone --branch aixcc-exemplar-challenge-01 git@github.com:aixcc-finals/example-libpng.git tasks_storage/example-libpng/src/example-libpng
|
||||
|
||||
git clone --branch master git@github.com:aixcc-finals/oss-fuzz-aixcc.git tasks_storage/example-libpng/fuzz-tooling/fuzz-tooling
|
||||
```
|
||||
|
||||
Start up CRS.
|
||||
|
||||
```shell
|
||||
cd afc-crs-trail-of-bits/
|
||||
|
||||
docker compose up -d --build
|
||||
|
||||
# cd common/
|
||||
# uv run src/buttercup/common/msg_publisher.py send tasks_ready_queue ../program-model/mock_data/ready_msg.json
|
||||
```
|
||||
|
||||
Send task to Program Model via Redis queue.
|
||||
|
||||
```shell
|
||||
docker compose logs -f program-model
|
||||
```
|
||||
|
||||
```shell
|
||||
cd afc-crs-trail-of-bits/program-model/
|
||||
|
||||
uv run mock/trigger_pm.py \
|
||||
--package_name libpng \
|
||||
--sanitizer AddressSanitizer \
|
||||
--source_path ../tasks_storage/example-libpng/src/example-libpng/ \
|
||||
--ossfuzz ../tasks_storage/example-libpng/fuzz-tooling/fuzz-tooling/ \
|
||||
--task_id example-libpng \
|
||||
--build_type full
|
||||
```
|
||||
|
||||
Send task to Program Model via cli.
|
||||
|
||||
```shell
|
||||
cd afc-crs-trail-of-bits/program-model/
|
||||
|
||||
uv run buttercup-program-model \
|
||||
--log_level debug \
|
||||
process \
|
||||
--wdir ../crs_scratch/ \
|
||||
--script_dir scripts/ \
|
||||
--kythe_dir scripts/gzs/kythe/ \
|
||||
--package_name libpng \
|
||||
--sanitizer AddressSanitizer \
|
||||
--source_path ../tasks_storage/example-libpng/src/example-libpng/ \
|
||||
--ossfuzz ../tasks_storage/example-libpng/fuzz-tooling/fuzz-tooling/ \
|
||||
--task_id example-libpng \
|
||||
--build_type full
|
||||
```
|
||||
|
||||
## Testing on Challenges
|
||||
|
||||
See [challenges.md](challenges.md).
|
||||
|
||||
## Development
|
||||
|
||||
Create a new virtual environment and enter it:
|
||||
|
||||
```shell
|
||||
cd afc-crs-trail-of-bits/program-model/
|
||||
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv sync --all-extras
|
||||
```
|
||||
|
||||
Run tests:
|
||||
|
||||
```shell
|
||||
pytest
|
||||
```
|
||||
|
||||
Lock, reformat, and lint before committing changes:
|
||||
|
||||
```shell
|
||||
just lock
|
||||
just reformat
|
||||
just lint
|
||||
```
|
||||
|
||||
## FAQs
|
||||
|
||||
* Why does this use a ubuntu base image?
|
||||
* Since Kythe uses OSS Fuzz to build and index the challenge source code, we have to use the same base image as ClusterFuzz.
|
||||
* Why does this use Python 3.10?
|
||||
* ClusterFuzz uses Python 3.10.
|
||||
* How do I build Kythe?
|
||||
* Follow the instructions in [dev.md](dev.md).
|
||||
|
||||
## TODO
|
||||
|
||||
* [x] Configure Dockerfiles
|
||||
* [x] Modify `cxxwrapper.sh` to match `ccwrapper.sh`
|
||||
* [x] Untruncate graph (see `graph.py`)
|
||||
* [x] Speed up graph creation (see `program_model.py`)
|
||||
* [ ] Create getter/setter APIs
|
||||
* [ ] Create unit and regression tests
|
||||
* [ ] Create one graph per task id
|
||||
* [ ] Integrate Program Model with other CRS components
|
||||
* [ ] Coverage Tracker
|
||||
* [ ] Seed Generator
|
||||
* [ ] Patcher
|
||||
* [ ] Verify graph creation correctness
|
||||
* [ ] Implement more error handling, especially for subprocess runs
|
||||
* [ ] Download Kythe `.tar.gz` file automatically from github releases
|
||||
* [ ] Debug issue in `oss_fuzz_indexer.py:index_target()` -- outputted `kzip` files contain critical errors
|
||||
* [ ] Ensure challenge project is being built and not the oss-fuzz project
|
||||
* [ ] Periodically backup the graph database into a graphml file
|
||||
* [ ] Figure out how to make `ccwrapper.sh` not break LD detection
|
||||
* [ ] Consider periodically removing indexing images for disk space
|
||||
* [ ] Add support for `java`
|
||||
* [ ] Consider corner cases
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Challenges
|
||||
|
||||
Details of challenges tested.
|
||||
|
||||
## [libpng](https://github.com/aixcc-finals/example-libpng)
|
||||
|
||||
* 2 minutes to write graphml file
|
||||
* Takes about 8 GB of memory
|
||||
* Outputted:
|
||||
* Nodes: 424,350
|
||||
* Edges: 3,706,932
|
||||
|
||||
* 14 minutes to load into JanusGraph
|
||||
* Takes about 10 GB of memory
|
||||
* Outputted:
|
||||
* Nodes: 217,060
|
||||
* Edges: 1,853,466
|
||||
|
||||
**TODO:** Reconcile the differences in the number of nodes and edges.
|
||||
|
||||
### Full-scan
|
||||
|
||||
* **base:** `2c894c66108f0724331a9e5b4826e351bf2d094b`
|
||||
|
||||
### Delta-scan
|
||||
|
||||
* **base:** `0cc367aaeaac3f888f255cee5d394968996f736e`
|
||||
* **delta:** `2c894c66108f0724331a9e5b4826e351bf2d094b`
|
||||
@@ -0,0 +1,44 @@
|
||||
host: 0.0.0.0
|
||||
port: 8182
|
||||
evaluationTimeout: 30000
|
||||
channelizer: org.apache.tinkerpop.gremlin.server.channel.WebSocketChannelizer
|
||||
graphManager: org.janusgraph.graphdb.management.JanusGraphManager
|
||||
graphs: {
|
||||
#graph: conf/janusgraph-inmemory.properties
|
||||
graph: conf/janusgraph-cql.properties
|
||||
}
|
||||
scriptEngines: {
|
||||
gremlin-groovy: {
|
||||
plugins: { org.janusgraph.graphdb.tinkerpop.plugin.JanusGraphGremlinPlugin: {},
|
||||
org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
|
||||
org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
|
||||
org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
|
||||
org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/empty-sample.groovy]}
|
||||
}
|
||||
}
|
||||
}
|
||||
# serializers:
|
||||
# - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
|
||||
# - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1, config: { serializeResultToString: true }}
|
||||
# - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
|
||||
processors:
|
||||
- { className: org.apache.tinkerpop.gremlin.server.op.session.SessionOpProcessor, config: { sessionTimeout: 28800000 }}
|
||||
- { className: org.apache.tinkerpop.gremlin.server.op.traversal.TraversalOpProcessor, config: { cacheExpirationTime: 600000, cacheMaxSize: 1000 }}
|
||||
metrics: {
|
||||
consoleReporter: {enabled: true, interval: 180000},
|
||||
csvReporter: {enabled: true, interval: 180000, fileName: /tmp/gremlin-server-metrics.csv},
|
||||
jmxReporter: {enabled: true},
|
||||
slf4jReporter: {enabled: true, interval: 180000},
|
||||
graphiteReporter: {enabled: false, interval: 180000}}
|
||||
maxInitialLineLength: 4096
|
||||
maxHeaderSize: 8192
|
||||
maxChunkSize: 8192
|
||||
|
||||
# NOTE(Evan): We need to increase the maxContentLength to 2MB to allow for larger Kythe entries.
|
||||
# 2MB = 2 * 1024 * 1024
|
||||
maxContentLength: 2097152
|
||||
|
||||
maxAccumulationBufferComponents: 1024
|
||||
resultIterationBatchSize: 64
|
||||
writeBufferLowWaterMark: 32768
|
||||
writeBufferHighWaterMark: 65536
|
||||
@@ -0,0 +1,229 @@
|
||||
# Dev
|
||||
|
||||
Documentation to get started on developing Program Model APIs.
|
||||
|
||||
## Requirements
|
||||
|
||||
`n2-standard-4` [instance](https://cloud.google.com/compute/docs/general-purpose-machines#n2_machine_types).
|
||||
|
||||
## Docker (WIP)
|
||||
|
||||
```shell
|
||||
(type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) \
|
||||
&& sudo mkdir -p -m 755 /etc/apt/keyrings \
|
||||
&& out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
||||
&& cat $out | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
|
||||
&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
|
||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
|
||||
&& sudo apt update \
|
||||
&& sudo apt install gh -y
|
||||
sudo apt update
|
||||
sudo apt install gh
|
||||
|
||||
github.com -> settings -> developer settings -> personal access tokens -> tokens (classic) -> generate new token
|
||||
select "repo" scope
|
||||
select "read:org" scope
|
||||
|
||||
gh auth login
|
||||
GitHub.com
|
||||
SSH
|
||||
No
|
||||
Paste an authentication token
|
||||
|
||||
gh release download v0.0.2 -R github.com/trailofbits/aixcc-kythe
|
||||
<authenticate URL in browser for the first time>
|
||||
rm kythe-v0.0.67.tar.gz
|
||||
|
||||
sudo apt install just
|
||||
|
||||
cd afc-crs-trail-of-bits/
|
||||
cp env.template .env
|
||||
|
||||
mkdir crs_scratch/
|
||||
git clone --recursive git@github.com:aixcc-finals/example-libpng.git crs_scratch/libpng
|
||||
|
||||
just run-indexer
|
||||
|
||||
<todo>
|
||||
|
||||
docker compose down
|
||||
```
|
||||
|
||||
## Local
|
||||
|
||||
### Setup
|
||||
|
||||
```shell
|
||||
cd afc-crs-trail-of-bits/
|
||||
cd program-model/
|
||||
mkdir crs_scratch/
|
||||
```
|
||||
|
||||
### Download libpng
|
||||
|
||||
```shell
|
||||
git clone git@github.com:aixcc-finals/example-libpng.git crs_scratch/libpng
|
||||
```
|
||||
|
||||
### Download Kythe
|
||||
|
||||
```shell
|
||||
mkdir -p scripts/gzs/
|
||||
|
||||
gh release download v0.0.2 -R github.com/trailofbits/aixcc-kythe -D scripts/gzs/
|
||||
|
||||
mkdir crs_scratch/opt/
|
||||
|
||||
tar -zxf scripts/gzs/kythe-v0.0.67.tar.gz --directory crs_scratch/opt/
|
||||
```
|
||||
|
||||
### If no download available, build Kythe
|
||||
|
||||
Takes about 3 hours to build.
|
||||
|
||||
#### Install Bazel
|
||||
|
||||
Follow [instructions](https://bazel.build/install/ubuntu#install-on-ubuntu). You will need to install a specific version of Bazel depending on what the `build` output below outputs.
|
||||
|
||||
#### Install Dependencies
|
||||
|
||||
```shell
|
||||
sudo apt install flex bison asciidoc graphviz source-highlight clang
|
||||
```
|
||||
|
||||
#### Download and build Kythe
|
||||
|
||||
From [documentation](https://kythe.io/getting-started/#build-a-release-of-kythe-using-bazel-and-unpack-it-in-optkythe)
|
||||
|
||||
```shell
|
||||
git clone git@github.com:trailofbits/aixcc-kythe.git crs_scratch/aixcc-kythe
|
||||
|
||||
cd crs_scratch/aixcc-kythe
|
||||
|
||||
git checkout a301676c20db9849a06878fa9cc017907eb64d72
|
||||
|
||||
bazel build //kythe/release
|
||||
|
||||
mkdir ../opt/
|
||||
|
||||
tar -zxf bazel-bin/kythe/release/kythe-v0.0.67.tar.gz --directory ../opt/
|
||||
|
||||
cd ../
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Run indexer with Kythe
|
||||
|
||||
From [documentation](https://kythe.io/examples/#extracting-cmake-based-repositories)
|
||||
|
||||
```shell
|
||||
sudo apt install cmake
|
||||
|
||||
cd crs_scratch/
|
||||
export KYTHE_ROOT_DIRECTORY=`pwd`/libpng
|
||||
export KYTHE_CORPUS="myrepo"
|
||||
export KYTHE_OUTPUT_DIRECTORY=`pwd`/kythe_output
|
||||
mkdir -p "$KYTHE_OUTPUT_DIRECTORY"
|
||||
export CMAKE_ROOT_DIRECTORY=`pwd`/libpng
|
||||
|
||||
cd libpng/
|
||||
|
||||
../opt/kythe-v0.0.67/tools/runextractor cmake -extractor=../opt/kythe-v0.0.67/extractors/cxx_extractor -sourcedir=$CMAKE_ROOT_DIRECTORY
|
||||
```
|
||||
|
||||
### Merge kzip files
|
||||
|
||||
```shell
|
||||
cd crs_scratch/
|
||||
|
||||
./opt/kythe-v0.0.67/tools/kzip merge --output $KYTHE_OUTPUT_DIRECTORY/merged.kzip $KYTHE_OUTPUT_DIRECTORY/*.kzip
|
||||
|
||||
./opt/kythe-v0.0.67/tools/kzip info --input $KYTHE_OUTPUT_DIRECTORY/merged.kzip | jq .
|
||||
|
||||
./opt/kythe-v0.0.67/tools/kzip view $KYTHE_OUTPUT_DIRECTORY/merged.kzip | jq .
|
||||
```
|
||||
|
||||
### Upload to JanusGraph
|
||||
|
||||
Extract graphml file from kzip contents.
|
||||
|
||||
```shell
|
||||
cd program-model/src/buttercup
|
||||
|
||||
mkdir ../../data/
|
||||
|
||||
time ../../crs_scratch/opt/kythe-v0.0.67/indexers/cxx_indexer --ignore_unimplemented ../../crs_scratch/kythe_output/merged.kzip | uv run program_model/indexer/entries_into_graphml.py --output ../../data/graph.xml
|
||||
```
|
||||
|
||||
Create network for JanusGraph to talk to Cassandra backend storage.
|
||||
|
||||
```shell
|
||||
docker network create janusgraph-net
|
||||
```
|
||||
|
||||
Start Cassandra [container](https://github.com/JanusGraph/janusgraph/blob/master/docs/storage-backend/cassandra.md#local-container-mode).
|
||||
|
||||
```shell
|
||||
cd program-model/
|
||||
|
||||
./start_cql.sh
|
||||
```
|
||||
|
||||
Start JanusGraph container with custom configuration file.
|
||||
|
||||
```shell
|
||||
cd program-model/
|
||||
|
||||
./start_jg.sh
|
||||
```
|
||||
|
||||
Run graph creation.
|
||||
|
||||
```shell
|
||||
cd program-model/
|
||||
docker ps | grep janusgraph
|
||||
|
||||
time docker run --rm --link <container_name>:janusgraph --network janusgraph-net -e GREMLIN_REMOTE_HOSTS=janusgraph -v `pwd`/load_data.groovy:/opt/janusgraph/load_data.groovy janusgraph/janusgraph -- ./bin/gremlin.sh -e load_data.groovy
|
||||
```
|
||||
|
||||
Verify graph creation. From [documentation](https://docs.janusgraph.org/getting-started/installation/).
|
||||
|
||||
```shell
|
||||
docker run --rm --link <container_name>:janusgraph --network janusgraph-net -e GREMLIN_REMOTE_HOSTS=janusgraph -it janusgraph/janusgraph ./bin/gremlin.sh
|
||||
```
|
||||
|
||||
```shell
|
||||
gremlin> :remote connect tinkerpop.server conf/remote.yaml
|
||||
gremlin> :remote console
|
||||
gremlin> g.V().count()
|
||||
gremlin> g.E().count()
|
||||
```
|
||||
|
||||
Clear graph.
|
||||
|
||||
```shell
|
||||
gremlin> g.V().drop().iterate()
|
||||
gremlin> g.E().drop().iterate()
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Before committing code, run the following to ensure that the code is formatted correctly.
|
||||
|
||||
```shell
|
||||
cd program-model/
|
||||
|
||||
just reformat
|
||||
|
||||
just lint
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
* [x] Use Indexes: `org.janusgraph.graphdb.transaction.StandardJanusGraphTx$3.execute - Query requires iterating over all vertices [[~label = x]]. For better performance, use indexes`
|
||||
* [x] Consider [bulk loading](https://docs.janusgraph.org/operations/bulk-loading/) if needed
|
||||
* Not required at the moment.
|
||||
* [x] Consider loading data from a [file](https://tinkerpop.apache.org/docs/3.7.3/dev/io/).
|
||||
* [ ] Create script to verify creation of graph is accurate.
|
||||
* [ ] Reconcile the differences in the number of nodes and edges for libpng example.
|
||||
@@ -0,0 +1,11 @@
|
||||
lock:
|
||||
uv lock
|
||||
uv sync --all-extras
|
||||
|
||||
lint:
|
||||
uv run ruff format --check
|
||||
uv run ruff check
|
||||
|
||||
reformat:
|
||||
uv run ruff format
|
||||
uv run ruff check --fix
|
||||
@@ -0,0 +1,16 @@
|
||||
// We need to configure the console to have state:
|
||||
// https://tinkerpop.apache.org/docs/current/reference/#console-sessions
|
||||
:remote connect tinkerpop.server conf/remote.yaml session
|
||||
:remote console
|
||||
|
||||
// 1 hour in milliseconds
|
||||
:remote config timeout 3600000
|
||||
|
||||
println("Starting graph creation...")
|
||||
|
||||
g.io('/crs_scratch/graph.xml').read().iterate()
|
||||
|
||||
println("Graph creation complete.")
|
||||
|
||||
:remote console
|
||||
:exit
|
||||
@@ -0,0 +1,9 @@
|
||||
task {
|
||||
message_id: "my-message-id"
|
||||
message_time: 123
|
||||
task_type: TASK_TYPE_FULL
|
||||
task_id: "example-libpng"
|
||||
sources {
|
||||
url: "https://github.com/buttercup-project/buttercup"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import argparse
|
||||
from redis import Redis
|
||||
from buttercup.common.queues import QueueFactory, QueueNames
|
||||
from buttercup.common.datastructures.msg_pb2 import IndexRequest
|
||||
|
||||
|
||||
def main():
|
||||
prsr = argparse.ArgumentParser("trigger Program Model manually")
|
||||
prsr.add_argument("--redis_url", default="redis://127.0.0.1:6379")
|
||||
prsr.add_argument("--package_name", required=True)
|
||||
prsr.add_argument("--sanitizer", required=True)
|
||||
prsr.add_argument("--ossfuzz", required=True)
|
||||
prsr.add_argument("--source_path", required=True)
|
||||
prsr.add_argument("--task_id", required=True)
|
||||
prsr.add_argument("--build_type", required=True)
|
||||
args = prsr.parse_args()
|
||||
|
||||
redis = Redis.from_url(args.redis_url)
|
||||
queue = QueueFactory(redis).create(QueueNames.INDEX)
|
||||
req = IndexRequest(
|
||||
package_name=args.package_name,
|
||||
sanitizer=args.sanitizer,
|
||||
ossfuzz=args.ossfuzz,
|
||||
source_path=args.source_path,
|
||||
task_id=args.task_id,
|
||||
build_type=args.build_type,
|
||||
)
|
||||
queue.push(req)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,7 +1,8 @@
|
||||
[project]
|
||||
name = "program-model"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
description = "Buttercup contextualizer"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{name = "Trail of Bits",email = "opensource@trailofbits"}
|
||||
]
|
||||
@@ -12,6 +13,9 @@ dependencies = [
|
||||
"protobuf (>=3.20, <=3.20.3)",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
buttercup-program-model = "buttercup.program_model.__cli__:main"
|
||||
|
||||
[tool.uv.sources]
|
||||
common = { path = "../common", editable = true }
|
||||
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
ARG BASE_IMAGE=myorg/myapp:latest
|
||||
FROM $BASE_IMAGE
|
||||
ARG KYTHE_VERSION=v0.0.67
|
||||
ARG KYTHE_TAR=gzs/kythe-v0.0.67.tar.gz
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
COPY ./ccwrapper.sh $SRC/ccwrapper.sh
|
||||
COPY ./cxxwrapper.sh $SRC/cxxwrapper.sh
|
||||
RUN chmod +x $SRC/ccwrapper.sh && chmod +x $SRC/cxxwrapper.sh
|
||||
COPY ./ccwrapper.sh ${SRC}/ccwrapper.sh
|
||||
COPY ./cxxwrapper.sh ${SRC}/cxxwrapper.sh
|
||||
RUN chmod +x ${SRC}/ccwrapper.sh && chmod +x ${SRC}/cxxwrapper.sh
|
||||
|
||||
ENV FUZZING_LANGUAGE=c++
|
||||
|
||||
ENV ORIG_CXX=$CXX
|
||||
ENV ORIG_CC=$CC
|
||||
ENV CXX=$SRC/cxxwrapper.sh
|
||||
ENV CCC=$SRC/cxxwrapper.sh
|
||||
ENV CC=$SRC/ccwrapper.sh
|
||||
ENV ORIG_CXX=${CXX}
|
||||
ENV ORIG_CC=${CC}
|
||||
ENV CXX=${SRC}/cxxwrapper.sh
|
||||
ENV CCC=${SRC}/cxxwrapper.sh
|
||||
ENV CC=${SRC}/ccwrapper.sh
|
||||
|
||||
ADD $KYTHE_TAR $SRC/kythe
|
||||
#RUN tar -xvf $SRC/kythe-release.tar.gz -C $SRC
|
||||
ENV KYTHE_RELEASE_DIR=$SRC/kythe/kythe-$KYTHE_VERSION
|
||||
COPY gzs/kythe/ ${SRC}/kythe
|
||||
ENV KYTHE_RELEASE_DIR=${SRC}/kythe/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# cxx_wrapper.sh
|
||||
# ccwrapper.sh
|
||||
#!/bin/bash -e
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
# cxx_wrapper.sh
|
||||
# cxxwrapper.sh
|
||||
#!/bin/bash -e
|
||||
|
||||
# TODO(Ian): we need to share the code from ccwrapper.sh that moves kzip files between temp and original directories
|
||||
set -euxo pipefail
|
||||
|
||||
echo "Called with $@"
|
||||
echo "done"
|
||||
$KYTHE_RELEASE_DIR/extractors/cxx_extractor "$@" & pid=$!
|
||||
$ORIG_CXX "$@"
|
||||
wait "$pid"
|
||||
|
||||
KYTHE_OUTPUT_DIRECTORY_ORIG=$KYTHE_OUTPUT_DIRECTORY
|
||||
KYTHE_OUTPUT_DIRECTORY=$(mktemp -d -t kythe_output_XXXXXXXXXXXXXXX)
|
||||
mkdir -p $KYTHE_OUTPUT_DIRECTORY
|
||||
KYTHE_OUTPUT_DIRECTORY=$KYTHE_OUTPUT_DIRECTORY $KYTHE_RELEASE_DIR/extractors/cxx_extractor "$@" & pid=$!
|
||||
$ORIG_CXX "$@"
|
||||
wait "$pid"
|
||||
|
||||
for X in $KYTHE_OUTPUT_DIRECTORY/*.kzip; do [[ -e $X ]] && mv "$X" $KYTHE_OUTPUT_DIRECTORY_ORIG/; done
|
||||
rm -r $KYTHE_OUTPUT_DIRECTORY
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import logging
|
||||
from buttercup.program_model.program_model import ProgramModel
|
||||
from buttercup.program_model.settings import (
|
||||
ProgramModelSettings,
|
||||
ProgramModelServeCommand,
|
||||
ProgramModelProcessCommand,
|
||||
)
|
||||
from buttercup.common.logger import setup_package_logger
|
||||
from pydantic_settings import get_subcommand
|
||||
from buttercup.common.datastructures.msg_pb2 import IndexRequest
|
||||
from redis import Redis
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def prepare_task(command: ProgramModelProcessCommand) -> IndexRequest:
|
||||
"""Prepares task for indexing."""
|
||||
|
||||
return IndexRequest(
|
||||
package_name=command.package_name,
|
||||
sanitizer=command.sanitizer,
|
||||
ossfuzz=command.ossfuzz,
|
||||
source_path=command.source_path,
|
||||
task_id=command.task_id,
|
||||
build_type=command.build_type,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
settings = ProgramModelSettings()
|
||||
setup_package_logger(__name__, settings.log_level)
|
||||
command = get_subcommand(settings)
|
||||
if isinstance(command, ProgramModelServeCommand):
|
||||
redis = Redis.from_url(command.redis_url, decode_responses=False)
|
||||
with ProgramModel(
|
||||
sleep_time=command.sleep_time,
|
||||
redis=redis,
|
||||
wdir=command.wdir,
|
||||
script_dir=command.script_dir,
|
||||
kythe_dir=command.kythe_dir,
|
||||
python=command.python,
|
||||
allow_pull=command.allow_pull,
|
||||
base_image_url=command.base_image_url,
|
||||
) as program_model:
|
||||
program_model.serve()
|
||||
elif isinstance(command, ProgramModelProcessCommand):
|
||||
task = prepare_task(command)
|
||||
with ProgramModel(
|
||||
wdir=command.wdir,
|
||||
script_dir=command.script_dir,
|
||||
kythe_dir=command.kythe_dir,
|
||||
python=command.python,
|
||||
allow_pull=command.allow_pull,
|
||||
base_image_url=command.base_image_url,
|
||||
) as program_model:
|
||||
program_model.process_task(task)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,4 @@
|
||||
"""Program Model."""
|
||||
|
||||
__version__ = "0.0.1"
|
||||
__module_name__ = "program_model"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: kythe/proto/storage.proto
|
||||
"""Generated protocol buffer code."""
|
||||
|
||||
from google.protobuf.internal import builder as _builder
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
@@ -12,45 +11,42 @@ from google.protobuf import symbol_database as _symbol_database
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
|
||||
b'\n\x19kythe/proto/storage.proto\x12\x0bkythe.proto"X\n\x05VName\x12\x11\n\tsignature\x18\x01 \x01(\t\x12\x0e\n\x06\x63orpus\x18\x02 \x01(\t\x12\x0c\n\x04root\x18\x03 \x01(\t\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x10\n\x08language\x18\x05 \x01(\t"\\\n\tVNameMask\x12\x11\n\tsignature\x18\x01 \x01(\x08\x12\x0e\n\x06\x63orpus\x18\x02 \x01(\x08\x12\x0c\n\x04root\x18\x03 \x01(\x08\x12\x0c\n\x04path\x18\x04 \x01(\x08\x12\x10\n\x08language\x18\x05 \x01(\x08"\x89\x01\n\x05\x45ntry\x12"\n\x06source\x18\x01 \x01(\x0b\x32\x12.kythe.proto.VName\x12\x11\n\tedge_kind\x18\x02 \x01(\t\x12"\n\x06target\x18\x03 \x01(\x0b\x32\x12.kythe.proto.VName\x12\x11\n\tfact_name\x18\x04 \x01(\t\x12\x12\n\nfact_value\x18\x05 \x01(\x0c".\n\x07\x45ntries\x12#\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x12.kythe.proto.Entry"D\n\x0bReadRequest\x12"\n\x06source\x18\x01 \x01(\x0b\x32\x12.kythe.proto.VName\x12\x11\n\tedge_kind\x18\x02 \x01(\t"\xcc\x01\n\x0cWriteRequest\x12"\n\x06source\x18\x01 \x01(\x0b\x32\x12.kythe.proto.VName\x12\x30\n\x06update\x18\x02 \x03(\x0b\x32 .kythe.proto.WriteRequest.Update\x1a\x66\n\x06Update\x12\x11\n\tedge_kind\x18\x01 \x01(\t\x12"\n\x06target\x18\x02 \x01(\x0b\x32\x12.kythe.proto.VName\x12\x11\n\tfact_name\x18\x03 \x01(\t\x12\x12\n\nfact_value\x18\x04 \x01(\x0c"\x0c\n\nWriteReply"Y\n\x0bScanRequest\x12"\n\x06target\x18\x01 \x01(\x0b\x32\x12.kythe.proto.VName\x12\x11\n\tedge_kind\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x63t_prefix\x18\x03 \x01(\t"-\n\x0c\x43ountRequest\x12\r\n\x05index\x18\x01 \x01(\x03\x12\x0e\n\x06shards\x18\x02 \x01(\x03"\x1d\n\nCountReply\x12\x0f\n\x07\x65ntries\x18\x01 \x01(\x03"-\n\x0cShardRequest\x12\r\n\x05index\x18\x01 \x01(\x03\x12\x0e\n\x06shards\x18\x02 \x01(\x03"N\n\x10VNameRewriteRule\x12\x0f\n\x07pattern\x18\x01 \x01(\t\x12)\n\x06v_name\x18\x02 \x01(\x0b\x32\x12.kythe.proto.VNameR\x05vname"@\n\x11VNameRewriteRules\x12+\n\x04rule\x18\x01 \x03(\x0b\x32\x1d.kythe.proto.VNameRewriteRuleBH\n\x1f\x63om.google.devtools.kythe.protoZ%kythe.io/kythe/proto/storage_go_protob\x06proto3'
|
||||
)
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19kythe/proto/storage.proto\x12\x0bkythe.proto\"X\n\x05VName\x12\x11\n\tsignature\x18\x01 \x01(\t\x12\x0e\n\x06\x63orpus\x18\x02 \x01(\t\x12\x0c\n\x04root\x18\x03 \x01(\t\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x10\n\x08language\x18\x05 \x01(\t\"\\\n\tVNameMask\x12\x11\n\tsignature\x18\x01 \x01(\x08\x12\x0e\n\x06\x63orpus\x18\x02 \x01(\x08\x12\x0c\n\x04root\x18\x03 \x01(\x08\x12\x0c\n\x04path\x18\x04 \x01(\x08\x12\x10\n\x08language\x18\x05 \x01(\x08\"\x89\x01\n\x05\x45ntry\x12\"\n\x06source\x18\x01 \x01(\x0b\x32\x12.kythe.proto.VName\x12\x11\n\tedge_kind\x18\x02 \x01(\t\x12\"\n\x06target\x18\x03 \x01(\x0b\x32\x12.kythe.proto.VName\x12\x11\n\tfact_name\x18\x04 \x01(\t\x12\x12\n\nfact_value\x18\x05 \x01(\x0c\".\n\x07\x45ntries\x12#\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x12.kythe.proto.Entry\"D\n\x0bReadRequest\x12\"\n\x06source\x18\x01 \x01(\x0b\x32\x12.kythe.proto.VName\x12\x11\n\tedge_kind\x18\x02 \x01(\t\"\xcc\x01\n\x0cWriteRequest\x12\"\n\x06source\x18\x01 \x01(\x0b\x32\x12.kythe.proto.VName\x12\x30\n\x06update\x18\x02 \x03(\x0b\x32 .kythe.proto.WriteRequest.Update\x1a\x66\n\x06Update\x12\x11\n\tedge_kind\x18\x01 \x01(\t\x12\"\n\x06target\x18\x02 \x01(\x0b\x32\x12.kythe.proto.VName\x12\x11\n\tfact_name\x18\x03 \x01(\t\x12\x12\n\nfact_value\x18\x04 \x01(\x0c\"\x0c\n\nWriteReply\"Y\n\x0bScanRequest\x12\"\n\x06target\x18\x01 \x01(\x0b\x32\x12.kythe.proto.VName\x12\x11\n\tedge_kind\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x63t_prefix\x18\x03 \x01(\t\"-\n\x0c\x43ountRequest\x12\r\n\x05index\x18\x01 \x01(\x03\x12\x0e\n\x06shards\x18\x02 \x01(\x03\"\x1d\n\nCountReply\x12\x0f\n\x07\x65ntries\x18\x01 \x01(\x03\"-\n\x0cShardRequest\x12\r\n\x05index\x18\x01 \x01(\x03\x12\x0e\n\x06shards\x18\x02 \x01(\x03\"N\n\x10VNameRewriteRule\x12\x0f\n\x07pattern\x18\x01 \x01(\t\x12)\n\x06v_name\x18\x02 \x01(\x0b\x32\x12.kythe.proto.VNameR\x05vname\"@\n\x11VNameRewriteRules\x12+\n\x04rule\x18\x01 \x03(\x0b\x32\x1d.kythe.proto.VNameRewriteRuleBH\n\x1f\x63om.google.devtools.kythe.protoZ%kythe.io/kythe/proto/storage_go_protob\x06proto3')
|
||||
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||
_builder.BuildTopDescriptorsAndMessages(
|
||||
DESCRIPTOR, "kythe.proto.storage_pb2", globals()
|
||||
)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kythe.proto.storage_pb2', globals())
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = (
|
||||
b"\n\037com.google.devtools.kythe.protoZ%kythe.io/kythe/proto/storage_go_proto"
|
||||
)
|
||||
_VNAME._serialized_start = 42
|
||||
_VNAME._serialized_end = 130
|
||||
_VNAMEMASK._serialized_start = 132
|
||||
_VNAMEMASK._serialized_end = 224
|
||||
_ENTRY._serialized_start = 227
|
||||
_ENTRY._serialized_end = 364
|
||||
_ENTRIES._serialized_start = 366
|
||||
_ENTRIES._serialized_end = 412
|
||||
_READREQUEST._serialized_start = 414
|
||||
_READREQUEST._serialized_end = 482
|
||||
_WRITEREQUEST._serialized_start = 485
|
||||
_WRITEREQUEST._serialized_end = 689
|
||||
_WRITEREQUEST_UPDATE._serialized_start = 587
|
||||
_WRITEREQUEST_UPDATE._serialized_end = 689
|
||||
_WRITEREPLY._serialized_start = 691
|
||||
_WRITEREPLY._serialized_end = 703
|
||||
_SCANREQUEST._serialized_start = 705
|
||||
_SCANREQUEST._serialized_end = 794
|
||||
_COUNTREQUEST._serialized_start = 796
|
||||
_COUNTREQUEST._serialized_end = 841
|
||||
_COUNTREPLY._serialized_start = 843
|
||||
_COUNTREPLY._serialized_end = 872
|
||||
_SHARDREQUEST._serialized_start = 874
|
||||
_SHARDREQUEST._serialized_end = 919
|
||||
_VNAMEREWRITERULE._serialized_start = 921
|
||||
_VNAMEREWRITERULE._serialized_end = 999
|
||||
_VNAMEREWRITERULES._serialized_start = 1001
|
||||
_VNAMEREWRITERULES._serialized_end = 1065
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\037com.google.devtools.kythe.protoZ%kythe.io/kythe/proto/storage_go_proto'
|
||||
_VNAME._serialized_start=42
|
||||
_VNAME._serialized_end=130
|
||||
_VNAMEMASK._serialized_start=132
|
||||
_VNAMEMASK._serialized_end=224
|
||||
_ENTRY._serialized_start=227
|
||||
_ENTRY._serialized_end=364
|
||||
_ENTRIES._serialized_start=366
|
||||
_ENTRIES._serialized_end=412
|
||||
_READREQUEST._serialized_start=414
|
||||
_READREQUEST._serialized_end=482
|
||||
_WRITEREQUEST._serialized_start=485
|
||||
_WRITEREQUEST._serialized_end=689
|
||||
_WRITEREQUEST_UPDATE._serialized_start=587
|
||||
_WRITEREQUEST_UPDATE._serialized_end=689
|
||||
_WRITEREPLY._serialized_start=691
|
||||
_WRITEREPLY._serialized_end=703
|
||||
_SCANREQUEST._serialized_start=705
|
||||
_SCANREQUEST._serialized_end=794
|
||||
_COUNTREQUEST._serialized_start=796
|
||||
_COUNTREQUEST._serialized_end=841
|
||||
_COUNTREPLY._serialized_start=843
|
||||
_COUNTREPLY._serialized_end=872
|
||||
_SHARDREQUEST._serialized_start=874
|
||||
_SHARDREQUEST._serialized_end=919
|
||||
_VNAMEREWRITERULE._serialized_start=921
|
||||
_VNAMEREWRITERULE._serialized_end=999
|
||||
_VNAMEREWRITERULES._serialized_start=1001
|
||||
_VNAMEREWRITERULES._serialized_end=1065
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
from google.protobuf.internal import containers as _containers
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
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
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
@@ -23,17 +17,13 @@ class CountRequest(_message.Message):
|
||||
SHARDS_FIELD_NUMBER: _ClassVar[int]
|
||||
index: int
|
||||
shards: int
|
||||
def __init__(
|
||||
self, index: _Optional[int] = ..., shards: _Optional[int] = ...
|
||||
) -> None: ...
|
||||
def __init__(self, index: _Optional[int] = ..., shards: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class Entries(_message.Message):
|
||||
__slots__ = ["entries"]
|
||||
ENTRIES_FIELD_NUMBER: _ClassVar[int]
|
||||
entries: _containers.RepeatedCompositeFieldContainer[Entry]
|
||||
def __init__(
|
||||
self, entries: _Optional[_Iterable[_Union[Entry, _Mapping]]] = ...
|
||||
) -> None: ...
|
||||
def __init__(self, entries: _Optional[_Iterable[_Union[Entry, _Mapping]]] = ...) -> None: ...
|
||||
|
||||
class Entry(_message.Message):
|
||||
__slots__ = ["edge_kind", "fact_name", "fact_value", "source", "target"]
|
||||
@@ -47,14 +37,7 @@ class Entry(_message.Message):
|
||||
fact_value: bytes
|
||||
source: VName
|
||||
target: VName
|
||||
def __init__(
|
||||
self,
|
||||
source: _Optional[_Union[VName, _Mapping]] = ...,
|
||||
edge_kind: _Optional[str] = ...,
|
||||
target: _Optional[_Union[VName, _Mapping]] = ...,
|
||||
fact_name: _Optional[str] = ...,
|
||||
fact_value: _Optional[bytes] = ...,
|
||||
) -> None: ...
|
||||
def __init__(self, source: _Optional[_Union[VName, _Mapping]] = ..., edge_kind: _Optional[str] = ..., target: _Optional[_Union[VName, _Mapping]] = ..., fact_name: _Optional[str] = ..., fact_value: _Optional[bytes] = ...) -> None: ...
|
||||
|
||||
class ReadRequest(_message.Message):
|
||||
__slots__ = ["edge_kind", "source"]
|
||||
@@ -62,11 +45,7 @@ class ReadRequest(_message.Message):
|
||||
SOURCE_FIELD_NUMBER: _ClassVar[int]
|
||||
edge_kind: str
|
||||
source: VName
|
||||
def __init__(
|
||||
self,
|
||||
source: _Optional[_Union[VName, _Mapping]] = ...,
|
||||
edge_kind: _Optional[str] = ...,
|
||||
) -> None: ...
|
||||
def __init__(self, source: _Optional[_Union[VName, _Mapping]] = ..., edge_kind: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class ScanRequest(_message.Message):
|
||||
__slots__ = ["edge_kind", "fact_prefix", "target"]
|
||||
@@ -76,12 +55,7 @@ class ScanRequest(_message.Message):
|
||||
edge_kind: str
|
||||
fact_prefix: str
|
||||
target: VName
|
||||
def __init__(
|
||||
self,
|
||||
target: _Optional[_Union[VName, _Mapping]] = ...,
|
||||
edge_kind: _Optional[str] = ...,
|
||||
fact_prefix: _Optional[str] = ...,
|
||||
) -> None: ...
|
||||
def __init__(self, target: _Optional[_Union[VName, _Mapping]] = ..., edge_kind: _Optional[str] = ..., fact_prefix: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class ShardRequest(_message.Message):
|
||||
__slots__ = ["index", "shards"]
|
||||
@@ -89,9 +63,7 @@ class ShardRequest(_message.Message):
|
||||
SHARDS_FIELD_NUMBER: _ClassVar[int]
|
||||
index: int
|
||||
shards: int
|
||||
def __init__(
|
||||
self, index: _Optional[int] = ..., shards: _Optional[int] = ...
|
||||
) -> None: ...
|
||||
def __init__(self, index: _Optional[int] = ..., shards: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class VName(_message.Message):
|
||||
__slots__ = ["corpus", "language", "path", "root", "signature"]
|
||||
@@ -105,14 +77,7 @@ class VName(_message.Message):
|
||||
path: str
|
||||
root: str
|
||||
signature: str
|
||||
def __init__(
|
||||
self,
|
||||
signature: _Optional[str] = ...,
|
||||
corpus: _Optional[str] = ...,
|
||||
root: _Optional[str] = ...,
|
||||
path: _Optional[str] = ...,
|
||||
language: _Optional[str] = ...,
|
||||
) -> None: ...
|
||||
def __init__(self, signature: _Optional[str] = ..., corpus: _Optional[str] = ..., root: _Optional[str] = ..., path: _Optional[str] = ..., language: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class VNameMask(_message.Message):
|
||||
__slots__ = ["corpus", "language", "path", "root", "signature"]
|
||||
@@ -126,14 +91,7 @@ class VNameMask(_message.Message):
|
||||
path: bool
|
||||
root: bool
|
||||
signature: bool
|
||||
def __init__(
|
||||
self,
|
||||
signature: bool = ...,
|
||||
corpus: bool = ...,
|
||||
root: bool = ...,
|
||||
path: bool = ...,
|
||||
language: bool = ...,
|
||||
) -> None: ...
|
||||
def __init__(self, signature: bool = ..., corpus: bool = ..., root: bool = ..., path: bool = ..., language: bool = ...) -> None: ...
|
||||
|
||||
class VNameRewriteRule(_message.Message):
|
||||
__slots__ = ["pattern", "v_name"]
|
||||
@@ -141,19 +99,13 @@ class VNameRewriteRule(_message.Message):
|
||||
V_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||
pattern: str
|
||||
v_name: VName
|
||||
def __init__(
|
||||
self,
|
||||
pattern: _Optional[str] = ...,
|
||||
v_name: _Optional[_Union[VName, _Mapping]] = ...,
|
||||
) -> None: ...
|
||||
def __init__(self, pattern: _Optional[str] = ..., v_name: _Optional[_Union[VName, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class VNameRewriteRules(_message.Message):
|
||||
__slots__ = ["rule"]
|
||||
RULE_FIELD_NUMBER: _ClassVar[int]
|
||||
rule: _containers.RepeatedCompositeFieldContainer[VNameRewriteRule]
|
||||
def __init__(
|
||||
self, rule: _Optional[_Iterable[_Union[VNameRewriteRule, _Mapping]]] = ...
|
||||
) -> None: ...
|
||||
def __init__(self, rule: _Optional[_Iterable[_Union[VNameRewriteRule, _Mapping]]] = ...) -> None: ...
|
||||
|
||||
class WriteReply(_message.Message):
|
||||
__slots__ = []
|
||||
@@ -171,20 +123,9 @@ class WriteRequest(_message.Message):
|
||||
fact_name: str
|
||||
fact_value: bytes
|
||||
target: VName
|
||||
def __init__(
|
||||
self,
|
||||
edge_kind: _Optional[str] = ...,
|
||||
target: _Optional[_Union[VName, _Mapping]] = ...,
|
||||
fact_name: _Optional[str] = ...,
|
||||
fact_value: _Optional[bytes] = ...,
|
||||
) -> None: ...
|
||||
|
||||
def __init__(self, edge_kind: _Optional[str] = ..., target: _Optional[_Union[VName, _Mapping]] = ..., fact_name: _Optional[str] = ..., fact_value: _Optional[bytes] = ...) -> None: ...
|
||||
SOURCE_FIELD_NUMBER: _ClassVar[int]
|
||||
UPDATE_FIELD_NUMBER: _ClassVar[int]
|
||||
source: VName
|
||||
update: _containers.RepeatedCompositeFieldContainer[WriteRequest.Update]
|
||||
def __init__(
|
||||
self,
|
||||
source: _Optional[_Union[VName, _Mapping]] = ...,
|
||||
update: _Optional[_Iterable[_Union[WriteRequest.Update, _Mapping]]] = ...,
|
||||
) -> None: ...
|
||||
def __init__(self, source: _Optional[_Union[VName, _Mapping]] = ..., update: _Optional[_Iterable[_Union[WriteRequest.Update, _Mapping]]] = ...) -> None: ...
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
import urllib.parse
|
||||
from xml.dom import minidom
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from io import BytesIO
|
||||
from typing import Generator, Dict, Set
|
||||
from buttercup.program_model.data.kythe.proto.storage_pb2 import Entry, VName
|
||||
from buttercup.program_model.utils.varint import decode_stream
|
||||
import base64
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True, repr=False)
|
||||
class KytheURI:
|
||||
"""Kythe entries described here: https://kythe.io/docs/kythe-storage.html#_entry"""
|
||||
|
||||
signature: str
|
||||
corpus: str
|
||||
root: str
|
||||
path: str
|
||||
language: str
|
||||
|
||||
def __str__(self):
|
||||
lst = [self.signature, self.corpus, self.root, self.path, self.language]
|
||||
uristr = "/".join([urllib.parse.quote(x, safe="") for x in lst])
|
||||
return uristr
|
||||
|
||||
@staticmethod
|
||||
def from_vname(vn: VName):
|
||||
return KytheURI(
|
||||
signature=vn.signature,
|
||||
corpus=vn.corpus,
|
||||
root=vn.root,
|
||||
path=vn.path,
|
||||
language=vn.language,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
"""Node in the graph."""
|
||||
|
||||
id: str
|
||||
property: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
def to_graphml(self) -> str:
|
||||
"""Convert node to a GraphML string."""
|
||||
content = []
|
||||
content.append(f'<node id="{self.id}">')
|
||||
for key, value in self.property.items():
|
||||
content.append(f'<data key="{key}">{value}</data>')
|
||||
content.append("</node>")
|
||||
return "".join(content)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Edge:
|
||||
"""Edge in the graph."""
|
||||
|
||||
id: str
|
||||
source_id: str
|
||||
target_id: str
|
||||
property: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
def to_graphml(self) -> str:
|
||||
"""Convert edge to a GraphML string."""
|
||||
content = []
|
||||
content.append(
|
||||
f'<edge id="{self.id}" source="{self.source_id}" target="{self.target_id}">'
|
||||
)
|
||||
for key, value in self.property.items():
|
||||
content.append(f'<data key="{key}">{value}</data>')
|
||||
content.append("</edge>")
|
||||
return "".join(content)
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class GraphStorage:
|
||||
"""Class to interact between Kythe and an output file."""
|
||||
|
||||
def __init__(self):
|
||||
self.nodes: Dict[str, Node] = {}
|
||||
self.node_properties: Set[str] = set()
|
||||
self.edges: Dict[str, Edge] = {}
|
||||
self.edge_properties: Set[str] = set()
|
||||
|
||||
def is_edge(self, ent: Entry) -> bool:
|
||||
"""Check if the entry is an edge."""
|
||||
return ent.edge_kind != ""
|
||||
|
||||
def encode_value(self, value: bytes) -> str:
|
||||
"""Encode a value in base64."""
|
||||
return base64.b64encode(value).decode()
|
||||
|
||||
def convert_node(self, nd: VName) -> Node:
|
||||
"""Converts a Kythe node to a GraphML node."""
|
||||
uri = str(KytheURI.from_vname(nd))
|
||||
if uri in self.nodes.keys():
|
||||
return self.nodes[uri]
|
||||
return Node(id=uri)
|
||||
|
||||
def process_stream(self, fl: BytesIO):
|
||||
"""Process a stream of Kythe entries and output them to a GraphML file."""
|
||||
|
||||
try:
|
||||
for count, entry in enumerate(self.iterate_over_entries(fl)):
|
||||
source_node = self.convert_node(entry.source)
|
||||
key = entry.fact_name
|
||||
value = self.encode_value(entry.fact_value)
|
||||
|
||||
if self.is_edge(entry):
|
||||
target_node = self.convert_node(entry.target)
|
||||
edge = Edge(
|
||||
id=len(self.edges.keys()),
|
||||
source_id=source_node.id,
|
||||
target_id=target_node.id,
|
||||
)
|
||||
edge.property["labelE"] = entry.edge_kind
|
||||
self.edge_properties.add("labelE")
|
||||
self.edges[edge.id] = edge
|
||||
else:
|
||||
source_node.property[key] = value
|
||||
self.node_properties.add(key)
|
||||
self.nodes[source_node.id] = source_node
|
||||
|
||||
# TODO(Evan): Remove this after diagnosing graph creation error
|
||||
if count == 1000:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error("Exception occurred: %s", e)
|
||||
raise e
|
||||
|
||||
def iterate_over_entries(self, fl: BytesIO) -> Generator[Entry, None, None]:
|
||||
"""Iterate over entries in the stream."""
|
||||
|
||||
# Indexers emit a delimited stream of entry protobufs
|
||||
# From: https://kythe.io/examples/#indexing-compilations
|
||||
while True:
|
||||
try:
|
||||
yield self.parse_entry(fl)
|
||||
# This is the end of the stream. This error is expected.
|
||||
except EOFError:
|
||||
break
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def parse_entry(self, fl: BytesIO) -> Entry:
|
||||
"""Parse a Kythe entry from the stream."""
|
||||
|
||||
# Read the size of the entry protobuf
|
||||
sz = decode_stream(fl)
|
||||
bts = bytes()
|
||||
while len(bts) < sz:
|
||||
rd = fl.read(sz - len(bts))
|
||||
if len(rd) <= 0:
|
||||
return None
|
||||
bts += rd
|
||||
ent = Entry()
|
||||
ent.ParseFromString(bts)
|
||||
return ent
|
||||
|
||||
def to_graphml(self) -> str:
|
||||
"""Convert graph to a GraphML file.
|
||||
From: https://tinkerpop.apache.org/docs/3.7.3/dev/io/
|
||||
"""
|
||||
content = '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
content += '<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.1/graphml.xsd">'
|
||||
content += '<graph id="G" edgedefault="directed">'
|
||||
|
||||
# Output node properties
|
||||
for p in self.node_properties:
|
||||
content += f'<key id="{p}" for="node" attr.name="{p}" attr.type="string" />'
|
||||
|
||||
# Output edge properties
|
||||
for p in self.edge_properties:
|
||||
content += f'<key id="{p}" for="edge" attr.name="{p}" attr.type="string" />'
|
||||
|
||||
# Output node contents
|
||||
for node in self.nodes.values():
|
||||
content += node.to_graphml()
|
||||
|
||||
# Output edge contents
|
||||
for edge in self.edges.values():
|
||||
content += edge.to_graphml()
|
||||
|
||||
content += "</graph>"
|
||||
content += "</graphml>"
|
||||
|
||||
xml_doc = minidom.parseString(content)
|
||||
pretty_xml = xml_doc.toprettyxml(indent=" ")
|
||||
return pretty_xml
|
||||
|
||||
|
||||
def main():
|
||||
prsr = argparse.ArgumentParser("Entry Output to GraphML")
|
||||
prsr.add_argument("--output", required=True)
|
||||
args = prsr.parse_args()
|
||||
|
||||
with open(args.output, "w") as fw, sys.stdin.buffer as fr:
|
||||
gs = GraphStorage()
|
||||
gs.process_stream(fr)
|
||||
fw.write(gs.to_graphml())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+10
-42
@@ -1,13 +1,15 @@
|
||||
import argparse
|
||||
import logging
|
||||
import uuid
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
from buttercup.common import oss_fuzz_tool
|
||||
from buttercup.common.oss_fuzz_tool import OSSFuzzTool
|
||||
from buttercup.program_model.indexer.entries_into_db import JanusStorage
|
||||
|
||||
from buttercup.program_model.kythe import KytheTool, KytheConf
|
||||
import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class IndexTarget:
|
||||
@@ -16,9 +18,8 @@ class IndexTarget:
|
||||
|
||||
|
||||
@dataclass
|
||||
class Conf:
|
||||
class IndexConf:
|
||||
scriptdir: str
|
||||
url: str
|
||||
python: str
|
||||
allow_pull: bool
|
||||
base_image_url: str
|
||||
@@ -26,7 +27,7 @@ class Conf:
|
||||
|
||||
|
||||
class Indexer:
|
||||
def __init__(self, conf: Conf):
|
||||
def __init__(self, conf: IndexConf):
|
||||
self.conf = conf
|
||||
|
||||
def build_image(self, idx_target: IndexTarget):
|
||||
@@ -39,7 +40,7 @@ class Indexer:
|
||||
)
|
||||
)
|
||||
base_image_name = fzz_tool.build_base_image(idx_target.package_name)
|
||||
print(base_image_name)
|
||||
logger.debug(f"Base image name: {base_image_name}")
|
||||
if base_image_name is None:
|
||||
return None
|
||||
|
||||
@@ -85,38 +86,9 @@ class Indexer:
|
||||
return output_dir
|
||||
|
||||
|
||||
@dataclass
|
||||
class KytheConf:
|
||||
kythe_dir: str
|
||||
|
||||
|
||||
class KytheTool:
|
||||
def __init__(self, conf: KytheConf):
|
||||
self.conf = conf
|
||||
|
||||
def merge_kythe_output(self, input_dir: str, output_kzip: str):
|
||||
merge_path = os.path.join(self.conf.kythe_dir, "tools/kzip")
|
||||
|
||||
total = []
|
||||
for fl in os.listdir(input_dir):
|
||||
if fl.endswith(".kzip"):
|
||||
total.append(os.path.join(input_dir, fl))
|
||||
|
||||
command = [merge_path, "merge", "--output", output_kzip] + total
|
||||
subprocess.run(command, check=True)
|
||||
return True
|
||||
|
||||
def cxx_index(self, input_kzip: str, output_bin: str):
|
||||
indexer_path = os.path.join(self.conf.kythe_dir, "indexers/cxx_indexer")
|
||||
command = [indexer_path, "-i", input_kzip, "-o", output_bin]
|
||||
subprocess.run(command, check=True)
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
prsr = argparse.ArgumentParser("oss fuzz builder")
|
||||
prsr.add_argument("--scriptdir", required=True)
|
||||
prsr.add_argument("--url", required=True)
|
||||
prsr.add_argument("--python", default="python")
|
||||
prsr.add_argument("--allow_pull", action="store_true", default=False)
|
||||
prsr.add_argument("--base_image_url", required=True)
|
||||
@@ -126,9 +98,8 @@ def main():
|
||||
prsr.add_argument("--kythe_dir", required=True)
|
||||
args = prsr.parse_args()
|
||||
|
||||
conf = Conf(
|
||||
conf = IndexConf(
|
||||
args.scriptdir,
|
||||
args.url,
|
||||
args.python,
|
||||
args.allow_pull,
|
||||
args.base_image_url,
|
||||
@@ -137,9 +108,9 @@ def main():
|
||||
indexer = Indexer(conf)
|
||||
output_dir = indexer.index_target(IndexTarget(args.oss_fuzz_dir, args.package_name))
|
||||
if output_dir is None:
|
||||
print("Failed to index target")
|
||||
logger.error("Failed to index target")
|
||||
return
|
||||
print(output_dir)
|
||||
logger.debug(f"Output dir: {output_dir}")
|
||||
output_id = str(uuid.uuid4())
|
||||
ktool = KytheTool(KytheConf(args.kythe_dir))
|
||||
merged_kzip = os.path.join(args.wdir, f"kythe_output_merge_{output_id}.kzip")
|
||||
@@ -147,9 +118,6 @@ def main():
|
||||
cxx_bin = os.path.join(args.wdir, f"kythe_output_cxx_{output_id}.bin")
|
||||
ktool.cxx_index(merged_kzip, cxx_bin)
|
||||
|
||||
with open(cxx_bin, "rb") as f:
|
||||
JanusStorage(args.url).process_stream(args.package_name, f)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,110 +0,0 @@
|
||||
from buttercup.program_model.data.kythe.proto.storage_pb2 import Entry, VName
|
||||
from buttercup.program_model.utils.varint import decode_stream
|
||||
from typing import Generator
|
||||
from io import BytesIO
|
||||
from gremlin_python.structure.graph import Graph, GraphTraversalSource
|
||||
from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection
|
||||
from dataclasses import dataclass
|
||||
import urllib
|
||||
from gremlin_python.structure.graph import Vertex
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
|
||||
@dataclass(frozen=True, repr=False)
|
||||
class KytheURI:
|
||||
signature: str
|
||||
corpus: str
|
||||
root: str
|
||||
path: str
|
||||
language: str
|
||||
|
||||
def __str__(self):
|
||||
lst = [self.signature, self.corpus, self.root, self.path, self.language]
|
||||
uristr = "/".join([urllib.parse.quote(x, safe="") for x in lst])
|
||||
return uristr
|
||||
|
||||
@staticmethod
|
||||
def from_vname(vn: VName):
|
||||
return KytheURI(
|
||||
signature=vn.signature,
|
||||
corpus=vn.corpus,
|
||||
root=vn.root,
|
||||
path=vn.path,
|
||||
language=vn.language,
|
||||
)
|
||||
|
||||
|
||||
def is_edge(ent: Entry) -> bool:
|
||||
return ent.edge_kind != ""
|
||||
|
||||
|
||||
def convert_node(trv: GraphTraversalSource, nd: VName) -> Vertex:
|
||||
ur = KytheURI.from_vname(nd)
|
||||
s = str(ur)
|
||||
print(s)
|
||||
maybe_nd = next(trv.V().has_label(s), None)
|
||||
if maybe_nd is not None:
|
||||
return maybe_nd
|
||||
|
||||
return trv.add_v(str(ur)).next()
|
||||
|
||||
|
||||
class JanusStorage:
|
||||
def __init__(self, url: str):
|
||||
self.connection = DriverRemoteConnection(url, traversal_source="g")
|
||||
self.graph = Graph()
|
||||
self.g = self.graph.traversal().with_remote(self.connection)
|
||||
next(self.g.V().has_label("x"), None)
|
||||
|
||||
def process_stream(self, _project_name: str, fl: BytesIO):
|
||||
next(self.g.V().has_label("x"), None)
|
||||
tx = self.g.tx()
|
||||
trv = tx.begin()
|
||||
for ent in iterate_over_entries(fl):
|
||||
next(trv.V().has_label("x"), None)
|
||||
if is_edge(ent):
|
||||
n1 = convert_node(trv, ent.source)
|
||||
n2 = convert_node(trv, ent.target)
|
||||
trv.add_e().from_(n1).to(n2).property(
|
||||
"edge_kind", ent.edge_kind
|
||||
).iterate()
|
||||
else:
|
||||
n1 = convert_node(trv, ent.source)
|
||||
trv.V().has_id(n1.id).property(
|
||||
ent.fact_name, ent.fact_value.decode()
|
||||
).iterate()
|
||||
tx.commit()
|
||||
|
||||
|
||||
def iterate_over_entries(fl: BytesIO) -> Generator[Entry, None, None]:
|
||||
try:
|
||||
sz = decode_stream(fl)
|
||||
bts = bytes()
|
||||
while len(bts) < sz:
|
||||
rd = fl.read(sz - len(bts))
|
||||
if len(rd) <= 0:
|
||||
return None
|
||||
bts += rd
|
||||
ent = Entry()
|
||||
ent.ParseFromString(bts)
|
||||
yield ent
|
||||
except EOFError:
|
||||
return None
|
||||
except Exception:
|
||||
# TODO(Ian) should catch decode errors
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
prsr = argparse.ArgumentParser("Entry Upload to Janus")
|
||||
prsr.add_argument("--url", required=True)
|
||||
args = prsr.parse_args()
|
||||
|
||||
storage = JanusStorage(args.url)
|
||||
with sys.stdin.buffer as f:
|
||||
storage.process_stream("", f)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,34 @@
|
||||
import subprocess
|
||||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class KytheConf:
|
||||
kythe_dir: str
|
||||
|
||||
|
||||
class KytheTool:
|
||||
def __init__(self, conf: KytheConf):
|
||||
self.conf = conf
|
||||
|
||||
def merge_kythe_output(self, input_dir: str, output_kzip: str):
|
||||
merge_path = os.path.join(self.conf.kythe_dir, "tools/kzip")
|
||||
|
||||
total = []
|
||||
for fl in os.listdir(input_dir):
|
||||
if fl.endswith(".kzip"):
|
||||
total.append(os.path.join(input_dir, fl))
|
||||
|
||||
command = [merge_path, "merge", "--output", output_kzip] + total
|
||||
subprocess.run(command, check=True)
|
||||
return True
|
||||
|
||||
def cxx_index(self, input_kzip: str, output_bin: str):
|
||||
indexer_path = os.path.join(self.conf.kythe_dir, "indexers/cxx_indexer")
|
||||
command = [indexer_path, input_kzip, "-o", output_bin]
|
||||
subprocess.run(command, check=True)
|
||||
return True
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Configure loggers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
import buttercup.program_model
|
||||
|
||||
MAIN_LOGGER_NAME = buttercup.program_model.__module_name__
|
||||
|
||||
|
||||
def logger_configurer(log_level: str | int) -> None:
|
||||
"""Configure main logger."""
|
||||
logger = logging.getLogger(MAIN_LOGGER_NAME)
|
||||
logger.setLevel(log_level)
|
||||
handler = logging.StreamHandler()
|
||||
handler.setLevel(log_level)
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
handler.setFormatter(formatter)
|
||||
if logger.hasHandlers():
|
||||
logger.handlers.clear()
|
||||
logger.addHandler(handler)
|
||||
logger.propagate = False
|
||||
@@ -0,0 +1,207 @@
|
||||
import logging
|
||||
import os
|
||||
import stat
|
||||
import shutil
|
||||
from dataclasses import dataclass, field
|
||||
from buttercup.common.queues import (
|
||||
RQItem,
|
||||
QueueFactory,
|
||||
ReliableQueue,
|
||||
QueueNames,
|
||||
GroupNames,
|
||||
)
|
||||
from buttercup.program_model.indexer import Indexer, IndexConf, IndexTarget
|
||||
from buttercup.program_model.kythe import KytheTool, KytheConf
|
||||
from buttercup.program_model.graph import GraphStorage
|
||||
from buttercup.common.datastructures.msg_pb2 import IndexRequest, IndexOutput
|
||||
from buttercup.common.challenge_task import ChallengeTask
|
||||
from pathlib import Path
|
||||
from redis import Redis
|
||||
import time
|
||||
import subprocess
|
||||
import uuid
|
||||
import tempfile
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProgramModel:
|
||||
sleep_time: float = 1.0
|
||||
redis: Redis | None = None
|
||||
task_queue: ReliableQueue | None = field(init=False, default=None)
|
||||
output_queue: ReliableQueue | None = field(init=False, default=None)
|
||||
wdir: Path | None = None
|
||||
script_dir: Path | None = None
|
||||
kythe_dir: Path | None = None
|
||||
python: str | None = None
|
||||
allow_pull: bool = True
|
||||
base_image_url: str = "gcr.io/oss-fuzz"
|
||||
|
||||
def __post_init__(self):
|
||||
"""Post-initialization setup."""
|
||||
self.wdir = Path(self.wdir).resolve()
|
||||
self.script_dir = Path(self.script_dir).resolve()
|
||||
self.kythe_dir = Path(self.kythe_dir).resolve()
|
||||
|
||||
if self.redis is not None:
|
||||
logger.debug("Using Redis for task queues")
|
||||
queue_factory = QueueFactory(self.redis)
|
||||
self.task_queue = queue_factory.create(QueueNames.INDEX, GroupNames.INDEX)
|
||||
self.output_queue = queue_factory.create(QueueNames.INDEX_OUTPUT)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup resources used by the program model"""
|
||||
pass
|
||||
|
||||
def process_task(self, args: IndexRequest) -> bool:
|
||||
"""Process a single task for indexing a program"""
|
||||
# Convert path strings to Path objects
|
||||
ossfuzz = Path(args.ossfuzz).resolve()
|
||||
source_path = Path(args.source_path).resolve()
|
||||
task_dir = source_path.parent.parent.resolve()
|
||||
|
||||
logger.debug(f"Kythe dir: {self.kythe_dir}")
|
||||
logger.debug(f"Script dir: {self.script_dir}")
|
||||
logger.debug(f"Wdir: {self.wdir}")
|
||||
logger.debug(f"Task dir: {task_dir}")
|
||||
logger.debug(f"Source dir: {source_path}")
|
||||
logger.debug(f"OSSFuzz dir: {ossfuzz}")
|
||||
logger.debug(f"Python: {self.python}")
|
||||
logger.debug(f"Allow pull: {self.allow_pull}")
|
||||
logger.debug(f"Base image URL: {self.base_image_url}")
|
||||
|
||||
with tempfile.TemporaryDirectory(dir=self.wdir) as td:
|
||||
logger.info(
|
||||
f"Running indexer for {args.package_name} | {source_path} | {args.task_id}"
|
||||
)
|
||||
|
||||
# Change permissions so that JanusGraph can read from the temporary directory
|
||||
current = os.stat(td).st_mode
|
||||
janus_user = stat.S_IRGRP | stat.S_IROTH | stat.S_IXGRP | stat.S_IXOTH
|
||||
os.chmod(td, current | janus_user)
|
||||
|
||||
tsk = ChallengeTask(
|
||||
read_only_task_dir=task_dir,
|
||||
project_name=args.package_name,
|
||||
python_path=self.python,
|
||||
)
|
||||
|
||||
with tsk.get_rw_copy(work_dir=td) as local_tsk:
|
||||
logger.debug(f"Local path: {local_tsk.local_task_dir}")
|
||||
|
||||
# Index the program
|
||||
indexer_conf = IndexConf(
|
||||
scriptdir=self.script_dir,
|
||||
python=self.python,
|
||||
allow_pull=self.allow_pull,
|
||||
base_image_url=self.base_image_url,
|
||||
wdir=td,
|
||||
)
|
||||
indexer = Indexer(indexer_conf)
|
||||
output_dir = indexer.index_target(
|
||||
IndexTarget(
|
||||
oss_fuzz_dir=ossfuzz,
|
||||
package_name=args.package_name,
|
||||
)
|
||||
)
|
||||
if output_dir is None:
|
||||
logger.error(f"Failed to index program {args.package_name}")
|
||||
return False
|
||||
else:
|
||||
logger.info(f"Successfully indexed program {args.package_name}")
|
||||
|
||||
# Merge index files
|
||||
output_id = str(uuid.uuid4())
|
||||
ktool = KytheTool(KytheConf(self.kythe_dir))
|
||||
merged_kzip = Path(td) / f"kythe_output_merge_{output_id}.kzip"
|
||||
ktool.merge_kythe_output(output_dir, merged_kzip)
|
||||
|
||||
# Convert the merged kzip file into a binary file
|
||||
bin_file = Path(td) / f"kythe_output_cxx_{output_id}.bin"
|
||||
try:
|
||||
ktool.cxx_index(merged_kzip, bin_file)
|
||||
logger.info(
|
||||
f"Successfully indexed program {args.package_name} to binary: {bin_file}"
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
# TODO(Evan): For now, if this errors just keep going
|
||||
logger.error(
|
||||
f"Failed to index program {args.package_name} to binary: {bin_file}"
|
||||
)
|
||||
|
||||
# Store the program into a graphml file
|
||||
graphml = Path(td) / f"kythe_output_graphml_{output_id}.xml"
|
||||
with open(graphml, "w") as fw, open(bin_file, "rb") as fr:
|
||||
gs = GraphStorage()
|
||||
gs.process_stream(fr)
|
||||
fw.write(gs.to_graphml())
|
||||
logger.info(
|
||||
f"Successfully stored program {args.package_name} in graphml file: {graphml}"
|
||||
)
|
||||
|
||||
# Copy graphml file
|
||||
graphml_final = Path(td) / "graph.xml"
|
||||
shutil.copy2(graphml, graphml_final)
|
||||
|
||||
logger.debug("Loading graphml file into JanusGraph...")
|
||||
|
||||
# TODO(Evan): This needs to wait until JanusGraph is ready. For some reason, even if the container is running and healthy, it's not ready to accept connections.
|
||||
|
||||
# Load graphml file into JanusGraph
|
||||
from gremlin_python.process.anonymous_traversal import traversal
|
||||
from gremlin_python.driver.driver_remote_connection import (
|
||||
DriverRemoteConnection,
|
||||
)
|
||||
|
||||
g = traversal().withRemote(
|
||||
DriverRemoteConnection("ws://graphdb:8182/gremlin", "g")
|
||||
)
|
||||
g.io(str(graphml_final)).read().iterate()
|
||||
|
||||
logger.debug("Successfully loaded graphml file into JanusGraph")
|
||||
|
||||
return True
|
||||
|
||||
def serve(self):
|
||||
"""Main loop to process tasks from queue"""
|
||||
if self.task_queue is None:
|
||||
raise ValueError("Task queue is not initialized")
|
||||
|
||||
if self.output_queue is None:
|
||||
raise ValueError("Output queue is not initialized")
|
||||
|
||||
logger.info("Starting downloader service")
|
||||
|
||||
while True:
|
||||
rq_item: Optional[RQItem] = self.task_queue.pop()
|
||||
|
||||
if rq_item is not None:
|
||||
task_index: IndexRequest = rq_item.deserialized
|
||||
success = self.process_task(task_index)
|
||||
|
||||
if success:
|
||||
self.output_queue.push(
|
||||
IndexOutput(
|
||||
package_name=task_index.package_name,
|
||||
sanitizer=task_index.sanitizer,
|
||||
ossfuzz=task_index.ossfuzz,
|
||||
source_path=task_index.source_path,
|
||||
task_id=task_index.task_id,
|
||||
build_type=task_index.build_type,
|
||||
)
|
||||
)
|
||||
self.task_queue.ack_item(rq_item.item_id)
|
||||
logger.info(f"Successfully processed task {task_index.task_id}")
|
||||
else:
|
||||
logger.error(f"Failed to process task {task_index.task_id}")
|
||||
|
||||
logger.info(f"Sleeping for {self.sleep_time} seconds")
|
||||
time.sleep(self.sleep_time)
|
||||
@@ -0,0 +1,63 @@
|
||||
from pydantic_settings import BaseSettings, CliSubCommand
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Annotated
|
||||
|
||||
|
||||
class Config:
|
||||
cli_parse_args = True
|
||||
nested_model_default_partial_update = True
|
||||
env_nested_delimiter = "__"
|
||||
extra = "allow"
|
||||
env_prefix = "BUTTERCUP_PROGRAM_MODEL_"
|
||||
|
||||
|
||||
class ButtercupBaseSettings(BaseSettings):
|
||||
class Config:
|
||||
cli_parse_args = True
|
||||
nested_model_default_partial_update = True
|
||||
env_nested_delimiter = "__"
|
||||
extra = "allow"
|
||||
|
||||
|
||||
class BuilderSettings(BaseModel):
|
||||
allow_pull: Annotated[bool, Field(default=True, description="Allow pull")]
|
||||
base_image_url: Annotated[
|
||||
str, Field(default="gcr.io/oss-fuzz", description="Base image URL")
|
||||
]
|
||||
|
||||
|
||||
class WorkerSettings(BaseModel):
|
||||
redis_url: Annotated[
|
||||
str, Field(default="redis://127.0.0.1:6379", description="Redis URL")
|
||||
]
|
||||
sleep_time: Annotated[
|
||||
float, Field(default=1.0, description="Sleep time between checks in seconds")
|
||||
]
|
||||
wdir: Annotated[str, Field(default=..., description="Working directory")]
|
||||
python: Annotated[str, Field(default="python", description="Python path")]
|
||||
|
||||
|
||||
class IndexerSettings(BaseModel):
|
||||
kythe_dir: Annotated[
|
||||
str, Field(default="scripts/gzs/kythe", description="Kythe directory")
|
||||
]
|
||||
script_dir: Annotated[str, Field(default="scripts", description="Script directory")]
|
||||
|
||||
|
||||
class ProgramModelServeCommand(WorkerSettings, IndexerSettings, BuilderSettings):
|
||||
pass
|
||||
|
||||
|
||||
class ProgramModelProcessCommand(WorkerSettings, IndexerSettings, BuilderSettings):
|
||||
package_name: Annotated[str, Field(description="Package name", default=...)]
|
||||
sanitizer: Annotated[str, Field(description="Sanitizer", default=...)]
|
||||
ossfuzz: Annotated[str, Field(description="OSSFuzz directory", default=...)]
|
||||
source_path: Annotated[str, Field(description="Source path", default=...)]
|
||||
task_id: Annotated[str, Field(description="Task ID", default=...)]
|
||||
build_type: Annotated[str, Field(description="Build type", default=...)]
|
||||
|
||||
|
||||
class ProgramModelSettings(ButtercupBaseSettings):
|
||||
log_level: Annotated[str, Field(default="info", description="Log level")]
|
||||
serve: CliSubCommand[ProgramModelServeCommand]
|
||||
process: CliSubCommand[ProgramModelProcessCommand]
|
||||
Generated
+1340
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user