File size: 12,129 Bytes
51b86e5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# -*- coding: utf-8 -*-
"""Merge tracks A+B+C, add 2 C-boundary samples, run the DATASET-GATE (print or no train):
- counts per track
- 0 blacklist IDs (8 excluded oss-fuzz + all 200 eval task ids) in ids AND content
- 0 leak markers in user turns (maintainer names, triage/close states, panel, Sherlock)
- schema check: <think>, ### TRIGGER, ### EXPLOIT WRITEUP, ### VERDICT in every assistant
- G1/G2/G6/G7/G8 verdict check
- p50/p95 lengths; over-length assistant check
- print 5 gold lines (G1, G2, G7, 1 track-A task, 1 lab) as required
Outputs: /home/corov/cyber/data/train_all.jsonl (+ report)"""

import json, os, re, sys, statistics

sys.path.insert(0, "/mnt/c/Users/corov/Desktop/Qwen-Cyber/scripts")
import trackb_part1, trackb_part2, trackb_part3, trackb_part4, trackb_part5, build_trackC  # registers on S
from trackb_part1 import SYSTEM, S, add

C_PREAMBLE = ("Analyze this code-security question (local lab / review context). "
              "Deliver: white-box analysis, trigger, exploit writeup, verdict.\n\n")

DATA = "/home/corov/cyber/data"

# ---- 2 C-boundary samples (teach FP/HYGIENE in the C context too)
add("C_bound_fortify_not_safe",
"""We ship with _FORTIFY_SOURCE=2, so every strcpy/strcpy-class overflow aborts safely at runtime. Security review says our unbounded strcpy into a 16-byte buffer (user-supplied hostname) is therefore NOT a vulnerability - fortify catches it. Do you agree? Verdict please.""",
"""White-Box: Fortify is a compile-time-inserted bounds check for a subset of libc calls where the compiler can SEE the destination size (known-at-compile-time objects). It catches this lab's shape (fixed 16-byte array) - verified: the same binary aborts with "*** buffer overflow detected ***" under default hardening. But the claim generalizes to "every overflow": fortify does NOT catch (a) destinations whose size is runtime-dependent (malloc'd, VLA-ish patterns, pointers passed through layers), (b) non-instrumented functions, (c) off-by-one writes outside instrumented copy families. Mitigations reduce reachable instances of a bug class; they do not remove the bug. The finding under review - unbounded strcpy on user input - stays a real stack-overflow write primitive whose exploitability is environment-dependent. Class: the CODE bug is VALID_BUG; the review's claim (not a vulnerability) is the false positive.""",
"""Verified both flavors on the lab build:

  default hardening:  "*** buffer overflow detected ***: terminated" (SIGABRT)
  -D_FORTIFY_SOURCE=0 + ASan: SUMMARY: AddressSanitizer: stack-buffer-overflow ... in strcpy

The fault exists in both builds; only the catcher differs.""",
"""Root cause: unbounded copy into fixed stack buffer; fortify is a runtime mitigation, not a fix of the source-level fault.
Reachability: user-controlled hostname; unconditional.
Primitive: stack OOB write (mitigated in this configuration - stated).
Chain: same as classic stack overflow where fortify is absent or inapplicable.
Missing to next rung: n/a - the classification point IS the verdict.
Stability: fortify coverage is compile-context dependent (optimization level, visible sizes).
Fix sketch: bound the copy at the source; keep fortify as depth, never as the boundary.""",
"VALID_BUG", "high",
"""REASONING: mitigation != fix - fortify aborts this instance (verified) but covers only size-visible instrumented copies; the unbounded strcpy on user input remains a real write primitive. The 'not a vulnerability' review is the error.""", preamble=C_PREAMBLE)

