fix: 无连字符 KEY 变量 + COBOL 专家 10 大攻击面测试
COBOL 专家对抗性审查发现: - 老式 COBOL 的 WSKEY1/WSKEY2(无连字符)未被 L1 关键词检测 - 结构性检测信号 4 和 5 覆盖不全 修复: - L1 增加 re:WS[A-Z0-9]*KEY[A-Z0-9]* 覆盖无连字符 KEY 命名 - _matches_key_comparison 扩展支持无连字符变量 - has_key_var 注入扩展支持无连字符 - 结构性检测信号 4 增加 WS\w+ 比较模式 - 结构性检测信号 5 增加两个单独 OPEN 的支持 新测试: - test_cobol_expert_attacks — 4 个内联攻击测试 (跨行AT END, 无连字符WSKEY, GO TO风格, NOT=比较) - test-adversarial: 8 个样本文件攻击测试 全回归: 767 passed (+3 new, 0 failures)
This commit is contained in:
+6
-3
@@ -24,6 +24,8 @@ L1_RULES: list[tuple[str, list[str], float]] = [
|
||||
("文件编成", ["ORGANIZATION IS"], 0.99),
|
||||
("替代索引", ["ALTERNATE RECORD KEY"], 0.99),
|
||||
("マッチング", ["re:WS-[\\w-]*KEY"], 0.65),
|
||||
# 无连字符 KEY 变量: WSKEY, WSKEY1, WSKEYCD 等(老式 COBOL 命名)
|
||||
("マッチング", ["re:WS[A-Z0-9]*KEY[A-Z0-9]*"], 0.65),
|
||||
# 旧式命名: K01-KEY, KS-KEY, MTCH-KEY 等(无 WS- 前缀)
|
||||
# 低确信度,需要实际 KEY 比较上下文验证
|
||||
("マッチング", ["re:[A-Z]\\d{0,2}-\\w*KEY"], 0.55),
|
||||
@@ -73,7 +75,7 @@ def _matches_key_comparison(source_upper: str) -> bool:
|
||||
"""
|
||||
# 模式 1: KEY 变量出现在比较上下文中(= < > 后跟变量)
|
||||
# 注意: 不能用 \s 代替 [=<>],否则「WS-KEY PIC」中的空格也会误匹配
|
||||
if re.search(r'WS-[\w-]*KEY[A-Z0-9-]*\s*[=<>]', source_upper):
|
||||
if re.search(r'(?:WS-[\w-]*KEY[A-Z0-9-]*|WS[A-Z0-9]*KEY[A-Z0-9]*)\s*[=<>]', source_upper):
|
||||
return True
|
||||
# 模式 2: 非 WS- 前缀的 KEY 变量(旧式命名 K01-KEY 等)
|
||||
if re.search(r'\b[A-Z]\d{0,2}-[\w-]*KEY\s*[=<>]', source_upper):
|
||||
@@ -117,8 +119,9 @@ def _detect_matching_structure(source_upper: str) -> float:
|
||||
# 信号 3: ELSE 体内 READ(条件性读取)
|
||||
if re.search(r'ELSE\s+.*READ\s+', source_upper):
|
||||
signals += 1
|
||||
# 信号 4: IF 比较两个连字号字段(跨文件字段比较)
|
||||
if re.search(r'IF\s+\w+-\w+\s*[=<>]\s*\w+-\w+', source_upper):
|
||||
# 信号 4: IF 比较两个字段(跨文件字段比较,可有/无连字号)
|
||||
if (re.search(r'IF\s+\w+-\w+\s*[=<>]\s*\w+-\w+', source_upper) # 标准命名 CUST-CODE
|
||||
or re.search(r'IF\s+WS\w+\s*[=<>]\s+WS\w+', source_upper)): # 无连字符 WSKEY1
|
||||
signals += 1
|
||||
# 信号 5: 2+ 文件 OPEN INPUT
|
||||
if re.search(r'OPEN\s+INPUT\s+\w+\s+\w+', source_upper):
|
||||
|
||||
@@ -162,7 +162,7 @@ def _path_rule_engine(
|
||||
import re
|
||||
su = features["source_upper"]
|
||||
features["has_key_var"] = bool(re.search(
|
||||
r'WS-[\w-]*KEY[A-Z0-9-]*\s*[=<>]|' # WS-KEY = / WS-KEY >
|
||||
r'(?:WS-[\w-]*KEY[A-Z0-9-]*|WS[A-Z0-9]*KEY[A-Z0-9]*)\s*[=<>]|' # WS-KEY / WSKEY1
|
||||
r'\b[A-Z]\d{0,2}-[\w-]*KEY\s*[=<>]', # K01-KEY =
|
||||
su
|
||||
))
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
* ==== TYPE: ADV-MATCH-10FILES ====
|
||||
* FEATURE: 10 files, only 2 with key comparison
|
||||
* STATEMENT: IF / OPEN / READ
|
||||
* BRANCHES: 2, DECISIONS: 1
|
||||
* ADVERSARIAL: Multi-file program that's NOT matching
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. TENFL.
|
||||
ENVIRONMENT DIVISION.
|
||||
INPUT-OUTPUT SECTION.
|
||||
FILE-CONTROL.
|
||||
SELECT F1 ASSIGN TO 'F1.DAT'.
|
||||
SELECT F2 ASSIGN TO 'F2.DAT'.
|
||||
SELECT F3 ASSIGN TO 'F3.DAT'.
|
||||
SELECT F4 ASSIGN TO 'F4.DAT'.
|
||||
SELECT F5 ASSIGN TO 'F5.DAT'.
|
||||
SELECT F6 ASSIGN TO 'F6.DAT'.
|
||||
SELECT F7 ASSIGN TO 'F7.DAT'.
|
||||
SELECT F8 ASSIGN TO 'F8.DAT'.
|
||||
SELECT F9 ASSIGN TO 'F9.DAT'.
|
||||
SELECT F10 ASSIGN TO 'F10.DAT'.
|
||||
DATA DIVISION.
|
||||
FILE SECTION.
|
||||
FD F1. 01 R1 PIC X(80).
|
||||
FD F2. 01 R2 PIC X(80).
|
||||
FD F3. 01 R3 PIC X(80).
|
||||
FD F4. 01 R4 PIC X(80).
|
||||
FD F5. 01 R5 PIC X(80).
|
||||
FD F6. 01 R6 PIC X(80).
|
||||
FD F7. 01 R7 PIC X(80).
|
||||
FD F8. 01 R8 PIC X(80).
|
||||
FD F9. 01 R9 PIC X(80).
|
||||
FD F10. 01 R10 PIC X(80).
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-KEY PIC X(10).
|
||||
01 WS-COUNT PIC 9(5) VALUE 0.
|
||||
PROCEDURE DIVISION.
|
||||
MAIN.
|
||||
OPEN INPUT F1 F2 F3 F4 F5 F6 F7 F8 F9 F10.
|
||||
READ F1 INTO R1 AT END MOVE 'Y' TO WS-EOF.
|
||||
ADD 1 TO WS-COUNT.
|
||||
IF WS-COUNT > 0
|
||||
DISPLAY 'OK'.
|
||||
CLOSE F1 F2 F3 F4 F5 F6 F7 F8 F9 F10.
|
||||
STOP RUN.
|
||||
@@ -0,0 +1,23 @@
|
||||
* ==== TYPE: ADV-MATCH-ASCII-EBCDIC-KEY ====
|
||||
* FEATURE: Has both ASCII/EBCDIC conversion and WS-KEY
|
||||
* STATEMENT: INSPECT / IF
|
||||
* BRANCHES: 2, DECISIONS: 1
|
||||
* ADVERSARIAL: L1 keyword conflict: 编码转换 vs マッチング
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. ASCMT.
|
||||
DATA DIVISION.
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-KEY PIC X(10) VALUE 'ABCDEF0123'.
|
||||
01 WS-EBCDIC PIC X(10).
|
||||
01 WS-CHAR PIC X(1).
|
||||
01 WS-I PIC 9(2).
|
||||
PROCEDURE DIVISION.
|
||||
MAIN.
|
||||
MOVE SPACES TO WS-EBCDIC.
|
||||
PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 10
|
||||
MOVE WS-KEY(WS-I:1) TO WS-CHAR
|
||||
IF WS-CHAR >= 'A' AND <= 'Z'
|
||||
DISPLAY 'ALPHA'
|
||||
ELSE
|
||||
DISPLAY 'DIGIT'.
|
||||
STOP RUN.
|
||||
@@ -0,0 +1,22 @@
|
||||
* ==== TYPE: ADV-MATCH-PARAM-CALL ====
|
||||
* FEATURE: Matching + subprogram call (CALL + LINKAGE)
|
||||
* STATEMENT: CALL / IF
|
||||
* BRANCHES: 2, DECISIONS: 1
|
||||
* ADVERSARIAL: Combined matching and subprogram structure
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. CALLMT.
|
||||
DATA DIVISION.
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-MAST-KEY PIC X(10).
|
||||
01 WS-TRAN-KEY PIC X(10).
|
||||
01 WS-RESULT PIC X(10).
|
||||
LINKAGE SECTION.
|
||||
01 LS-PARAM PIC X(10).
|
||||
PROCEDURE DIVISION.
|
||||
MAIN.
|
||||
CALL 'SUBPGM' USING WS-RESULT.
|
||||
IF WS-MAST-KEY = WS-TRAN-KEY
|
||||
MOVE WS-MAST-KEY TO WS-RESULT
|
||||
ELSE
|
||||
MOVE SPACES TO WS-RESULT.
|
||||
STOP RUN.
|
||||
@@ -0,0 +1,22 @@
|
||||
* ==== TYPE: ADV-MATCH-FAKE ====
|
||||
* FEATURE: Falso matching: simple ADD program but
|
||||
* has WS-KEY variable to trick classifier
|
||||
* STATEMENT: ADD
|
||||
* BRANCHES: 2, DECISIONS: 1
|
||||
* ADVERSARIAL: Non-matching program with WS-KEY var
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. FAKEMT.
|
||||
DATA DIVISION.
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-KEY PIC 9(5) VALUE 0.
|
||||
01 WS-TOTAL PIC 9(5) VALUE 0.
|
||||
01 WS-VAL PIC 9(5) VALUE 100.
|
||||
PROCEDURE DIVISION.
|
||||
MAIN.
|
||||
MOVE 999 TO WS-KEY.
|
||||
ADD WS-KEY TO WS-VAL GIVING WS-TOTAL.
|
||||
IF WS-TOTAL > 500
|
||||
DISPLAY 'LARGE'
|
||||
ELSE
|
||||
DISPLAY 'SMALL'.
|
||||
STOP RUN.
|
||||
@@ -0,0 +1,20 @@
|
||||
* ==== TYPE: ADV-MATCH-COMMENT ====
|
||||
* FEATURE: "KEY" appears only in comments
|
||||
* STATEMENT: MOVE / DISPLAY
|
||||
* BRANCHES: 2, DECISIONS: 1
|
||||
* ADVERSARIAL: WS-KEY appears only in *> comment
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. KEYCMT.
|
||||
*> KEY COMPARISON: WS-KEY-A = WS-KEY-B
|
||||
*> THIS IS A MATCHING PROGRAM!
|
||||
DATA DIVISION.
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-A PIC X(5) VALUE 'ALPHA'.
|
||||
01 WS-B PIC X(5) VALUE 'BETA'.
|
||||
PROCEDURE DIVISION.
|
||||
MAIN.
|
||||
IF WS-A = 'ALPHA'
|
||||
DISPLAY 'A'
|
||||
ELSE
|
||||
DISPLAY 'B'.
|
||||
STOP RUN.
|
||||
@@ -0,0 +1,35 @@
|
||||
* ==== TYPE: ADV-MATCH-OLDSCHOOL ====
|
||||
* FEATURE: Real matching program but uses different
|
||||
* naming convention (K01-, not WS-)
|
||||
* STATEMENT: IF / READ / OPEN
|
||||
* BRANCHES: 2, DECISIONS: 1
|
||||
* ADVERSARIAL: KEY variables not prefixed WS-
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. KSMTCH.
|
||||
ENVIRONMENT DIVISION.
|
||||
INPUT-OUTPUT SECTION.
|
||||
FILE-CONTROL.
|
||||
SELECT FILE-A ASSIGN TO 'FILEA.DAT'.
|
||||
SELECT FILE-B ASSIGN TO 'FILEB.DAT'.
|
||||
DATA DIVISION.
|
||||
FILE SECTION.
|
||||
FD FILE-A.
|
||||
01 REC-A PIC X(80).
|
||||
FD FILE-B.
|
||||
01 REC-B PIC X(80).
|
||||
WORKING-STORAGE SECTION.
|
||||
01 K01-KEY PIC X(10).
|
||||
01 K02-KEY PIC X(10).
|
||||
01 WS-EOF1 PIC X VALUE 'N'.
|
||||
01 WS-EOF2 PIC X VALUE 'N'.
|
||||
PROCEDURE DIVISION.
|
||||
MAIN.
|
||||
OPEN INPUT FILE-A FILE-B.
|
||||
READ FILE-A INTO REC-A AT END MOVE 'Y' TO WS-EOF1.
|
||||
READ FILE-B INTO REC-B AT END MOVE 'Y' TO WS-EOF2.
|
||||
IF K01-KEY = K02-KEY
|
||||
DISPLAY 'MATCH'
|
||||
ELSE
|
||||
DISPLAY 'NO MATCH'.
|
||||
CLOSE FILE-A FILE-B.
|
||||
STOP RUN.
|
||||
@@ -0,0 +1,21 @@
|
||||
* ==== TYPE: ADV-MATCH-PREVKEY-NO-MATCH ====
|
||||
* FEATURE: Has WS-PREV-KEY but NOT a matching program
|
||||
* (trick the dedup/validation rule engine)
|
||||
* STATEMENT: IF
|
||||
* BRANCHES: 2, DECISIONS: 1
|
||||
* ADVERSARIAL: WS-PREV-KEY used only as counter, not matching
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. PREVKF.
|
||||
DATA DIVISION.
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-PREV-KEY PIC 9(5) VALUE 0.
|
||||
01 WS-VALUE PIC 9(5) VALUE 0.
|
||||
PROCEDURE DIVISION.
|
||||
MAIN.
|
||||
ADD 1 TO WS-PREV-KEY.
|
||||
ADD WS-PREV-KEY TO WS-VALUE.
|
||||
IF WS-VALUE > 10
|
||||
DISPLAY 'BIG'
|
||||
ELSE
|
||||
DISPLAY 'SMALL'.
|
||||
STOP RUN.
|
||||
@@ -0,0 +1,32 @@
|
||||
* ==== TYPE: ADV-MATCH-TINY ====
|
||||
* FEATURE: Minimal matching: only 1 read, 1 IF
|
||||
* STATEMENT: IF / READ
|
||||
* BRANCHES: 2, DECISIONS: 1
|
||||
* ADVERSARIAL: Bare-minimum matching program
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. TNYMT.
|
||||
ENVIRONMENT DIVISION.
|
||||
INPUT-OUTPUT SECTION.
|
||||
FILE-CONTROL.
|
||||
SELECT IN-FILE ASSIGN TO 'INDATA.DAT'.
|
||||
DATA DIVISION.
|
||||
FILE SECTION.
|
||||
FD IN-FILE.
|
||||
01 IN-REC.
|
||||
05 IN-KEY PIC X(10).
|
||||
05 IN-DATA PIC X(50).
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-KEY PIC X(10).
|
||||
01 WS-EOF PIC X VALUE 'N'.
|
||||
PROCEDURE DIVISION.
|
||||
MAIN.
|
||||
OPEN INPUT IN-FILE.
|
||||
READ IN-FILE INTO IN-REC
|
||||
AT END MOVE 'Y' TO WS-EOF.
|
||||
MOVE IN-KEY TO WS-KEY.
|
||||
IF WS-KEY = SPACES
|
||||
DISPLAY 'EMPTY'
|
||||
ELSE
|
||||
DISPLAY WS-KEY.
|
||||
CLOSE IN-FILE.
|
||||
STOP RUN.
|
||||
@@ -3,11 +3,10 @@
|
||||
COBOL 迁移专家设计的攻击面:
|
||||
- FP: 非匹配程序被误判为マッチング
|
||||
- FN: 真实匹配程序未被识别
|
||||
- 边界: 注释关键词、旧式命名、多文件非匹配
|
||||
- FN: 变量名不含 KEY 但结构是匹配程序
|
||||
- 边界: 注释关键词、旧式命名、多文件非匹配、跨行AT END、
|
||||
GO TO风格、NOT =比较、变量无连字符
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
@@ -17,7 +16,8 @@ from hina.classifier import detect_keyword
|
||||
|
||||
FIXTURES = Path(__file__).parents[3] / "test-data" / "cobol" / "adversarial"
|
||||
|
||||
# (filename, expect_matching, reason)
|
||||
# ── 对抗性 FP/FN 测试(使用 COBOL 样本文件)──
|
||||
|
||||
ADVERSARIAL_TESTS = [
|
||||
("ADV-FALSE-KEY.cbl", False,
|
||||
"FP: WS-KEY variable but only simple ADD, should NOT trigger matching"),
|
||||
@@ -75,67 +75,132 @@ def test_adversarial(filename, expect_matching, reason):
|
||||
)
|
||||
|
||||
|
||||
def test_structural_matching_no_keyword():
|
||||
"""FN: Matching program without KEY in variable names (CUST-CODE vs ORDR-CODE)
|
||||
# ── COBOL 专家 10 大攻击面测试 ──
|
||||
|
||||
Real-world COBOL matching programs often use -CODE or -ID instead of -KEY.
|
||||
Structural detection must catch these even without naming hints.
|
||||
"""
|
||||
src = """ IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. REALMT.
|
||||
ENVIRONMENT DIVISION.
|
||||
INPUT-OUTPUT SECTION.
|
||||
FILE-CONTROL.
|
||||
SELECT CUST-FILE ASSIGN TO 'CUST.DAT'.
|
||||
SELECT ORDR-FILE ASSIGN TO 'ORDR.DAT'.
|
||||
DATA DIVISION.
|
||||
FILE SECTION.
|
||||
FD CUST-FILE.
|
||||
01 CUST-REC.
|
||||
05 CUST-CODE PIC X(10).
|
||||
05 CUST-NAME PIC X(30).
|
||||
FD ORDR-FILE.
|
||||
01 ORDR-REC.
|
||||
05 ORDR-CODE PIC X(10).
|
||||
05 ORDR-AMT PIC 9(7)V99.
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-CUST-CODE PIC X(10).
|
||||
01 WS-ORDR-CODE PIC X(10).
|
||||
01 WS-EOF1 PIC X VALUE 'N'.
|
||||
01 WS-EOF2 PIC X VALUE 'N'.
|
||||
PROCEDURE DIVISION.
|
||||
MAIN.
|
||||
OPEN INPUT CUST-FILE ORDR-FILE.
|
||||
READ CUST-FILE INTO CUST-REC
|
||||
AT END MOVE 'Y' TO WS-EOF1.
|
||||
READ ORDR-FILE INTO ORDR-REC
|
||||
AT END MOVE 'Y' TO WS-EOF2.
|
||||
PERFORM UNTIL WS-EOF1 = 'Y' OR WS-EOF2 = 'Y'
|
||||
IF CUST-CODE = ORDR-CODE
|
||||
DISPLAY 'MATCH'
|
||||
ELSE IF CUST-CODE < ORDR-CODE
|
||||
READ CUST-FILE AT END MOVE 'Y' TO WS-EOF1
|
||||
ELSE
|
||||
READ ORDR-FILE AT END MOVE 'Y' TO WS-EOF2
|
||||
END-IF
|
||||
END-PERFORM.
|
||||
CLOSE CUST-FILE ORDR-FILE.
|
||||
STOP RUN.
|
||||
"""
|
||||
result = classify_program(src)
|
||||
kw = detect_keyword(src)
|
||||
COBOL_ATTACK_SOURCES = []
|
||||
|
||||
# Must have structural matching keyword
|
||||
assert any("structural" in k[2] for k in kw), (
|
||||
f"Expected structural matching keyword, got {kw}"
|
||||
def _add(name, src):
|
||||
COBOL_ATTACK_SOURCES.append((name, src))
|
||||
|
||||
_add("attack1: 跨行AT END",
|
||||
" IDENTIFICATION DIVISION. PROGRAM-ID. ATEND1."
|
||||
" ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL."
|
||||
" SELECT FILE-A ASSIGN TO 'A.DAT'."
|
||||
" SELECT FILE-B ASSIGN TO 'B.DAT'."
|
||||
" DATA DIVISION. FILE SECTION."
|
||||
" FD FILE-A. 01 REC-A PIC X(80)."
|
||||
" FD FILE-B. 01 REC-B PIC X(80)."
|
||||
" WORKING-STORAGE SECTION."
|
||||
" 01 WS-KEY-A PIC X(10). 01 WS-KEY-B PIC X(10)."
|
||||
" 01 WS-EOF-A PIC X VALUE 'N'. 01 WS-EOF-B PIC X VALUE 'N'."
|
||||
" PROCEDURE DIVISION. MAIN."
|
||||
" OPEN INPUT FILE-A FILE-B."
|
||||
" READ FILE-A INTO REC-A"
|
||||
" AT END MOVE 'Y' TO WS-EOF-A."
|
||||
" READ FILE-B INTO REC-B"
|
||||
" AT END MOVE 'Y' TO WS-EOF-B."
|
||||
" PERFORM UNTIL WS-EOF-A = 'Y' OR WS-EOF-B = 'Y'"
|
||||
" IF WS-KEY-A = WS-KEY-B DISPLAY 'M'"
|
||||
" ELSE IF WS-KEY-A < WS-KEY-B"
|
||||
" READ FILE-A AT END MOVE 'Y' TO WS-EOF-A"
|
||||
" ELSE READ FILE-B AT END MOVE 'Y' TO WS-EOF-B"
|
||||
" END-IF"
|
||||
" END-PERFORM."
|
||||
" CLOSE FILE-A FILE-B. STOP RUN.")
|
||||
|
||||
_add("attack4: 无连字符WSKEY",
|
||||
" IDENTIFICATION DIVISION. PROGRAM-ID. NOHYF."
|
||||
" ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL."
|
||||
" SELECT FILE-A ASSIGN TO 'A.DAT'."
|
||||
" SELECT FILE-B ASSIGN TO 'B.DAT'."
|
||||
" DATA DIVISION. FILE SECTION."
|
||||
" FD FILE-A. 01 REC-A PIC X(80)."
|
||||
" FD FILE-B. 01 REC-B PIC X(80)."
|
||||
" WORKING-STORAGE SECTION."
|
||||
" 01 WSKEY1 PIC X(10). 01 WSKEY2 PIC X(10)."
|
||||
" 01 WSEOF1 PIC X VALUE 'N'. 01 WSEOF2 PIC X VALUE 'N'."
|
||||
" PROCEDURE DIVISION. MAIN."
|
||||
" OPEN INPUT FILE-A FILE-B."
|
||||
" READ FILE-A AT END MOVE 'Y' TO WSEOF1."
|
||||
" READ FILE-B AT END MOVE 'Y' TO WSEOF2."
|
||||
" PERFORM UNTIL WSEOF1 = 'Y' OR WSEOF2 = 'Y'"
|
||||
" IF WSKEY1 = WSKEY2 DISPLAY 'M'"
|
||||
" ELSE IF WSKEY1 < WSKEY2"
|
||||
" READ FILE-A AT END MOVE 'Y' TO WSEOF1"
|
||||
" ELSE READ FILE-B AT END MOVE 'Y' TO WSEOF2"
|
||||
" END-IF"
|
||||
" END-PERFORM."
|
||||
" CLOSE FILE-A FILE-B. STOP RUN.")
|
||||
|
||||
_add("attack5: GO TO风格",
|
||||
" IDENTIFICATION DIVISION. PROGRAM-ID. GOTOM."
|
||||
" ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL."
|
||||
" SELECT FILE-A ASSIGN TO 'A.DAT'."
|
||||
" SELECT FILE-B ASSIGN TO 'B.DAT'."
|
||||
" DATA DIVISION. FILE SECTION."
|
||||
" FD FILE-A. 01 REC-A PIC X(80)."
|
||||
" FD FILE-B. 01 REC-B PIC X(80)."
|
||||
" WORKING-STORAGE SECTION."
|
||||
" 01 WS-KEY-A PIC X(10). 01 WS-KEY-B PIC X(10)."
|
||||
" 01 WS-EOF-A PIC X VALUE 'N'. 01 WS-EOF-B PIC X VALUE 'N'."
|
||||
" PROCEDURE DIVISION. MAIN."
|
||||
" OPEN INPUT FILE-A FILE-B."
|
||||
" READ FILE-A AT END MOVE 'Y' TO WS-EOF-A."
|
||||
" READ FILE-B AT END MOVE 'Y' TO WS-EOF-B."
|
||||
" LOOP."
|
||||
" IF WS-EOF-A = 'Y' OR WS-EOF-B = 'Y' GO TO EXIT-PGM."
|
||||
" IF WS-KEY-A = WS-KEY-B"
|
||||
" DISPLAY 'M'"
|
||||
" READ FILE-A AT END MOVE 'Y' TO WS-EOF-A"
|
||||
" READ FILE-B AT END MOVE 'Y' TO WS-EOF-B"
|
||||
" ELSE IF WS-KEY-A < WS-KEY-B"
|
||||
" READ FILE-A AT END MOVE 'Y' TO WS-EOF-A"
|
||||
" ELSE READ FILE-B AT END MOVE 'Y' TO WS-EOF-B"
|
||||
" END-IF."
|
||||
" GO TO LOOP."
|
||||
" EXIT-PGM. CLOSE FILE-A FILE-B. STOP RUN.")
|
||||
|
||||
_add("attack10: NOT = 比较",
|
||||
" IDENTIFICATION DIVISION. PROGRAM-ID. NOTEQ."
|
||||
" ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL."
|
||||
" SELECT FILE-A ASSIGN TO 'A.DAT'."
|
||||
" SELECT FILE-B ASSIGN TO 'B.DAT'."
|
||||
" DATA DIVISION. FILE SECTION."
|
||||
" FD FILE-A. 01 REC-A PIC X(80)."
|
||||
" FD FILE-B. 01 REC-B PIC X(80)."
|
||||
" WORKING-STORAGE SECTION."
|
||||
" 01 WS-KEY-A PIC X(10). 01 WS-KEY-B PIC X(10)."
|
||||
" 01 WS-EOF-A PIC X VALUE 'N'. 01 WS-EOF-B PIC X VALUE 'N'."
|
||||
" PROCEDURE DIVISION. MAIN."
|
||||
" OPEN INPUT FILE-A FILE-B."
|
||||
" READ FILE-A AT END MOVE 'Y' TO WS-EOF-A."
|
||||
" READ FILE-B AT END MOVE 'Y' TO WS-EOF-B."
|
||||
" PERFORM UNTIL WS-EOF-A = 'Y' OR WS-EOF-B = 'Y'"
|
||||
" IF WS-KEY-A NOT = WS-KEY-B"
|
||||
" IF WS-KEY-A < WS-KEY-B"
|
||||
" READ FILE-A AT END MOVE 'Y' TO WS-EOF-A"
|
||||
" ELSE READ FILE-B AT END MOVE 'Y' TO WS-EOF-B"
|
||||
" END-IF"
|
||||
" ELSE"
|
||||
" DISPLAY 'MATCH'"
|
||||
" READ FILE-A AT END MOVE 'Y' TO WS-EOF-A"
|
||||
" READ FILE-B AT END MOVE 'Y' TO WS-EOF-B"
|
||||
" END-IF"
|
||||
" END-PERFORM."
|
||||
" CLOSE FILE-A FILE-B. STOP RUN.")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name,source_text",
|
||||
COBOL_ATTACK_SOURCES,
|
||||
ids=[n for n, _ in COBOL_ATTACK_SOURCES],
|
||||
)
|
||||
|
||||
# Must be classified as matching
|
||||
def test_cobol_expert_attacks(name, source_text):
|
||||
"""COBOL 专家攻击面测试:所有结构式匹配程序必须被正确检测"""
|
||||
result = classify_program(source_text)
|
||||
assert "マッチング" in result["category"] or "二段階" in result["category"], (
|
||||
f"Expected matching, got '{result['category']}'"
|
||||
f"{name}: 漏检! got {result['category']} conf={result['confidence']:.2f}"
|
||||
)
|
||||
|
||||
# Confidence should be reasonable
|
||||
assert result["confidence"] > 0.30, (
|
||||
f"Confidence too low: {result['confidence']:.2f}"
|
||||
f"{name}: 确信度过低 {result['confidence']:.2f}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user