|
|
|
|
@ -0,0 +1,598 @@
|
|
|
|
|
/* Copyright (C) 2026 Open Information Security Foundation
|
|
|
|
|
*
|
|
|
|
|
* You can copy, redistribute or modify this Program under the terms of
|
|
|
|
|
* the GNU General Public License version 2 as published by the Free
|
|
|
|
|
* Software Foundation.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* version 2 along with this program; if not, write to the Free Software
|
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
|
* 02110-1301, USA.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/** \file \brief tcp.session: keyword (Redmine #7704). */
|
|
|
|
|
|
|
|
|
|
#include "suricata-common.h"
|
|
|
|
|
|
|
|
|
|
#include "detect.h"
|
|
|
|
|
#include "detect-engine.h"
|
|
|
|
|
#include "detect-parse.h"
|
|
|
|
|
|
|
|
|
|
#include "flow.h"
|
|
|
|
|
#include "stream-tcp.h"
|
|
|
|
|
#include "stream-tcp-private.h"
|
|
|
|
|
|
|
|
|
|
#include "util-debug.h"
|
|
|
|
|
#include "util-unittest.h"
|
|
|
|
|
|
|
|
|
|
#include "detect-tcp-session.h"
|
|
|
|
|
|
|
|
|
|
/** Upper bound on the total length of the keyword argument. */
|
|
|
|
|
#define DETECT_TCP_SESSION_ARG_MAX_LEN 32
|
|
|
|
|
|
|
|
|
|
/* prototypes */
|
|
|
|
|
static int DetectTcpSessionMatch(
|
|
|
|
|
DetectEngineThreadCtx *, Packet *, const Signature *, const SigMatchCtx *);
|
|
|
|
|
static int DetectTcpSessionSetup(DetectEngineCtx *, Signature *, const char *);
|
|
|
|
|
static void DetectTcpSessionFree(DetectEngineCtx *, void *);
|
|
|
|
|
#ifdef UNITTESTS
|
|
|
|
|
static void DetectTcpSessionRegisterTests(void);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/** Map a single phase token to its phase flag. */
|
|
|
|
|
static uint8_t DetectTcpSessionPhaseFlagFromToken(const char *token)
|
|
|
|
|
{
|
|
|
|
|
if (strcmp(token, "setup") == 0) {
|
|
|
|
|
return DETECT_TCP_SESSION_PHASE_SETUP;
|
|
|
|
|
}
|
|
|
|
|
if (strcmp(token, "established") == 0) {
|
|
|
|
|
return DETECT_TCP_SESSION_PHASE_ESTABLISHED;
|
|
|
|
|
}
|
|
|
|
|
if (strcmp(token, "closing") == 0) {
|
|
|
|
|
return DETECT_TCP_SESSION_PHASE_CLOSING;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Parse a tcp.session: keyword argument into a DetectTcpSessionData. */
|
|
|
|
|
static DetectTcpSessionData *DetectTcpSessionParse(const char *arg)
|
|
|
|
|
{
|
|
|
|
|
if (arg == NULL || strlen(arg) == 0) {
|
|
|
|
|
SCLogError("tcp.session keyword requires a value: comma-separated "
|
|
|
|
|
"subset of {setup, established, closing}");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strlen(arg) > DETECT_TCP_SESSION_ARG_MAX_LEN) {
|
|
|
|
|
SCLogError("tcp.session argument too long (%zu > %d): accepted "
|
|
|
|
|
"tokens are {setup, established, closing}",
|
|
|
|
|
strlen(arg), DETECT_TCP_SESSION_ARG_MAX_LEN);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Reject leading, trailing, or consecutive commas — strtok_r would
|
|
|
|
|
* silently skip them, but we treat these as syntax errors. */
|
|
|
|
|
if (arg[0] == ',' || arg[strlen(arg) - 1] == ',') {
|
|
|
|
|
SCLogError("tcp.session: leading or trailing comma in argument "
|
|
|
|
|
"\"%s\"; accepted tokens are {setup, established, closing}",
|
|
|
|
|
arg);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
if (strstr(arg, ",,") != NULL) {
|
|
|
|
|
SCLogError("tcp.session: consecutive commas in argument \"%s\"; "
|
|
|
|
|
"accepted tokens are {setup, established, closing}",
|
|
|
|
|
arg);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t phase_flags = 0;
|
|
|
|
|
|
|
|
|
|
/* Work on a mutable copy for strtok_r. */
|
|
|
|
|
char *argdup = SCStrdup(arg);
|
|
|
|
|
if (argdup == NULL) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *saveptr = NULL;
|
|
|
|
|
char *token = strtok_r(argdup, ",", &saveptr);
|
|
|
|
|
|
|
|
|
|
while (token != NULL) {
|
|
|
|
|
if (strlen(token) == 0) {
|
|
|
|
|
SCLogError("tcp.session: empty token in argument \"%s\"; "
|
|
|
|
|
"accepted tokens are {setup, established, closing}",
|
|
|
|
|
arg);
|
|
|
|
|
SCFree(argdup);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uint8_t flag = DetectTcpSessionPhaseFlagFromToken(token);
|
|
|
|
|
if (flag == 0) {
|
|
|
|
|
SCLogError("tcp.session: unknown token \"%s\" in argument "
|
|
|
|
|
"\"%s\"; accepted tokens are {setup, established, "
|
|
|
|
|
"closing}",
|
|
|
|
|
token, arg);
|
|
|
|
|
SCFree(argdup);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (phase_flags & flag) {
|
|
|
|
|
SCLogError("tcp.session: duplicate token \"%s\" in argument "
|
|
|
|
|
"\"%s\"",
|
|
|
|
|
token, arg);
|
|
|
|
|
SCFree(argdup);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
phase_flags |= flag;
|
|
|
|
|
token = strtok_r(NULL, ",", &saveptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCFree(argdup);
|
|
|
|
|
|
|
|
|
|
if (phase_flags == 0) {
|
|
|
|
|
SCLogError("tcp.session keyword requires a value: comma-separated "
|
|
|
|
|
"subset of {setup, established, closing}");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DetectTcpSessionData *data = SCMalloc(sizeof(*data));
|
|
|
|
|
if (unlikely(data == NULL)) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
data->phase_flags = phase_flags;
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Per-packet match: returns 1 iff the packet's TCP session phase intersects phase_flags. */
|
|
|
|
|
static int DetectTcpSessionMatch(
|
|
|
|
|
DetectEngineThreadCtx *det_ctx, Packet *p, const Signature *s, const SigMatchCtx *ctx)
|
|
|
|
|
{
|
|
|
|
|
const DetectTcpSessionData *d = (const DetectTcpSessionData *)ctx;
|
|
|
|
|
const Flow *f = p->flow;
|
|
|
|
|
|
|
|
|
|
if (f == NULL || f->proto != IPPROTO_TCP) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t pkt_phase = 0;
|
|
|
|
|
|
|
|
|
|
/* Setup vs established split via FLOW_PKT_ESTABLISHED. */
|
|
|
|
|
if (p->flowflags & FLOW_PKT_ESTABLISHED) {
|
|
|
|
|
pkt_phase |= DETECT_TCP_SESSION_PHASE_ESTABLISHED;
|
|
|
|
|
} else {
|
|
|
|
|
pkt_phase |= DETECT_TCP_SESSION_PHASE_SETUP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Closing phase: consult TcpSession::state directly. */
|
|
|
|
|
const TcpSession *ssn = (const TcpSession *)f->protoctx;
|
|
|
|
|
if (ssn != NULL) {
|
|
|
|
|
switch (ssn->state) {
|
|
|
|
|
case TCP_FIN_WAIT1:
|
|
|
|
|
case TCP_FIN_WAIT2:
|
|
|
|
|
case TCP_TIME_WAIT:
|
|
|
|
|
case TCP_LAST_ACK:
|
|
|
|
|
case TCP_CLOSE_WAIT:
|
|
|
|
|
case TCP_CLOSING:
|
|
|
|
|
pkt_phase |= DETECT_TCP_SESSION_PHASE_CLOSING;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (pkt_phase & d->phase_flags) ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Setup function: parse argument and append the SigMatch. */
|
|
|
|
|
static int DetectTcpSessionSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
|
|
|
|
|
{
|
|
|
|
|
if (!(DetectProtoContainsProto(&s->proto, IPPROTO_TCP))) {
|
|
|
|
|
SCLogError("tcp.session requires a TCP rule");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DetectTcpSessionData *data = DetectTcpSessionParse(arg);
|
|
|
|
|
if (data == NULL) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SCSigMatchAppendSMToList(
|
|
|
|
|
de_ctx, s, DETECT_TCP_SESSION, (SigMatchCtx *)data, DETECT_SM_LIST_MATCH) == NULL) {
|
|
|
|
|
DetectTcpSessionFree(de_ctx, data);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s->flags |= SIG_FLAG_REQUIRE_PACKET;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Free a DetectTcpSessionData allocation. */
|
|
|
|
|
static void DetectTcpSessionFree(DetectEngineCtx *de_ctx, void *ptr)
|
|
|
|
|
{
|
|
|
|
|
if (ptr != NULL) {
|
|
|
|
|
SCFree(ptr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Registration function for the tcp.session: keyword. */
|
|
|
|
|
void DetectTcpSessionRegister(void)
|
|
|
|
|
{
|
|
|
|
|
sigmatch_table[DETECT_TCP_SESSION].name = "tcp.session";
|
|
|
|
|
sigmatch_table[DETECT_TCP_SESSION].desc =
|
|
|
|
|
"match TCP session lifecycle phase set (setup, established, closing)";
|
|
|
|
|
sigmatch_table[DETECT_TCP_SESSION].url = "/rules/flow-keywords.html#tcp-session";
|
|
|
|
|
sigmatch_table[DETECT_TCP_SESSION].Match = DetectTcpSessionMatch;
|
|
|
|
|
sigmatch_table[DETECT_TCP_SESSION].Setup = DetectTcpSessionSetup;
|
|
|
|
|
sigmatch_table[DETECT_TCP_SESSION].Free = DetectTcpSessionFree;
|
|
|
|
|
sigmatch_table[DETECT_TCP_SESSION].flags = SIGMATCH_SUPPORT_FIREWALL;
|
|
|
|
|
#ifdef UNITTESTS
|
|
|
|
|
sigmatch_table[DETECT_TCP_SESSION].RegisterTests = DetectTcpSessionRegisterTests;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef UNITTESTS
|
|
|
|
|
#include "util-unittest.h"
|
|
|
|
|
#include "util-unittest-helper.h"
|
|
|
|
|
#include "detect-engine.h"
|
|
|
|
|
#include "detect-flow.h"
|
|
|
|
|
|
|
|
|
|
int DetectFlowMatch(DetectEngineThreadCtx *, Packet *, const Signature *, const SigMatchCtx *);
|
|
|
|
|
|
|
|
|
|
/** Test01: single-token parsing. */
|
|
|
|
|
static int DetectTcpSessionTest01(void)
|
|
|
|
|
{
|
|
|
|
|
DetectTcpSessionData *d = DetectTcpSessionParse("setup");
|
|
|
|
|
FAIL_IF_NULL(d);
|
|
|
|
|
FAIL_IF_NOT(d->phase_flags == DETECT_TCP_SESSION_PHASE_SETUP);
|
|
|
|
|
DetectTcpSessionFree(NULL, d);
|
|
|
|
|
|
|
|
|
|
d = DetectTcpSessionParse("established");
|
|
|
|
|
FAIL_IF_NULL(d);
|
|
|
|
|
FAIL_IF_NOT(d->phase_flags == DETECT_TCP_SESSION_PHASE_ESTABLISHED);
|
|
|
|
|
DetectTcpSessionFree(NULL, d);
|
|
|
|
|
|
|
|
|
|
d = DetectTcpSessionParse("closing");
|
|
|
|
|
FAIL_IF_NULL(d);
|
|
|
|
|
FAIL_IF_NOT(d->phase_flags == DETECT_TCP_SESSION_PHASE_CLOSING);
|
|
|
|
|
DetectTcpSessionFree(NULL, d);
|
|
|
|
|
|
|
|
|
|
PASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Test02: multi-token parsing and order independence. */
|
|
|
|
|
static int DetectTcpSessionTest02(void)
|
|
|
|
|
{
|
|
|
|
|
DetectTcpSessionData *d = DetectTcpSessionParse("setup,established");
|
|
|
|
|
FAIL_IF_NULL(d);
|
|
|
|
|
FAIL_IF_NOT(d->phase_flags ==
|
|
|
|
|
(DETECT_TCP_SESSION_PHASE_SETUP | DETECT_TCP_SESSION_PHASE_ESTABLISHED));
|
|
|
|
|
DetectTcpSessionFree(NULL, d);
|
|
|
|
|
|
|
|
|
|
d = DetectTcpSessionParse("established,setup");
|
|
|
|
|
FAIL_IF_NULL(d);
|
|
|
|
|
FAIL_IF_NOT(d->phase_flags ==
|
|
|
|
|
(DETECT_TCP_SESSION_PHASE_SETUP | DETECT_TCP_SESSION_PHASE_ESTABLISHED));
|
|
|
|
|
DetectTcpSessionFree(NULL, d);
|
|
|
|
|
|
|
|
|
|
d = DetectTcpSessionParse("setup,established,closing");
|
|
|
|
|
FAIL_IF_NULL(d);
|
|
|
|
|
FAIL_IF_NOT(d->phase_flags ==
|
|
|
|
|
(DETECT_TCP_SESSION_PHASE_SETUP | DETECT_TCP_SESSION_PHASE_ESTABLISHED |
|
|
|
|
|
DETECT_TCP_SESSION_PHASE_CLOSING));
|
|
|
|
|
DetectTcpSessionFree(NULL, d);
|
|
|
|
|
|
|
|
|
|
PASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Test03: closing token and closing combinations. */
|
|
|
|
|
static int DetectTcpSessionTest03(void)
|
|
|
|
|
{
|
|
|
|
|
DetectTcpSessionData *d = DetectTcpSessionParse("closing");
|
|
|
|
|
FAIL_IF_NULL(d);
|
|
|
|
|
FAIL_IF_NOT(d->phase_flags == DETECT_TCP_SESSION_PHASE_CLOSING);
|
|
|
|
|
DetectTcpSessionFree(NULL, d);
|
|
|
|
|
|
|
|
|
|
d = DetectTcpSessionParse("established,closing");
|
|
|
|
|
FAIL_IF_NULL(d);
|
|
|
|
|
FAIL_IF_NOT(d->phase_flags ==
|
|
|
|
|
(DETECT_TCP_SESSION_PHASE_ESTABLISHED | DETECT_TCP_SESSION_PHASE_CLOSING));
|
|
|
|
|
DetectTcpSessionFree(NULL, d);
|
|
|
|
|
|
|
|
|
|
PASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Test04: empty/missing value is rejected. */
|
|
|
|
|
static int DetectTcpSessionTest04(void)
|
|
|
|
|
{
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse(NULL));
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse(""));
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse(","));
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse(",setup"));
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse("setup,"));
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse("setup,,established"));
|
|
|
|
|
PASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Test05: unknown/whitespace/over-length tokens are rejected. */
|
|
|
|
|
static int DetectTcpSessionTest05(void)
|
|
|
|
|
{
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse("bogus"));
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse("setup,bogus"));
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse("SETUP"));
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse(" setup"));
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse("setup "));
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse("setu p"));
|
|
|
|
|
char longarg[128];
|
|
|
|
|
memset(longarg, 'a', sizeof(longarg) - 1);
|
|
|
|
|
longarg[sizeof(longarg) - 1] = '\0';
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse(longarg));
|
|
|
|
|
PASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Test06: duplicate tokens are rejected. */
|
|
|
|
|
static int DetectTcpSessionTest06(void)
|
|
|
|
|
{
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse("setup,setup"));
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse("established,established"));
|
|
|
|
|
FAIL_IF_NOT_NULL(DetectTcpSessionParse("setup,established,setup"));
|
|
|
|
|
PASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Build a minimal TCP packet+flow harness for match tests. */
|
|
|
|
|
static Packet *DetectTcpSessionTestBuildPacket(
|
|
|
|
|
Flow *f, TcpSession *ssn, TCPHdr *tcph, uint8_t state, bool established)
|
|
|
|
|
{
|
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
|
|
|
if (p == NULL)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
memset(f, 0, sizeof(*f));
|
|
|
|
|
memset(ssn, 0, sizeof(*ssn));
|
|
|
|
|
memset(tcph, 0, sizeof(*tcph));
|
|
|
|
|
|
|
|
|
|
f->proto = IPPROTO_TCP;
|
|
|
|
|
ssn->state = state;
|
|
|
|
|
f->protoctx = ssn;
|
|
|
|
|
|
|
|
|
|
p->flow = f;
|
|
|
|
|
PacketSetTCP(p, (uint8_t *)tcph);
|
|
|
|
|
p->flowflags |= FLOW_PKT_TOSERVER;
|
|
|
|
|
if (established) {
|
|
|
|
|
p->flowflags |= FLOW_PKT_ESTABLISHED;
|
|
|
|
|
}
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Test07: established flow matches tcp.session:established. */
|
|
|
|
|
static int DetectTcpSessionTest07(void)
|
|
|
|
|
{
|
|
|
|
|
Flow f;
|
|
|
|
|
TcpSession ssn;
|
|
|
|
|
TCPHdr tcph;
|
|
|
|
|
ThreadVars tv;
|
|
|
|
|
DetectEngineThreadCtx dtx;
|
|
|
|
|
Signature s;
|
|
|
|
|
memset(&tv, 0, sizeof(tv));
|
|
|
|
|
memset(&dtx, 0, sizeof(dtx));
|
|
|
|
|
memset(&s, 0, sizeof(s));
|
|
|
|
|
|
|
|
|
|
Packet *p = DetectTcpSessionTestBuildPacket(&f, &ssn, &tcph, TCP_ESTABLISHED, true);
|
|
|
|
|
FAIL_IF_NULL(p);
|
|
|
|
|
|
|
|
|
|
DetectTcpSessionData *d = DetectTcpSessionParse("established");
|
|
|
|
|
FAIL_IF_NULL(d);
|
|
|
|
|
|
|
|
|
|
FAIL_IF_NOT(DetectTcpSessionMatch(&dtx, p, &s, (const SigMatchCtx *)d) == 1);
|
|
|
|
|
|
|
|
|
|
DetectTcpSessionFree(NULL, d);
|
|
|
|
|
PacketFree(p);
|
|
|
|
|
PASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Test08: SYN_SENT flow matches tcp.session:setup but not established. */
|
|
|
|
|
static int DetectTcpSessionTest08(void)
|
|
|
|
|
{
|
|
|
|
|
Flow f;
|
|
|
|
|
TcpSession ssn;
|
|
|
|
|
TCPHdr tcph;
|
|
|
|
|
ThreadVars tv;
|
|
|
|
|
DetectEngineThreadCtx dtx;
|
|
|
|
|
Signature s;
|
|
|
|
|
memset(&tv, 0, sizeof(tv));
|
|
|
|
|
memset(&dtx, 0, sizeof(dtx));
|
|
|
|
|
memset(&s, 0, sizeof(s));
|
|
|
|
|
|
|
|
|
|
Packet *p = DetectTcpSessionTestBuildPacket(&f, &ssn, &tcph, TCP_SYN_SENT, false);
|
|
|
|
|
FAIL_IF_NULL(p);
|
|
|
|
|
|
|
|
|
|
DetectTcpSessionData *setup = DetectTcpSessionParse("setup");
|
|
|
|
|
FAIL_IF_NULL(setup);
|
|
|
|
|
FAIL_IF_NOT(DetectTcpSessionMatch(&dtx, p, &s, (const SigMatchCtx *)setup) == 1);
|
|
|
|
|
DetectTcpSessionFree(NULL, setup);
|
|
|
|
|
|
|
|
|
|
DetectTcpSessionData *est = DetectTcpSessionParse("established");
|
|
|
|
|
FAIL_IF_NULL(est);
|
|
|
|
|
FAIL_IF_NOT(DetectTcpSessionMatch(&dtx, p, &s, (const SigMatchCtx *)est) == 0);
|
|
|
|
|
DetectTcpSessionFree(NULL, est);
|
|
|
|
|
|
|
|
|
|
PacketFree(p);
|
|
|
|
|
PASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Test09: midstream pickup in TCP_ESTABLISHED matches established, not setup. */
|
|
|
|
|
static int DetectTcpSessionTest09(void)
|
|
|
|
|
{
|
|
|
|
|
Flow f;
|
|
|
|
|
TcpSession ssn;
|
|
|
|
|
TCPHdr tcph;
|
|
|
|
|
ThreadVars tv;
|
|
|
|
|
DetectEngineThreadCtx dtx;
|
|
|
|
|
Signature s;
|
|
|
|
|
memset(&tv, 0, sizeof(tv));
|
|
|
|
|
memset(&dtx, 0, sizeof(dtx));
|
|
|
|
|
memset(&s, 0, sizeof(s));
|
|
|
|
|
|
|
|
|
|
Packet *p = DetectTcpSessionTestBuildPacket(&f, &ssn, &tcph, TCP_ESTABLISHED, true);
|
|
|
|
|
FAIL_IF_NULL(p);
|
|
|
|
|
ssn.flags |= STREAMTCP_FLAG_MIDSTREAM | STREAMTCP_FLAG_MIDSTREAM_ESTABLISHED;
|
|
|
|
|
|
|
|
|
|
DetectTcpSessionData *est = DetectTcpSessionParse("established");
|
|
|
|
|
FAIL_IF_NULL(est);
|
|
|
|
|
FAIL_IF_NOT(DetectTcpSessionMatch(&dtx, p, &s, (const SigMatchCtx *)est) == 1);
|
|
|
|
|
DetectTcpSessionFree(NULL, est);
|
|
|
|
|
|
|
|
|
|
DetectTcpSessionData *setup = DetectTcpSessionParse("setup");
|
|
|
|
|
FAIL_IF_NULL(setup);
|
|
|
|
|
FAIL_IF_NOT(DetectTcpSessionMatch(&dtx, p, &s, (const SigMatchCtx *)setup) == 0);
|
|
|
|
|
DetectTcpSessionFree(NULL, setup);
|
|
|
|
|
|
|
|
|
|
PacketFree(p);
|
|
|
|
|
PASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Test10: equivalence with flow:established / flow:not_established. */
|
|
|
|
|
static int DetectTcpSessionTest10(void)
|
|
|
|
|
{
|
|
|
|
|
ThreadVars tv;
|
|
|
|
|
DetectEngineThreadCtx dtx;
|
|
|
|
|
Signature s;
|
|
|
|
|
memset(&tv, 0, sizeof(tv));
|
|
|
|
|
memset(&dtx, 0, sizeof(dtx));
|
|
|
|
|
memset(&s, 0, sizeof(s));
|
|
|
|
|
|
|
|
|
|
DetectTcpSessionData *ts_setup = DetectTcpSessionParse("setup");
|
|
|
|
|
FAIL_IF_NULL(ts_setup);
|
|
|
|
|
DetectTcpSessionData *ts_est = DetectTcpSessionParse("established");
|
|
|
|
|
FAIL_IF_NULL(ts_est);
|
|
|
|
|
|
|
|
|
|
DetectFlowData fd_not_est = { .flags = DETECT_FLOW_FLAG_NOT_ESTABLISHED, .match_cnt = 1 };
|
|
|
|
|
DetectFlowData fd_est = { .flags = DETECT_FLOW_FLAG_ESTABLISHED, .match_cnt = 1 };
|
|
|
|
|
|
|
|
|
|
const uint8_t states[] = { TCP_NONE, TCP_SYN_SENT, TCP_SYN_RECV, TCP_ESTABLISHED };
|
|
|
|
|
const bool est_flags[] = { false, true };
|
|
|
|
|
|
|
|
|
|
for (size_t si = 0; si < sizeof(states) / sizeof(states[0]); si++) {
|
|
|
|
|
for (size_t ei = 0; ei < sizeof(est_flags) / sizeof(est_flags[0]); ei++) {
|
|
|
|
|
Flow f;
|
|
|
|
|
TcpSession ssn;
|
|
|
|
|
TCPHdr tcph;
|
|
|
|
|
Packet *p = DetectTcpSessionTestBuildPacket(&f, &ssn, &tcph, states[si], est_flags[ei]);
|
|
|
|
|
FAIL_IF_NULL(p);
|
|
|
|
|
|
|
|
|
|
int ts_setup_r = DetectTcpSessionMatch(&dtx, p, &s, (const SigMatchCtx *)ts_setup);
|
|
|
|
|
int flow_not_est_r = DetectFlowMatch(&dtx, p, &s, (const SigMatchCtx *)&fd_not_est);
|
|
|
|
|
FAIL_IF_NOT(ts_setup_r == flow_not_est_r);
|
|
|
|
|
|
|
|
|
|
int ts_est_r = DetectTcpSessionMatch(&dtx, p, &s, (const SigMatchCtx *)ts_est);
|
|
|
|
|
int flow_est_r = DetectFlowMatch(&dtx, p, &s, (const SigMatchCtx *)&fd_est);
|
|
|
|
|
FAIL_IF_NOT(ts_est_r == flow_est_r);
|
|
|
|
|
|
|
|
|
|
PacketFree(p);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DetectTcpSessionFree(NULL, ts_setup);
|
|
|
|
|
DetectTcpSessionFree(NULL, ts_est);
|
|
|
|
|
PASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int DetectTcpSessionMatchExhaustive(void);
|
|
|
|
|
|
|
|
|
|
static void DetectTcpSessionRegisterTests(void)
|
|
|
|
|
{
|
|
|
|
|
UtRegisterTest("DetectTcpSessionTest01", DetectTcpSessionTest01);
|
|
|
|
|
UtRegisterTest("DetectTcpSessionTest02", DetectTcpSessionTest02);
|
|
|
|
|
UtRegisterTest("DetectTcpSessionTest03", DetectTcpSessionTest03);
|
|
|
|
|
UtRegisterTest("DetectTcpSessionTest04", DetectTcpSessionTest04);
|
|
|
|
|
UtRegisterTest("DetectTcpSessionTest05", DetectTcpSessionTest05);
|
|
|
|
|
UtRegisterTest("DetectTcpSessionTest06", DetectTcpSessionTest06);
|
|
|
|
|
UtRegisterTest("DetectTcpSessionTest07", DetectTcpSessionTest07);
|
|
|
|
|
UtRegisterTest("DetectTcpSessionTest08", DetectTcpSessionTest08);
|
|
|
|
|
UtRegisterTest("DetectTcpSessionTest09", DetectTcpSessionTest09);
|
|
|
|
|
UtRegisterTest("DetectTcpSessionTest10", DetectTcpSessionTest10);
|
|
|
|
|
UtRegisterTest("DetectTcpSessionMatchExhaustive", DetectTcpSessionMatchExhaustive);
|
|
|
|
|
}
|
|
|
|
|
#endif /* UNITTESTS */
|
|
|
|
|
|
|
|
|
|
#ifdef UNITTESTS
|
|
|
|
|
|
|
|
|
|
/** Oracle: compute expected per-packet phase mask from state and flow flag. */
|
|
|
|
|
static uint8_t DetectTcpSessionExpectedPhaseMask(enum TcpState state, bool flow_pkt_est)
|
|
|
|
|
{
|
|
|
|
|
uint8_t mask = 0;
|
|
|
|
|
|
|
|
|
|
if (flow_pkt_est) {
|
|
|
|
|
mask |= DETECT_TCP_SESSION_PHASE_ESTABLISHED;
|
|
|
|
|
} else {
|
|
|
|
|
mask |= DETECT_TCP_SESSION_PHASE_SETUP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
|
case TCP_FIN_WAIT1:
|
|
|
|
|
case TCP_FIN_WAIT2:
|
|
|
|
|
case TCP_TIME_WAIT:
|
|
|
|
|
case TCP_LAST_ACK:
|
|
|
|
|
case TCP_CLOSE_WAIT:
|
|
|
|
|
case TCP_CLOSING:
|
|
|
|
|
mask |= DETECT_TCP_SESSION_PHASE_CLOSING;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Exhaustive match test over all (state × flag × phase_flags) combinations. */
|
|
|
|
|
static int DetectTcpSessionMatchExhaustive(void)
|
|
|
|
|
{
|
|
|
|
|
/* Iterate all TcpState values (0..TCP_CLOSED). Value 1 is unused
|
|
|
|
|
* (TCP_LISTEN is commented out) but the match function handles it
|
|
|
|
|
* via the default case, so including it is harmless. */
|
|
|
|
|
|
|
|
|
|
for (uint8_t st = 0; st <= TCP_CLOSED; st++) {
|
|
|
|
|
for (int est = 0; est <= 1; est++) {
|
|
|
|
|
const bool flow_pkt_est = (est == 1);
|
|
|
|
|
|
|
|
|
|
/* All 7 non-empty subsets of the 3-bit phase space. */
|
|
|
|
|
for (uint8_t pf = 1; pf <= 0x7; pf++) {
|
|
|
|
|
ThreadVars tv;
|
|
|
|
|
DetectEngineThreadCtx dtx;
|
|
|
|
|
Signature s;
|
|
|
|
|
TcpSession ssn;
|
|
|
|
|
Flow f;
|
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
|
|
|
FAIL_IF_NULL(p);
|
|
|
|
|
memset(&tv, 0, sizeof(tv));
|
|
|
|
|
memset(&dtx, 0, sizeof(dtx));
|
|
|
|
|
memset(&s, 0, sizeof(s));
|
|
|
|
|
memset(&ssn, 0, sizeof(ssn));
|
|
|
|
|
memset(&f, 0, sizeof(f));
|
|
|
|
|
f.proto = IPPROTO_TCP;
|
|
|
|
|
ssn.state = st;
|
|
|
|
|
f.protoctx = &ssn;
|
|
|
|
|
p->flow = &f;
|
|
|
|
|
p->flags |= PKT_HAS_FLOW;
|
|
|
|
|
if (flow_pkt_est)
|
|
|
|
|
p->flowflags |= FLOW_PKT_ESTABLISHED;
|
|
|
|
|
|
|
|
|
|
DetectTcpSessionData d = { .phase_flags = pf };
|
|
|
|
|
const uint8_t expected_mask =
|
|
|
|
|
DetectTcpSessionExpectedPhaseMask((enum TcpState)st, flow_pkt_est);
|
|
|
|
|
const int expected = (expected_mask & pf) ? 1 : 0;
|
|
|
|
|
const int actual = DetectTcpSessionMatch(&dtx, p, &s, (const SigMatchCtx *)&d);
|
|
|
|
|
PacketFree(p);
|
|
|
|
|
FAIL_IF_NOT(actual == expected);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif /* UNITTESTS */
|