Source code for curate_ipsum.verification.harness.builder
"""
Harness builder: generates minimal C harness binaries from function specifications.
Compiles with ``gcc -O0 -g -fno-inline`` to preserve symbol information
for angr symbolic execution.
Decision: D-016
"""
from __future__ import annotations
import logging
import subprocess
import tempfile
from dataclasses import dataclass, field
from pathlib import Path
LOG = logging.getLogger("verification.harness.builder")
[docs]
@dataclass
class HarnessSpec:
"""Specification for generating a C harness."""
function_name: str
parameters: list[dict[str, str]] = field(default_factory=list)
# Each param: {"name": "x", "type": "int", "bits": "32"}
violation_condition: str = "" # C expression for the violation
includes: list[str] = field(default_factory=list)
extra_code: str = ""
_HARNESS_TEMPLATE = """\
/* Auto-generated harness for verification */
{includes}
void violation(void) {{
/* Marker function — angr searches for this address */
return;
}}
{extra_code}
void {function_name}({params}) {{
if ({violation_condition}) {{
violation();
}}
}}
int main(void) {{
{function_name}({call_args});
return 0;
}}
"""
[docs]
class HarnessBuilder:
"""
Builds C harness binaries from specifications.
Usage::
builder = HarnessBuilder()
binary_path = builder.build(spec, output_dir="/tmp/harnesses")
"""
def __init__(self, cc: str = "gcc", cflags: list[str] | None = None) -> None:
self._cc = cc
self._cflags = cflags or ["-O0", "-g", "-fno-inline", "-fno-stack-protector"]
[docs]
def generate_source(self, spec: HarnessSpec) -> str:
"""Generate C source code for the harness."""
includes = "\n".join(f"#include <{h}>" for h in (spec.includes or ["stdio.h"]))
params = ", ".join(f"{p.get('type', 'int')} {p['name']}" for p in spec.parameters) or "void"
call_args = ", ".join("0" for _ in spec.parameters)
violation = spec.violation_condition or "0"
return _HARNESS_TEMPLATE.format(
includes=includes,
extra_code=spec.extra_code or "",
function_name=spec.function_name,
params=params,
call_args=call_args,
violation_condition=violation,
)
[docs]
def build(
self,
spec: HarnessSpec,
output_dir: str | None = None,
) -> str:
"""
Generate and compile a harness binary.
Returns the path to the compiled binary.
"""
if output_dir is None:
output_dir = tempfile.mkdtemp(prefix="harness_")
out_path = Path(output_dir)
out_path.mkdir(parents=True, exist_ok=True)
src_path = out_path / f"{spec.function_name}_harness.c"
bin_path = out_path / f"{spec.function_name}_harness"
source = self.generate_source(spec)
src_path.write_text(source, encoding="utf-8")
cmd = [self._cc] + self._cflags + [str(src_path), "-o", str(bin_path)]
LOG.info("Compiling harness: %s", " ".join(cmd))
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode != 0:
raise RuntimeError(f"Harness compilation failed (exit {result.returncode}):\n{result.stderr}")
return str(bin_path)
[docs]
def build_from_spec(
self,
function_name: str,
params: list[dict[str, str]],
violation: str,
output_dir: str | None = None,
) -> str:
"""Convenience: build a harness from individual arguments."""
spec = HarnessSpec(
function_name=function_name,
parameters=params,
violation_condition=violation,
)
return self.build(spec, output_dir)