detect: test the pending LTE accept priority

The semantics of a pending `<hook` rule live in the tx walk, so pin them
there: two `<hook` accept rules where only the second one's buffer completes,
in a later segment, plus a TD rule at the same hook. The first segment must
leave the flow undecided, the second must accept it through the pending rule,
and the TD rule must still have run once.

Ticket: #9149.
pull/16392/head
Victor Julien 3 weeks ago
parent dcccd70ebc
commit 878fe24442

@ -4805,6 +4805,130 @@ static int DetectFwLteCoverageTest03(void)
PASS;
}
/** \test a pending <hook accept rule holds first-match priority over a later
* same-hook rule for the whole tx, and a TD rule at the hook still runs.
*
* Add order sets the candidate (iid) order, newest first: the TD rule first,
* then the scaffold, the early rule and finally the late rule, so the late
* rule is walked first and holds the hook priority while pending. */
static int DetectFwLteMpmWalkTest01(void)
{
EngineModeSetFirewall(ENGINE_HOST_IS_ROUTER);
ThreadVars th_v;
memset(&th_v, 0, sizeof(th_v));
StatsThreadInit(&th_v.stats);
Flow f;
TcpSession ssn;
memset(&f, 0, sizeof(f));
memset(&ssn, 0, sizeof(ssn));
FLOW_INITIALIZE(&f);
f.protoctx = (void *)&ssn;
f.flags |= FLOW_IPV4;
f.proto = IPPROTO_TCP;
f.alproto = ALPROTO_HTTP1;
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
FAIL_IF_NULL(alp_tctx);
StreamTcpInitConfig(true);
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
FAIL_IF(DetectFirewallInitDefaultPolicies(de_ctx) != 0);
/* TD rule at the hook: must still run while the accept rules are pending */
Signature *s_td = DetectEngineAppendSig(de_ctx,
"alert http1:request_headers any any -> any any "
"(http.host.raw; content:\"early.example.com\"; sid:1103;)");
FAIL_IF_NULL(s_td);
/* accept the packet hook for all tcp: suppresses the default drop and lets
* the app-layer rules decide */
Signature *s_scaf1 = DetectFirewallRuleAppendNew(
de_ctx, "accept:hook tcp:all any any -> any any (flow:not_established; sid:1021;)");
FAIL_IF_NULL(s_scaf1);
Signature *s_scaf2 = DetectFirewallRuleAppendNew(
de_ctx, "accept:hook tcp:all any any -> any any (flow:established; sid:1022;)");
FAIL_IF_NULL(s_scaf2);
/* content present from the first segment */
Signature *s_early = DetectFirewallRuleAppendNew(de_ctx,
"accept:flow,alert http1:<request_headers any any -> any any "
"(http.host.raw; content:\"early.example.com\"; sid:1102;)");
FAIL_IF_NULL(s_early);
/* the header only arrives in the second segment */
Signature *s_late = DetectFirewallRuleAppendNew(de_ctx,
"accept:flow,alert http1:<request_headers any any -> any any "
"(http.header; content:\"X-Late: a\"; sid:1101;)");
FAIL_IF_NULL(s_late);
SigGroupBuild(de_ctx);
DetectEngineThreadCtx *det_ctx = NULL;
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
FAIL_IF_NULL(det_ctx);
/* segment 1: request line + host, the header block is open */
const uint8_t *seg1 = (const uint8_t *)"GET / HTTP/1.1\r\nHost: early.example.com\r\n";
Packet *p1 = UTHBuildPacket((uint8_t *)seg1, (uint16_t)strlen((const char *)seg1), IPPROTO_TCP);
FAIL_IF_NULL(p1);
p1->flow = &f;
p1->flowflags |= FLOW_PKT_TOSERVER;
p1->flowflags |= FLOW_PKT_ESTABLISHED;
p1->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
p1->app_update_direction = STREAM_TOSERVER;
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_HTTP1, STREAM_TOSERVER, seg1,
(uint16_t)strlen((const char *)seg1));
FAIL_IF(r != 0);
SigMatchSignatures(&th_v, de_ctx, det_ctx, p1);
/* 1101 is pending: 1102 must not have accepted the flow yet */
FAIL_IF(f.flags & FLOW_ACTION_ACCEPT);
/* segment 2: the late header completes the block */
const uint8_t *seg2 = (const uint8_t *)"X-Late: a\r\n\r\n";
Packet *p2 = UTHBuildPacket((uint8_t *)seg2, (uint16_t)strlen((const char *)seg2), IPPROTO_TCP);
FAIL_IF_NULL(p2);
p2->flow = &f;
p2->flowflags |= FLOW_PKT_TOSERVER;
p2->flowflags |= FLOW_PKT_ESTABLISHED;
p2->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
p2->app_update_direction = STREAM_TOSERVER;
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_HTTP1, STREAM_TOSERVER, seg2,
(uint16_t)strlen((const char *)seg2));
FAIL_IF(r != 0);
SigMatchSignatures(&th_v, de_ctx, det_ctx, p2);
/* 1101 matched on completion: the flow is accepted by the pending rule */
FAIL_IF(!(f.flags & FLOW_ACTION_ACCEPT));
/* the pending rule won; the early rule never matched. The TD rule ran
* once, on completion. */
uint32_t late = 0, early = 0, td = 0;
for (uint16_t i = 0; i < det_ctx->alert_queue_size; i++) {
if (det_ctx->alert_queue[i].s == s_late)
late++;
else if (det_ctx->alert_queue[i].s == s_early)
early++;
else if (det_ctx->alert_queue[i].s == s_td)
td++;
}
FAIL_IF(late != 1);
FAIL_IF(early != 0);
FAIL_IF(td != 1);
UTHFreePackets(&p2, 1);
UTHFreePackets(&p1, 1);
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FLOW_DESTROY(&f);
StatsThreadCleanup(&th_v.stats);
EngineModeSetIDS();
PASS;
}
/** \test a pending LTE rule the walk would discard must not hold the hook.
* Pins the filters in DetectFwLteRuleStillLive(): a rule whose header and protocol
* checks pass but whose packet engines fail is no barrier, as the full walk would
@ -4971,6 +5095,7 @@ void SigRegisterTests(void)
UtRegisterTest("DetectFwLteCoverageTest01", DetectFwLteCoverageTest01);
UtRegisterTest("DetectFwLteCoverageTest02", DetectFwLteCoverageTest02);
UtRegisterTest("DetectFwLteCoverageTest03", DetectFwLteCoverageTest03);
UtRegisterTest("DetectFwLteMpmWalkTest01", DetectFwLteMpmWalkTest01);
UtRegisterTest("DetectFwLtePendingLiveTest01", DetectFwLtePendingLiveTest01);
UtRegisterTest("DetectFwLtePendingLivePolicyTest01", DetectFwLtePendingLivePolicyTest01);

Loading…
Cancel
Save