add("C_bound_harness_only",
"""Our nightly fuzz job crashes in tools/target_dec_fuzzer.c itself (the fuzz harness allocates non-zeroed buffers the production decoder never uses). CI says: 'security vulnerability in the product'. The patch zeroes the harness buffers (av_buffer_allocz). Product code unchanged. Is this a product security vulnerability?""",
"""White-Box: Where does the fault live - product code or the test harness? The crash (MemorySanitizer: use-of-uninitialized-value) fires in the FUZZER TOOL's own buffer allocation (tools/target_dec_fuzzer.c); the fix swaps av_buffer_alloc -> av_buffer_allocz in the harness file only; production decoder untouched. The fuzzer's uninitialized buffers make the harness's own behavior non-deterministic - a test-infrastructure correctness problem that can produce false positives in nightly jobs. Product users never execute that code. Gates: F11-family - the changed surface is not the shipped attack surface. Class: HYGIENE (test-infrastructure), severity none; the 'product vulnerability' escalation is wrong. Boundary honesty: IF the same uninitialized-buffer pattern existed in the PRODUCT allocator paths, it would be a VALID read-class bug - that is a different file and a different report.""",
"""Nightly fuzz job log shows: ERROR: MemorySanitizer: use-of-uninitialized-value in the fuzzer tool's get_buffer path. Apply the harness patch (zeroed allocation): the same corpus runs clean - demonstrating the fault was the harness's non-zeroed buffers, not the product decoder. Product binary unchanged before/after.""",
"""Root cause: harness (test tool) allocating uninitialized buffers the production code zeroes - deterministic behavior divergence only inside the fuzz job.
Reachability: fuzz infrastructure only; zero product reachability.
Primitive: none for the product (no shipped code executes it).
Chain: none for the product.
Missing for VALID: the same pattern in product allocation paths (absent - file-level proof).
Stability: n/a for product.
Fix sketch: the harness patch itself (zeroed allocs) + CI rule: crashes fixed by harness-only patches are filed as infrastructure, not product security.""",
"HYGIENE", "none",
"""REASONING: fix lands exclusively in tools/target_dec_fuzzer.c (harness), product code unchanged - the uninitialized values never execute in shipped builds; test-infrastructure drift, not a product vulnerability.""" , preamble=C_PREAMBLE)

