개요
룰의 크기 비교가 숫자가 아니라 글자순으로 비교되고 있었다. 자릿수가 다르면 판정이 뒤집힌다.
"900" > "1000" -> 참 ('9' > '1')
크기 비교를 쓰는 룰 7건 중 5건이 틀린 판정을 내리고 있었다. 디스크 지연·부팅 지연·시간 변경 증상이 모두 여기에 해당한다. 시계 오차 증상을 설계하다 발견했다.
노린 것
버그를 없애는 데서 끝내지 않았다. 룰 작성자가 숫자를 따옴표로 적어도 숫자로 비교되게 했다.
종전에는 {"$gte": "3600"} 과 {"$gte": 3600} 이 다르게 동작했다. 룰을 쓰는 사람이 JSON 타입까지 신경 써야 했다. 사람이 기억할 일이 아니라 엔진이 흡수할 일이다. 실제로 룰 파일이 두 방식을 섞어 쓰고 있었고 그래서 틀렸다. 이제 어느 쪽으로 적어도 같게 동작한다. 룰 파일은 한 글자도 고치지 않았다.
원인 셋
1. ETW EventData가 전부 문자열로 들어온다
XML을 JSON으로 바꾸는 곳에 숫자 판별이 없다. (agent/yagent21/CETW.cpp:2342)
parent = node->value(); // const char* -> JSON 문자열
LatencyMs도 DegradationTime도 값은 숫자로 보이지만 JSON 타입은 문자열이다.
2. 비교기가 양쪽 다 JSON 숫자일 때만 숫자로 봤다
agent/module/CRule.h:1270
int ValueCompare(const Json::Value& L, const Json::Value& R){
const bool numL = IsNumeric(L), numR = IsNumeric(R);
if (numL && numR){ ... 숫자 비교 ... }
...
int ci = _stricmp(a.c_str(), b.c_str()); // <- 여기로 떨어졌다
}
IsNumeric은 JSON 타입만 본다. 문자열 "3600"은 숫자가 아니다. ResolveRhsToken도 {...} 형태만 해석하고 나머지는 그대로 돌려주므로, 룰에 적은 "3600"은 끝까지 문자열이었다.
3. CalcTimeDiff가 문자열을 돌려줬다
초 단위 차이를 계산하는 함수인데 agent/module/CRule.h:1412가 std::to_string 결과를 담았다. 좌변까지 문자열이 됐다. 계산 함수가 문자열을 내놓는 것 자체가 맞지 않다.
깨져 있던 곳 — 크기 비교 7건 중 5건
| 룰 | 좌변 | 우변 |
|---|---|---|
0011/ntfs.json | LatencyMs 문자열 | 1000 숫자 |
0011/io_latency.json | LatencyMs 문자열 | "1000" 문자열 |
0011/boot_delay.json | DegradationTime 문자열 | "5000" 문자열 |
0011/change_system_time.json (규칙 2개) | CalcTimeDiff(...) 문자열 | "3600" 문자열 |
우변에 따옴표를 쳤는지는 상관없었다. 좌변이 문자열이라 어느 쪽이든 글자 비교로 갔다.
10분 변경(600초) -> 「1시간 이상」에 잘못 매칭
3시간 변경(10000초) -> 누락
0.9초 지연(900ms) -> 「1초 이상」에 잘못 매칭
멀쩡했던 2건은 0008/DNS.json과 0008/TCP.json이다. 좌변을 에이전트가 Json::UInt64로 직접 넣어 양쪽 다 숫자였다.
고친 것 — module/CRule.h 한 파일
| 항목 | 내용 |
|---|---|
#include <charconv> | 표준 문자열→숫자 |
RULE_NUMBER / AsNumber() / NumberCompare() | 신설. 파싱은 std::from_chars가 한다 |
ValueCompare | AsNumber를 쓴다. 양쪽이 숫자로 읽히면 숫자로 |
CalcTimeDiff | Json::Int64로 담는다 |
지켜야 할 세 가지를 표준이 그대로 만족한다.
- 16진 거부 — base 10 고정이라
"0x10"은'x'에서 멈춘다. 업데이트 오류 코드 14건이 종전대로 동작한다. - 전체 소비 —
ptr != last면 덜 읽은 것이다."1.3.15"는 두 번째'.'에서 걸린다. - 범위 초과 —
errc::result_out_of_range면 글자로 간다. 소수점이나 지수가 없으면long long으로 읽는다.double로 통일하면 2^53을 넘는 주소·핸들이 뭉개진다.
빈 문자열과 앞 공백도 표준이 거부한다. 그래서 ""는 0과 같아지지 않는다.
$eq도 같이 바뀐다
ValueEquals가 ValueCompare를 그대로 쓴다. 갈라놓지 않았다. 갈라두면 $gte "141"은 숫자로, $eq "141"은 글자로 비교되어 같은 룰 파일 안에서 규칙이 어긋난다.
같음 비교 681건은 원래 정상이었다. 글자로 떨어질 때 ToString으로 양쪽을 정규화하므로 결과가 맞았다. 이 변경으로 달라지는 것은 선행 0 다섯 건뿐이다. "00000008"이 "8"과 같아지는데, EXCEPTION_ACCESS_VIOLATION의 예외 파라미터(읽기 0 / 쓰기 1 / DEP 8)라 숫자로 보는 편이 옳다.
$regex는 ValueCompare를 타지 않아 영향이 없다.
검증
orange.exe --rule로 12건을 확인했다. 전부 통과.
- 고쳐진 것 4건 — 시간 변경 600초·10000초, 디스크 지연 900ms·1500ms
- 지켜진 것 8건 — 16진·버전 문자열·빈 문자열·앞 공백·선행 0·쉼표·정수 크기
Orange Platform 에이전트 룰 엔진의 숫자 비교 정정에 관한 기술 리포트입니다.