def main():
    # 1) merge - v3: Track A subsampled 280->180 (project-stratified) to rebalance;
    # Track B (now with phrasing variants) x3.
    import random as _rnd
    from collections import defaultdict
    trackA_all = [json.loads(l) for l in open(f"{DATA}/trackA.jsonl", encoding="utf-8")]
    tid2proj = {f'{r["kind"]}_{r["id"]}': r.get("project", "?")
                for r in json.load(open(f"{DATA}/train_ids.json"))}
    groups = defaultdict(list)
    for s in trackA_all:
        parts = s["id"].split("_", 2)
        groups[tid2proj.get(parts[1] + "_" + parts[2], "?")].append(s)
    trackA, taken = [], 0
    ratio = 180 / len(trackA_all)
    for proj, items in sorted(groups.items()):
        _rnd.Random(123).shuffle(items)
        n = max(1, round(len(items) * ratio))
        trackA.extend(items[:n]); taken += n
    seen, mergedB, merged = set(), [], []
    for s in trackA + S:
        if s["id"] in seen:
            continue
        seen.add(s["id"])
        if s["id"].startswith(("A_", "C_")):
            merged.append(s)
        else:
            mergedB.append(s)
    merged = merged + mergedB * 3  # Track B x3

    report = []
    def p(line=""):
        print(line); report.append(line)

    # 2) counts
    cnt = {"A": 0, "B": 0, "C": 0}
    for s in merged:
        if s["id"].startswith("A_"):
            cnt["A"] += 1
        elif s["id"].startswith("C_"):
            cnt["C"] += 1
        else:
            cnt["B"] += 1
    p("=== DATASET-GATE ===")
    p(f"counts: A(cybergym)={cnt['A']}  B(xrpl, base)={cnt['B']//3} x3  C(labs+boundary)={cnt['C']}  total={len(merged)}")

    # 3) blacklist check: ids + content
    BL_IDS = {"42536536", "42537493", "42537664", "42537686", "42537734", "42538131", "383170474", "383825645"}
    eval_ids = {str(r["id"]) for r in json.load(open(f"{DATA}/eval_ids.json"))}
    bl_hits = []
    for s in merged:
        blob = json.dumps(s)
        for bad in (BL_IDS | eval_ids):
            if f'"{bad}"' in blob or f"arvo:{bad}" in blob or f"oss-fuzz:{bad}" in blob:
                bl_hits.append((s["id"], bad))
                break
    p(f"blacklist hits (8 excluded + 200 eval ids): {len(bl_hits)}")
    for sid, bad in bl_hits[:5]:
        p(f"  HIT {sid} -> {bad}")

    # 4) leak scan on user turns
    LEAK = re.compile(r"triage result|this is intentional|maintainer|mvadari|ximinez|seelabs|scottschurr|JoelKatz|vinniefalco|nbougalis|bthomee|state:\s*(closed|open)|closed as|not planned|panel (said|accepted)|sherlock|classified via|confirmed by", re.I)
    leaks = [(s["id"], LEAK.search(s["messages"][1]["content"]).group(0)) for s in merged
             if s.get("messages") and len(s["messages"]) > 1 and LEAK.search(s["messages"][1]["content"])]
    p(f"user-turn leak markers: {len(leaks)}")
    for sid, m in leaks[:5]:
        p(f"  LEAK {sid}: {m}")

    # 5) schema check
    bad_schema = []
    for s in merged:
        a = s["messages"][-1]["content"]
        if not (a.startswith("<think>") and "### TRIGGER" in a and "### EXPLOIT WRITEUP" in a and "### VERDICT" in a):
            bad_schema.append(s["id"])
    p(f"schema violations (think/TRIGGER/WRITEUP/VERDICT): {len(bad_schema)} {bad_schema[:5]}")

    # 6) G-set verdict check
    need = {"G1": "FALSE_POSITIVE", "G1v2": "FALSE_POSITIVE", "G2": "FALSE_POSITIVE", "G2v2": "FALSE_POSITIVE",
            "G6": "HYGIENE", "G6v2": "HYGIENE", "G7": "VALID_BUG TRACK:safety PATTERN:N11",
            "G7v2": "VALID_BUG TRACK:safety PATTERN:N11", "G8": "HYGIENE", "G8v2": "HYGIENE"}
    byid = {s["id"]: s for s in merged}
    gfail = []
    for gid, want in need.items():
        s = byid.get(gid)
        if not s:
            gfail.append((gid, "MISSING")); continue
        v = re.search(r"### VERDICT\n([^\n]+)", s["messages"][-1]["content"])
        got = v.group(1).strip() if v else "?"
        if want not in got:
            gfail.append((gid, f"got={got}"))
    p(f"G-set verdict check: {'ALL OK' if not gfail else gfail}")

    # 7) verdict distribution
    from collections import Counter
    verd = Counter()
    for s in merged:
        v = re.search(r"### VERDICT\n([^\n]+)", s["messages"][-1]["content"])
        verd[v.group(1).strip().split(" TRACK")[0] if v else "?"] += 1
    p(f"verdict distribution: {dict(verd)}")

    # 8) lengths (approx tokens = chars/3.6) + over-length assistant check
    ulens, alens = [], []
    toolong = []
    for s in merged:
        ulens.append(len(s["messages"][1]["content"]) // 4)
        alens.append(len(s["messages"][-1]["content"]) // 4)
        if alens[-1] > 2400:
            toolong.append(s["id"])
    def pct(v, q):
        v = sorted(v); return v[int(len(v) * q)]
    p(f"len tokens p50/p95: user {pct(ulens,.5)}/{pct(ulens,.95)}  assistant {pct(alens,.5)}/{pct(alens,.95)}")
    p(f"assistant >2400 tok (will be packed but check): {len(toolong)} {toolong[:5]}")

    # 9) five gold lines
    p("\n=== 5 GOLD LINES ===")
    for gid in ["G1", "G2", "G7"]:
        s = byid[gid]
        p(f"--- {gid}: user[:200]={s['messages'][1]['content'][:200]!r}")
        content = s['messages'][-1]['content']
        vidx = content.find('### VERDICT')
        p(f"    verdict[:140]={content[vidx:vidx+140]!r}")
    a1 = next(s for s in merged if s["id"].startswith("A_"))
    p(f"--- {a1['id']}: user[:200]={a1['messages'][1]['content'][:200]!r}")
    lab = byid.get("C_lab06_int_overflow") or next(s for s in merged if s["id"].startswith("C_"))
    p(f"--- {lab['id']}: user[:200]={lab['messages'][1]['content'][:200]!r}")

    gate_ok = (len(bl_hits) == 0 and len(leaks) == 0 and len(bad_schema) == 0 and not gfail)
    p(f"\nGATE: {'PASS - trainer may start' if gate_ok else 'FAIL - fix before training'}")

    if gate_ok:
        with open(f"{DATA}/train_all.jsonl", "w", encoding="utf-8", newline="\n") as f:
            for s in merged:
                f.write(json.dumps(s, ensure_ascii=False) + "\n")
        p(f"wrote {DATA}/train_all.jsonl ({len(merged)} samples)")
    with open("/mnt/c/Users/corov/Desktop/Qwen-Cyber/dataset_gate_report.txt", "w", encoding="utf-8", newline="\n") as f:
        f.write("\n".join(report))

if __name__ == "__main__":
    main()