|
|
|
/* Copyright (C) 2007-2017 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
|
|
|
|
*
|
|
|
|
* \author Victor Julien <victor@inliniac.net>
|
|
|
|
*
|
|
|
|
* Basic detection engine
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "suricata-common.h"
|
|
|
|
#include "suricata.h"
|
|
|
|
#include "conf.h"
|
|
|
|
|
|
|
|
#include "decode.h"
|
|
|
|
#include "flow.h"
|
|
|
|
#include "stream-tcp.h"
|
|
|
|
#include "app-layer.h"
|
|
|
|
#include "app-layer-parser.h"
|
|
|
|
|
|
|
|
#include "detect.h"
|
|
|
|
#include "detect-engine.h"
|
|
|
|
#include "detect-engine-profile.h"
|
|
|
|
|
|
|
|
#include "detect-engine-alert.h"
|
|
|
|
#include "detect-engine-siggroup.h"
|
|
|
|
#include "detect-engine-address.h"
|
|
|
|
#include "detect-engine-proto.h"
|
|
|
|
#include "detect-engine-port.h"
|
|
|
|
#include "detect-engine-mpm.h"
|
|
|
|
#include "detect-engine-iponly.h"
|
|
|
|
#include "detect-engine-threshold.h"
|
|
|
|
#include "detect-engine-prefilter.h"
|
|
|
|
#include "detect-engine-state.h"
|
|
|
|
#include "detect-engine-analyzer.h"
|
|
|
|
|
|
|
|
#include "detect-engine-payload.h"
|
|
|
|
#include "detect-engine-event.h"
|
|
|
|
|
|
|
|
#include "detect-filestore.h"
|
|
|
|
#include "detect-flowvar.h"
|
|
|
|
#include "detect-replace.h"
|
|
|
|
|
|
|
|
#include "util-validate.h"
|
|
|
|
#include "util-detect.h"
|
|
|
|
|
|
|
|
typedef struct DetectRunScratchpad {
|
|
|
|
const AppProto alproto;
|
|
|
|
const uint8_t flow_flags; /* flow/state flags: STREAM_* */
|
|
|
|
const bool app_decoder_events;
|
|
|
|
const SigGroupHead *sgh;
|
|
|
|
SignatureMask pkt_mask;
|
|
|
|
} DetectRunScratchpad;
|
|
|
|
|
|
|
|
/* prototypes */
|
|
|
|
static DetectRunScratchpad DetectRunSetup(const DetectEngineCtx *de_ctx,
|
|
|
|
DetectEngineThreadCtx *det_ctx, Packet * const p, Flow * const pflow);
|
|
|
|
static void DetectRunInspectIPOnly(ThreadVars *tv, const DetectEngineCtx *de_ctx,
|
|
|
|
DetectEngineThreadCtx *det_ctx, Flow * const pflow, Packet * const p);
|
|
|
|
static inline void DetectRunGetRuleGroup(const DetectEngineCtx *de_ctx,
|
|
|
|
Packet * const p, Flow * const pflow, DetectRunScratchpad *scratch);
|
|
|
|
static inline void DetectRunPrefilterPkt(ThreadVars *tv,
|
|
|
|
DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, Packet *p,
|
|
|
|
DetectRunScratchpad *scratch);
|
|
|
|
static inline void DetectRulePacketRules(ThreadVars * const tv,
|
|
|
|
DetectEngineCtx * const de_ctx, DetectEngineThreadCtx * const det_ctx,
|
|
|
|
Packet * const p, Flow * const pflow, const DetectRunScratchpad *scratch);
|
|
|
|
static void DetectRunTx(ThreadVars *tv, DetectEngineCtx *de_ctx,
|
|
|
|
DetectEngineThreadCtx *det_ctx, Packet *p,
|
|
|
|
Flow *f, DetectRunScratchpad *scratch);
|
|
|
|
static inline void DetectRunPostRules(ThreadVars *tv, DetectEngineCtx *de_ctx,
|
|
|
|
DetectEngineThreadCtx *det_ctx, Packet * const p, Flow * const pflow,
|
|
|
|
DetectRunScratchpad *scratch);
|
|
|
|
static void DetectRunCleanup(DetectEngineThreadCtx *det_ctx,
|
|
|
|
Packet *p, Flow * const pflow);
|
|
|
|
|
|
|
|
/** \internal
|
|
|
|
*/
|
|
|
|
static void DetectRun(ThreadVars *th_v,
|
|
|
|
DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
|
|
|
|
Packet *p)
|
|
|
|
{
|
|
|
|
SCEnter();
|
|
|
|
SCLogDebug("pcap_cnt %"PRIu64, p->pcap_cnt);
|
|
|
|
|
|
|
|
/* bail early if packet should not be inspected */
|
|
|
|
if (p->flags & PKT_NOPACKET_INSPECTION) {
|
|
|
|
/* nothing to do */
|
|
|
|
SCReturn;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Load the Packet's flow early, even though it might not be needed.
|
|
|
|
* Mark as a constant pointer, although the flow itself can change. */
|
|
|
|
Flow * const pflow = p->flow;
|
|
|
|
|
|
|
|
DetectRunScratchpad scratch = DetectRunSetup(de_ctx, det_ctx, p, pflow);
|
|
|
|
|
|
|
|
/* run the IPonly engine */
|
|
|
|
DetectRunInspectIPOnly(th_v, de_ctx, det_ctx, pflow, p);
|
|
|
|
|
|
|
|
/* get our rule group */
|
|
|
|
DetectRunGetRuleGroup(de_ctx, p, pflow, &scratch);
|
|
|
|
/* if we didn't get a sig group head, we
|
|
|
|
* have nothing to do.... */
|
|
|
|
if (scratch.sgh == NULL) {
|
|
|
|
SCLogDebug("no sgh for this packet, nothing to match against");
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* run the prefilters for packets */
|
|
|
|
DetectRunPrefilterPkt(th_v, de_ctx, det_ctx, p, &scratch);
|
|
|
|
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_RULES);
|
|
|
|
/* inspect the rules against the packet */
|
|
|
|
DetectRulePacketRules(th_v, de_ctx, det_ctx, p, pflow, &scratch);
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_RULES);
|
|
|
|
|
|
|
|
/* run tx/state inspection. Don't call for ICMP error msgs. */
|
|
|
|
if (pflow && pflow->alstate && likely(pflow->proto == p->proto)) {
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_TX);
|
|
|
|
DetectRunTx(th_v, de_ctx, det_ctx, p, pflow, &scratch);
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_TX);
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
DetectRunPostRules(th_v, de_ctx, det_ctx, p, pflow, &scratch);
|
|
|
|
|
|
|
|
DetectRunCleanup(det_ctx, p, pflow);
|
|
|
|
SCReturn;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void DetectRunPostMatch(ThreadVars *tv,
|
|
|
|
DetectEngineThreadCtx *det_ctx, Packet *p,
|
|
|
|
const Signature *s)
|
|
|
|
{
|
|
|
|
/* run the packet match functions */
|
|
|
|
const SigMatchData *smd = s->sm_arrays[DETECT_SM_LIST_POSTMATCH];
|
|
|
|
if (smd != NULL) {
|
|
|
|
KEYWORD_PROFILING_SET_LIST(det_ctx, DETECT_SM_LIST_POSTMATCH);
|
|
|
|
|
|
|
|
SCLogDebug("running match functions, sm %p", smd);
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
KEYWORD_PROFILING_START;
|
|
|
|
(void)sigmatch_table[smd->type].Match(det_ctx, p, s, smd->ctx);
|
|
|
|
KEYWORD_PROFILING_END(det_ctx, smd->type, 1);
|
|
|
|
if (smd->is_last)
|
|
|
|
break;
|
|
|
|
smd++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief Get the SigGroupHead for a packet.
|
|
|
|
*
|
|
|
|
* \param de_ctx detection engine context
|
|
|
|
* \param p packet
|
|
|
|
*
|
|
|
|
* \retval sgh the SigGroupHead or NULL if non applies to the packet
|
|
|
|
*/
|
|
|
|
const SigGroupHead *SigMatchSignaturesGetSgh(const DetectEngineCtx *de_ctx,
|
|
|
|
const Packet *p)
|
|
|
|
{
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
int f;
|
|
|
|
SigGroupHead *sgh = NULL;
|
|
|
|
|
|
|
|
/* if the packet proto is 0 (not set), we're inspecting it against
|
|
|
|
* the decoder events sgh we have. */
|
|
|
|
if (p->proto == 0 && p->events.cnt > 0) {
|
|
|
|
SCReturnPtr(de_ctx->decoder_event_sgh, "SigGroupHead");
|
|
|
|
} else if (p->proto == 0) {
|
|
|
|
if (!(PKT_IS_IPV4(p) || PKT_IS_IPV6(p))) {
|
|
|
|
/* not IP, so nothing to do */
|
|
|
|
SCReturnPtr(NULL, "SigGroupHead");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* select the flow_gh */
|
|
|
|
if (p->flowflags & FLOW_PKT_TOCLIENT)
|
|
|
|
f = 0;
|
|
|
|
else
|
|
|
|
f = 1;
|
|
|
|
|
|
|
|
int proto = IP_GET_IPPROTO(p);
|
|
|
|
if (proto == IPPROTO_TCP) {
|
|
|
|
DetectPort *list = de_ctx->flow_gh[f].tcp;
|
|
|
|
SCLogDebug("tcp toserver %p, tcp toclient %p: going to use %p",
|
|
|
|
de_ctx->flow_gh[1].tcp, de_ctx->flow_gh[0].tcp, de_ctx->flow_gh[f].tcp);
|
|
|
|
uint16_t port = f ? p->dp : p->sp;
|
|
|
|
SCLogDebug("tcp port %u -> %u:%u", port, p->sp, p->dp);
|
|
|
|
DetectPort *sghport = DetectPortLookupGroup(list, port);
|
|
|
|
if (sghport != NULL)
|
|
|
|
sgh = sghport->sh;
|
|
|
|
SCLogDebug("TCP list %p, port %u, direction %s, sghport %p, sgh %p",
|
|
|
|
list, port, f ? "toserver" : "toclient", sghport, sgh);
|
|
|
|
} else if (proto == IPPROTO_UDP) {
|
|
|
|
DetectPort *list = de_ctx->flow_gh[f].udp;
|
|
|
|
uint16_t port = f ? p->dp : p->sp;
|
|
|
|
DetectPort *sghport = DetectPortLookupGroup(list, port);
|
|
|
|
if (sghport != NULL)
|
|
|
|
sgh = sghport->sh;
|
|
|
|
SCLogDebug("UDP list %p, port %u, direction %s, sghport %p, sgh %p",
|
|
|
|
list, port, f ? "toserver" : "toclient", sghport, sgh);
|
|
|
|
} else {
|
|
|
|
sgh = de_ctx->flow_gh[f].sgh[proto];
|
|
|
|
}
|
|
|
|
|
|
|
|
SCReturnPtr(sgh, "SigGroupHead");
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void DetectPrefilterMergeSort(DetectEngineCtx *de_ctx,
|
|
|
|
DetectEngineThreadCtx *det_ctx)
|
|
|
|
{
|
|
|
|
SigIntId mpm, nonmpm;
|
|
|
|
SigIntId *mpm_ptr = det_ctx->pmq.rule_id_array;
|
|
|
|
SigIntId *nonmpm_ptr = det_ctx->non_pf_id_array;
|
|
|
|
uint32_t m_cnt = det_ctx->pmq.rule_id_array_cnt;
|
|
|
|
uint32_t n_cnt = det_ctx->non_pf_id_cnt;
|
|
|
|
SigIntId *final_ptr;
|
|
|
|
uint32_t final_cnt;
|
|
|
|
SigIntId id;
|
|
|
|
SigIntId previous_id = (SigIntId)-1;
|
|
|
|
Signature **sig_array = de_ctx->sig_array;
|
|
|
|
Signature **match_array = det_ctx->match_array;
|
|
|
|
Signature *s;
|
|
|
|
|
|
|
|
SCLogDebug("PMQ rule id array count %d", det_ctx->pmq.rule_id_array_cnt);
|
|
|
|
|
|
|
|
/* Load first values. */
|
|
|
|
if (likely(m_cnt)) {
|
|
|
|
mpm = *mpm_ptr;
|
|
|
|
} else {
|
|
|
|
/* mpm list is empty */
|
|
|
|
final_ptr = nonmpm_ptr;
|
|
|
|
final_cnt = n_cnt;
|
|
|
|
goto final;
|
|
|
|
}
|
|
|
|
if (likely(n_cnt)) {
|
|
|
|
nonmpm = *nonmpm_ptr;
|
|
|
|
} else {
|
|
|
|
/* non-mpm list is empty. */
|
|
|
|
final_ptr = mpm_ptr;
|
|
|
|
final_cnt = m_cnt;
|
|
|
|
goto final;
|
|
|
|
}
|
|
|
|
while (1) {
|
|
|
|
if (mpm < nonmpm) {
|
|
|
|
/* Take from mpm list */
|
|
|
|
id = mpm;
|
|
|
|
|
|
|
|
s = sig_array[id];
|
|
|
|
/* As the mpm list can contain duplicates, check for that here. */
|
|
|
|
if (likely(id != previous_id)) {
|
|
|
|
*match_array++ = s;
|
|
|
|
previous_id = id;
|
|
|
|
}
|
|
|
|
if (unlikely(--m_cnt == 0)) {
|
|
|
|
/* mpm list is now empty */
|
|
|
|
final_ptr = nonmpm_ptr;
|
|
|
|
final_cnt = n_cnt;
|
|
|
|
goto final;
|
|
|
|
}
|
|
|
|
mpm_ptr++;
|
|
|
|
mpm = *mpm_ptr;
|
|
|
|
} else if (mpm > nonmpm) {
|
|
|
|
id = nonmpm;
|
|
|
|
|
|
|
|
s = sig_array[id];
|
|
|
|
/* As the mpm list can contain duplicates, check for that here. */
|
|
|
|
if (likely(id != previous_id)) {
|
|
|
|
*match_array++ = s;
|
|
|
|
previous_id = id;
|
|
|
|
}
|
|
|
|
if (unlikely(--n_cnt == 0)) {
|
|
|
|
final_ptr = mpm_ptr;
|
|
|
|
final_cnt = m_cnt;
|
|
|
|
goto final;
|
|
|
|
}
|
|
|
|
nonmpm_ptr++;
|
|
|
|
nonmpm = *nonmpm_ptr;
|
|
|
|
|
|
|
|
} else { /* implied mpm == nonmpm */
|
|
|
|
/* special case: if on both lists, it's a negated mpm pattern */
|
|
|
|
|
|
|
|
/* mpm list may have dups, so skip past them here */
|
|
|
|
while (--m_cnt != 0) {
|
|
|
|
mpm_ptr++;
|
|
|
|
mpm = *mpm_ptr;
|
|
|
|
if (mpm != nonmpm)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* if mpm is done, update nonmpm_ptrs and jump to final */
|
|
|
|
if (unlikely(m_cnt == 0)) {
|
|
|
|
n_cnt--;
|
|
|
|
|
|
|
|
/* mpm list is now empty */
|
|
|
|
final_ptr = ++nonmpm_ptr;
|
|
|
|
final_cnt = n_cnt;
|
|
|
|
goto final;
|
|
|
|
}
|
|
|
|
/* otherwise, if nonmpm is done jump to final for mpm
|
|
|
|
* mpm ptrs alrady updated */
|
|
|
|
if (unlikely(--n_cnt == 0)) {
|
|
|
|
final_ptr = mpm_ptr;
|
|
|
|
final_cnt = m_cnt;
|
|
|
|
goto final;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* not at end of the lists, update nonmpm. Mpm already
|
|
|
|
* updated in while loop above. */
|
|
|
|
nonmpm_ptr++;
|
|
|
|
nonmpm = *nonmpm_ptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final: /* Only one list remaining. Just walk that list. */
|
|
|
|
|
|
|
|
while (final_cnt-- > 0) {
|
|
|
|
id = *final_ptr++;
|
|
|
|
s = sig_array[id];
|
|
|
|
|
|
|
|
/* As the mpm list can contain duplicates, check for that here. */
|
|
|
|
if (likely(id != previous_id)) {
|
|
|
|
*match_array++ = s;
|
|
|
|
previous_id = id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
det_ctx->match_array_cnt = match_array - det_ctx->match_array;
|
|
|
|
|
|
|
|
DEBUG_VALIDATE_BUG_ON((det_ctx->pmq.rule_id_array_cnt + det_ctx->non_pf_id_cnt) < det_ctx->match_array_cnt);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \internal
|
|
|
|
* \brief build non-prefilter list based on the rule group list we've set.
|
|
|
|
*/
|
|
|
|
static inline void
|
|
|
|
DetectPrefilterBuildNonPrefilterList(DetectEngineThreadCtx *det_ctx,
|
|
|
|
const SignatureMask mask, const uint8_t alproto)
|
|
|
|
{
|
|
|
|
for (uint32_t x = 0; x < det_ctx->non_pf_store_cnt; x++) {
|
|
|
|
/* only if the mask matches this rule can possibly match,
|
|
|
|
* so build the non_mpm array only for match candidates */
|
|
|
|
const SignatureMask rule_mask = det_ctx->non_pf_store_ptr[x].mask;
|
|
|
|
const uint8_t rule_alproto = det_ctx->non_pf_store_ptr[x].alproto;
|
|
|
|
if ((rule_mask & mask) == rule_mask && (rule_alproto == 0 || rule_alproto == alproto)) {
|
|
|
|
det_ctx->non_pf_id_array[det_ctx->non_pf_id_cnt++] = det_ctx->non_pf_store_ptr[x].id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \internal
|
|
|
|
* \brief select non-mpm list
|
|
|
|
* Based on the packet properties, select the non-mpm list to use
|
|
|
|
* \todo move non_pf_store* into scratchpad */
|
|
|
|
static inline void
|
|
|
|
DetectPrefilterSetNonPrefilterList(const Packet *p, DetectEngineThreadCtx *det_ctx, DetectRunScratchpad *scratch)
|
|
|
|
{
|
|
|
|
if ((p->proto == IPPROTO_TCP) && (p->tcph != NULL) && (p->tcph->th_flags & TH_SYN)) {
|
|
|
|
det_ctx->non_pf_store_ptr = scratch->sgh->non_pf_syn_store_array;
|
|
|
|
det_ctx->non_pf_store_cnt = scratch->sgh->non_pf_syn_store_cnt;
|
|
|
|
} else {
|
|
|
|
det_ctx->non_pf_store_ptr = scratch->sgh->non_pf_other_store_array;
|
|
|
|
det_ctx->non_pf_store_cnt = scratch->sgh->non_pf_other_store_cnt;
|
|
|
|
}
|
|
|
|
SCLogDebug("sgh non_pf ptr %p cnt %u (syn %p/%u, other %p/%u)",
|
|
|
|
det_ctx->non_pf_store_ptr, det_ctx->non_pf_store_cnt,
|
|
|
|
scratch->sgh->non_pf_syn_store_array, scratch->sgh->non_pf_syn_store_cnt,
|
|
|
|
scratch->sgh->non_pf_other_store_array, scratch->sgh->non_pf_other_store_cnt);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \internal
|
|
|
|
* \brief update flow's file tracking flags based on the detection engine
|
|
|
|
* A set of flags is prepared that is sent to the File API. The
|
|
|
|
File API may reject one or more based on the global force settings.
|
|
|
|
*/
|
|
|
|
static inline void
|
|
|
|
DetectPostInspectFileFlagsUpdate(Flow *f, const SigGroupHead *sgh, uint8_t direction)
|
|
|
|
{
|
|
|
|
uint16_t flow_file_flags = FLOWFILE_INIT;
|
|
|
|
|
|
|
|
if (sgh == NULL) {
|
|
|
|
SCLogDebug("requesting disabling all file features for flow");
|
|
|
|
flow_file_flags = FLOWFILE_NONE;
|
|
|
|
} else {
|
|
|
|
if (sgh->filestore_cnt == 0) {
|
|
|
|
SCLogDebug("requesting disabling filestore for flow");
|
|
|
|
flow_file_flags |= (FLOWFILE_NO_STORE_TS|FLOWFILE_NO_STORE_TC);
|
|
|
|
}
|
|
|
|
#ifdef HAVE_MAGIC
|
|
|
|
if (!(sgh->flags & SIG_GROUP_HEAD_HAVEFILEMAGIC)) {
|
|
|
|
SCLogDebug("requesting disabling magic for flow");
|
|
|
|
flow_file_flags |= (FLOWFILE_NO_MAGIC_TS|FLOWFILE_NO_MAGIC_TC);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_NSS
|
|
|
|
if (!(sgh->flags & SIG_GROUP_HEAD_HAVEFILEMD5)) {
|
|
|
|
SCLogDebug("requesting disabling md5 for flow");
|
|
|
|
flow_file_flags |= (FLOWFILE_NO_MD5_TS|FLOWFILE_NO_MD5_TC);
|
|
|
|
}
|
|
|
|
if (!(sgh->flags & SIG_GROUP_HEAD_HAVEFILESHA1)) {
|
|
|
|
SCLogDebug("requesting disabling sha1 for flow");
|
|
|
|
flow_file_flags |= (FLOWFILE_NO_SHA1_TS|FLOWFILE_NO_SHA1_TC);
|
|
|
|
}
|
|
|
|
if (!(sgh->flags & SIG_GROUP_HEAD_HAVEFILESHA256)) {
|
|
|
|
SCLogDebug("requesting disabling sha256 for flow");
|
|
|
|
flow_file_flags |= (FLOWFILE_NO_SHA256_TS|FLOWFILE_NO_SHA256_TC);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (!(sgh->flags & SIG_GROUP_HEAD_HAVEFILESIZE)) {
|
|
|
|
SCLogDebug("requesting disabling filesize for flow");
|
|
|
|
flow_file_flags |= (FLOWFILE_NO_SIZE_TS|FLOWFILE_NO_SIZE_TC);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (flow_file_flags != 0) {
|
|
|
|
FileUpdateFlowFileFlags(f, flow_file_flags, direction);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
DetectRunPostGetFirstRuleGroup(const Packet *p, Flow *pflow, const SigGroupHead *sgh)
|
|
|
|
{
|
|
|
|
if ((p->flowflags & FLOW_PKT_TOSERVER) && !(pflow->flags & FLOW_SGH_TOSERVER)) {
|
|
|
|
/* first time we see this toserver sgh, store it */
|
|
|
|
pflow->sgh_toserver = sgh;
|
|
|
|
pflow->flags |= FLOW_SGH_TOSERVER;
|
|
|
|
|
|
|
|
if (p->proto == IPPROTO_TCP && (sgh == NULL || !(sgh->flags & SIG_GROUP_HEAD_HAVERAWSTREAM))) {
|
|
|
|
if (pflow->protoctx != NULL) {
|
|
|
|
TcpSession *ssn = pflow->protoctx;
|
|
|
|
SCLogDebug("STREAMTCP_STREAM_FLAG_DISABLE_RAW ssn.client");
|
|
|
|
ssn->client.flags |= STREAMTCP_STREAM_FLAG_DISABLE_RAW;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DetectPostInspectFileFlagsUpdate(pflow,
|
|
|
|
pflow->sgh_toserver, STREAM_TOSERVER);
|
|
|
|
|
|
|
|
} else if ((p->flowflags & FLOW_PKT_TOCLIENT) && !(pflow->flags & FLOW_SGH_TOCLIENT)) {
|
|
|
|
pflow->sgh_toclient = sgh;
|
|
|
|
pflow->flags |= FLOW_SGH_TOCLIENT;
|
|
|
|
|
|
|
|
if (p->proto == IPPROTO_TCP && (sgh == NULL || !(sgh->flags & SIG_GROUP_HEAD_HAVERAWSTREAM))) {
|
|
|
|
if (pflow->protoctx != NULL) {
|
|
|
|
TcpSession *ssn = pflow->protoctx;
|
|
|
|
SCLogDebug("STREAMTCP_STREAM_FLAG_DISABLE_RAW ssn.server");
|
|
|
|
ssn->server.flags |= STREAMTCP_STREAM_FLAG_DISABLE_RAW;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DetectPostInspectFileFlagsUpdate(pflow,
|
|
|
|
pflow->sgh_toclient, STREAM_TOCLIENT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void DetectRunGetRuleGroup(
|
|
|
|
const DetectEngineCtx *de_ctx,
|
|
|
|
Packet * const p, Flow * const pflow,
|
|
|
|
DetectRunScratchpad *scratch)
|
|
|
|
{
|
|
|
|
const SigGroupHead *sgh = NULL;
|
|
|
|
|
|
|
|
if (pflow) {
|
|
|
|
bool use_flow_sgh = false;
|
|
|
|
/* Get the stored sgh from the flow (if any). Make sure we're not using
|
|
|
|
* the sgh for icmp error packets part of the same stream. */
|
|
|
|
if (IP_GET_IPPROTO(p) == pflow->proto) { /* filter out icmp */
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_GETSGH);
|
|
|
|
if ((p->flowflags & FLOW_PKT_TOSERVER) && (pflow->flags & FLOW_SGH_TOSERVER)) {
|
|
|
|
sgh = pflow->sgh_toserver;
|
|
|
|
SCLogDebug("sgh = pflow->sgh_toserver; => %p", sgh);
|
|
|
|
use_flow_sgh = true;
|
|
|
|
} else if ((p->flowflags & FLOW_PKT_TOCLIENT) && (pflow->flags & FLOW_SGH_TOCLIENT)) {
|
|
|
|
sgh = pflow->sgh_toclient;
|
|
|
|
SCLogDebug("sgh = pflow->sgh_toclient; => %p", sgh);
|
|
|
|
use_flow_sgh = true;
|
|
|
|
}
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_GETSGH);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(use_flow_sgh)) {
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_GETSGH);
|
|
|
|
sgh = SigMatchSignaturesGetSgh(de_ctx, p);
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_GETSGH);
|
|
|
|
|
|
|
|
/* HACK: prevent the wrong sgh (or NULL) from being stored in the
|
|
|
|
* flow's sgh pointers */
|
|
|
|
if (PKT_IS_ICMPV4(p) && ICMPV4_DEST_UNREACH_IS_VALID(p)) {
|
|
|
|
; /* no-op */
|
|
|
|
} else {
|
|
|
|
/* store the found sgh (or NULL) in the flow to save us
|
|
|
|
* from looking it up again for the next packet.
|
|
|
|
* Also run other tasks */
|
|
|
|
DetectRunPostGetFirstRuleGroup(p, pflow, sgh);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else { /* p->flags & PKT_HAS_FLOW */
|
|
|
|
/* no flow */
|
|
|
|
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_GETSGH);
|
|
|
|
sgh = SigMatchSignaturesGetSgh(de_ctx, p);
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_GETSGH);
|
|
|
|
}
|
|
|
|
|
|
|
|
scratch->sgh = sgh;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void DetectRunInspectIPOnly(ThreadVars *tv, const DetectEngineCtx *de_ctx,
|
|
|
|
DetectEngineThreadCtx *det_ctx,
|
|
|
|
Flow * const pflow, Packet * const p)
|
|
|
|
{
|
|
|
|
if (pflow) {
|
|
|
|
/* set the iponly stuff */
|
|
|
|
if (pflow->flags & FLOW_TOCLIENT_IPONLY_SET)
|
|
|
|
p->flowflags |= FLOW_PKT_TOCLIENT_IPONLY_SET;
|
|
|
|
if (pflow->flags & FLOW_TOSERVER_IPONLY_SET)
|
|
|
|
p->flowflags |= FLOW_PKT_TOSERVER_IPONLY_SET;
|
|
|
|
|
|
|
|
if (((p->flowflags & FLOW_PKT_TOSERVER) && !(p->flowflags & FLOW_PKT_TOSERVER_IPONLY_SET)) ||
|
|
|
|
((p->flowflags & FLOW_PKT_TOCLIENT) && !(p->flowflags & FLOW_PKT_TOCLIENT_IPONLY_SET)))
|
|
|
|
{
|
|
|
|
SCLogDebug("testing against \"ip-only\" signatures");
|
|
|
|
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_IPONLY);
|
|
|
|
IPOnlyMatchPacket(tv, de_ctx, det_ctx, &de_ctx->io_ctx, &det_ctx->io_ctx, p);
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_IPONLY);
|
|
|
|
|
|
|
|
/* save in the flow that we scanned this direction... */
|
|
|
|
FlowSetIPOnlyFlag(pflow, p->flowflags & FLOW_PKT_TOSERVER ? 1 : 0);
|
|
|
|
|
|
|
|
} else if (((p->flowflags & FLOW_PKT_TOSERVER) &&
|
|
|
|
(pflow->flags & FLOW_TOSERVER_IPONLY_SET)) ||
|
|
|
|
((p->flowflags & FLOW_PKT_TOCLIENT) &&
|
|
|
|
(pflow->flags & FLOW_TOCLIENT_IPONLY_SET)))
|
|
|
|
{
|
|
|
|
/* If we have a drop from IP only module,
|
|
|
|
* we will drop the rest of the flow packets
|
|
|
|
* This will apply only to inline/IPS */
|
|
|
|
if (pflow->flags & FLOW_ACTION_DROP) {
|
|
|
|
PACKET_DROP(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else { /* p->flags & PKT_HAS_FLOW */
|
|
|
|
/* no flow */
|
|
|
|
|
|
|
|
/* Even without flow we should match the packet src/dst */
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_IPONLY);
|
|
|
|
IPOnlyMatchPacket(tv, de_ctx, det_ctx, &de_ctx->io_ctx,
|
|
|
|
&det_ctx->io_ctx, p);
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_IPONLY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* returns 0 if no match, 1 if match */
|
|
|
|
static inline int DetectRunInspectRuleHeader(
|
|
|
|
const Packet *p,
|
|
|
|
const Flow *f,
|
|
|
|
const Signature *s,
|
|
|
|
const uint32_t sflags,
|
|
|
|
const uint8_t s_proto_flags)
|
|
|
|
{
|
|
|
|
/* check if this signature has a requirement for flowvars of some type
|
|
|
|
* and if so, if we actually have any in the flow. If not, the sig
|
|
|
|
* can't match and we skip it. */
|
|
|
|
if ((p->flags & PKT_HAS_FLOW) && (sflags & SIG_FLAG_REQUIRE_FLOWVAR)) {
|
|
|
|
DEBUG_VALIDATE_BUG_ON(f == NULL);
|
|
|
|
|
|
|
|
int m = f->flowvar ? 1 : 0;
|
|
|
|
|
|
|
|
/* no flowvars? skip this sig */
|
|
|
|
if (m == 0) {
|
|
|
|
SCLogDebug("skipping sig as the flow has no flowvars and sig "
|
|
|
|
"has SIG_FLAG_REQUIRE_FLOWVAR flag set.");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((s_proto_flags & DETECT_PROTO_IPV4) && !PKT_IS_IPV4(p)) {
|
|
|
|
SCLogDebug("ip version didn't match");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if ((s_proto_flags & DETECT_PROTO_IPV6) && !PKT_IS_IPV6(p)) {
|
|
|
|
SCLogDebug("ip version didn't match");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DetectProtoContainsProto(&s->proto, IP_GET_IPPROTO(p)) == 0) {
|
|
|
|
SCLogDebug("proto didn't match");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check the source & dst port in the sig */
|
|
|
|
if (p->proto == IPPROTO_TCP || p->proto == IPPROTO_UDP || p->proto == IPPROTO_SCTP) {
|
|
|
|
if (!(sflags & SIG_FLAG_DP_ANY)) {
|
|
|
|
if (p->flags & PKT_IS_FRAGMENT)
|
|
|
|
return 0;
|
|
|
|
DetectPort *dport = DetectPortLookupGroup(s->dp,p->dp);
|
|
|
|
if (dport == NULL) {
|
|
|
|
SCLogDebug("dport didn't match.");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!(sflags & SIG_FLAG_SP_ANY)) {
|
|
|
|
if (p->flags & PKT_IS_FRAGMENT)
|
|
|
|
return 0;
|
|
|
|
DetectPort *sport = DetectPortLookupGroup(s->sp,p->sp);
|
|
|
|
if (sport == NULL) {
|
|
|
|
SCLogDebug("sport didn't match.");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if ((sflags & (SIG_FLAG_DP_ANY|SIG_FLAG_SP_ANY)) != (SIG_FLAG_DP_ANY|SIG_FLAG_SP_ANY)) {
|
|
|
|
SCLogDebug("port-less protocol and sig needs ports");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check the destination address */
|
|
|
|
if (!(sflags & SIG_FLAG_DST_ANY)) {
|
|
|
|
if (PKT_IS_IPV4(p)) {
|
|
|
|
if (DetectAddressMatchIPv4(s->addr_dst_match4, s->addr_dst_match4_cnt, &p->dst) == 0)
|
|
|
|
return 0;
|
|
|
|
} else if (PKT_IS_IPV6(p)) {
|
|
|
|
if (DetectAddressMatchIPv6(s->addr_dst_match6, s->addr_dst_match6_cnt, &p->dst) == 0)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* check the source address */
|
|
|
|
if (!(sflags & SIG_FLAG_SRC_ANY)) {
|
|
|
|
if (PKT_IS_IPV4(p)) {
|
|
|
|
if (DetectAddressMatchIPv4(s->addr_src_match4, s->addr_src_match4_cnt, &p->src) == 0)
|
|
|
|
return 0;
|
|
|
|
} else if (PKT_IS_IPV6(p)) {
|
|
|
|
if (DetectAddressMatchIPv6(s->addr_src_match6, s->addr_src_match6_cnt, &p->src) == 0)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \internal
|
|
|
|
* \brief run packet/stream prefilter engines
|
|
|
|
*/
|
|
|
|
static inline void DetectRunPrefilterPkt(
|
|
|
|
ThreadVars *tv,
|
|
|
|
DetectEngineCtx *de_ctx,
|
|
|
|
DetectEngineThreadCtx *det_ctx,
|
|
|
|
Packet *p,
|
|
|
|
DetectRunScratchpad *scratch
|
|
|
|
)
|
|
|
|
{
|
|
|
|
DetectPrefilterSetNonPrefilterList(p, det_ctx, scratch);
|
|
|
|
|
|
|
|
/* create our prefilter mask */
|
|
|
|
PacketCreateMask(p, &scratch->pkt_mask, scratch->alproto, scratch->app_decoder_events);
|
|
|
|
|
|
|
|
/* build and prefilter non_pf list against the mask of the packet */
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_NONMPMLIST);
|
|
|
|
det_ctx->non_pf_id_cnt = 0;
|
|
|
|
if (likely(det_ctx->non_pf_store_cnt > 0)) {
|
|
|
|
DetectPrefilterBuildNonPrefilterList(det_ctx, scratch->pkt_mask, scratch->alproto);
|
|
|
|
}
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_NONMPMLIST);
|
|
|
|
|
|
|
|
/* run the prefilter engines */
|
|
|
|
Prefilter(det_ctx, scratch->sgh, p, scratch->flow_flags);
|
|
|
|
/* create match list if we have non-pf and/or pf */
|
|
|
|
if (det_ctx->non_pf_store_cnt || det_ctx->pmq.rule_id_array_cnt) {
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_PF_SORT2);
|
|
|
|
DetectPrefilterMergeSort(de_ctx, det_ctx);
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_PF_SORT2);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef PROFILING
|
|
|
|
if (tv) {
|
|
|
|
StatsAddUI64(tv, det_ctx->counter_mpm_list,
|
|
|
|
(uint64_t)det_ctx->pmq.rule_id_array_cnt);
|
|
|
|
StatsAddUI64(tv, det_ctx->counter_nonmpm_list,
|
|
|
|
(uint64_t)det_ctx->non_pf_store_cnt);
|
|
|
|
/* non mpm sigs after mask prefilter */
|
|
|
|
StatsAddUI64(tv, det_ctx->counter_fnonmpm_list,
|
|
|
|
(uint64_t)det_ctx->non_pf_id_cnt);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void DetectRulePacketRules(
|
|
|
|
ThreadVars * const tv,
|
|
|
|
DetectEngineCtx * const de_ctx,
|
|
|
|
DetectEngineThreadCtx * const det_ctx,
|
|
|
|
Packet * const p,
|
|
|
|
Flow * const pflow,
|
|
|
|
const DetectRunScratchpad *scratch
|
|
|
|
)
|
|
|
|
{
|
|
|
|
const Signature *s = NULL;
|
|
|
|
const Signature *next_s = NULL;
|
|
|
|
|
|
|
|
/* inspect the sigs against the packet */
|
|
|
|
/* Prefetch the next signature. */
|
|
|
|
SigIntId match_cnt = det_ctx->match_array_cnt;
|
|
|
|
#ifdef PROFILING
|
|
|
|
if (tv) {
|
|
|
|
StatsAddUI64(tv, det_ctx->counter_match_list,
|
|
|
|
(uint64_t)match_cnt);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
Signature **match_array = det_ctx->match_array;
|
|
|
|
|
|
|
|
SGH_PROFILING_RECORD(det_ctx, scratch->sgh);
|
|
|
|
#ifdef PROFILING
|
|
|
|
if (match_cnt >= de_ctx->profile_match_logging_threshold)
|
|
|
|
RulesDumpMatchArray(det_ctx, scratch->sgh, p);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
uint32_t sflags, next_sflags = 0;
|
|
|
|
if (match_cnt) {
|
|
|
|
next_s = *match_array++;
|
|
|
|
next_sflags = next_s->flags;
|
|
|
|
}
|
|
|
|
while (match_cnt--) {
|
|
|
|
RULE_PROFILING_START(p);
|
|
|
|
uint8_t alert_flags = 0;
|
|
|
|
bool state_alert = false;
|
|
|
|
#ifdef PROFILING
|
|
|
|
bool smatch = false; /* signature match */
|
|
|
|
#endif
|
|
|
|
s = next_s;
|
|
|
|
sflags = next_sflags;
|
|
|
|
if (match_cnt) {
|
|
|
|
next_s = *match_array++;
|
|
|
|
next_sflags = next_s->flags;
|
|
|
|
}
|
|
|
|
const uint8_t s_proto_flags = s->proto.flags;
|
|
|
|
|
|
|
|
SCLogDebug("inspecting signature id %"PRIu32"", s->id);
|
|
|
|
|
|
|
|
if (s->app_inspect != NULL) {
|
|
|
|
goto next; // handle sig in DetectRunTx
|
|
|
|
}
|
|
|
|
|
|
|
|
/* don't run mask check for stateful rules.
|
|
|
|
* There we depend on prefilter */
|
|
|
|
if ((s->mask & scratch->pkt_mask) != s->mask) {
|
|
|
|
SCLogDebug("mask mismatch %x & %x != %x", s->mask, scratch->pkt_mask, s->mask);
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unlikely(sflags & SIG_FLAG_DSIZE)) {
|
|
|
|
if (likely(p->payload_len < s->dsize_low || p->payload_len > s->dsize_high)) {
|
|
|
|
SCLogDebug("kicked out as p->payload_len %u, dsize low %u, hi %u",
|
|
|
|
p->payload_len, s->dsize_low, s->dsize_high);
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if the sig has alproto and the session as well they should match */
|
|
|
|
if (likely(sflags & SIG_FLAG_APPLAYER)) {
|
|
|
|
if (s->alproto != ALPROTO_UNKNOWN && s->alproto != scratch->alproto) {
|
|
|
|
if (s->alproto == ALPROTO_DCERPC) {
|
|
|
|
if (scratch->alproto != ALPROTO_SMB) {
|
|
|
|
SCLogDebug("DCERPC sig, alproto not SMB");
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
SCLogDebug("alproto mismatch");
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DetectRunInspectRuleHeader(p, pflow, s, sflags, s_proto_flags) == 0) {
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DetectEnginePktInspectionRun(tv, det_ctx, s, pflow, p, &alert_flags) == false) {
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef PROFILING
|
|
|
|
smatch = true;
|
|
|
|
#endif
|
|
|
|
DetectRunPostMatch(tv, det_ctx, p, s);
|
|
|
|
|
|
|
|
if (!(sflags & SIG_FLAG_NOALERT)) {
|
|
|
|
/* stateful sigs call PacketAlertAppend from DeStateDetectStartDetection */
|
|
|
|
if (!state_alert)
|
|
|
|
PacketAlertAppend(det_ctx, s, p, 0, alert_flags);
|
|
|
|
} else {
|
|
|
|
/* apply actions even if not alerting */
|
|
|
|
DetectSignatureApplyActions(p, s, alert_flags);
|
|
|
|
}
|
|
|
|
next:
|
|
|
|
DetectVarProcessList(det_ctx, pflow, p);
|
|
|
|
DetectReplaceFree(det_ctx);
|
|
|
|
RULE_PROFILING_END(det_ctx, s, smatch, p);
|
|
|
|
|
|
|
|
det_ctx->flags = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static DetectRunScratchpad DetectRunSetup(
|
|
|
|
const DetectEngineCtx *de_ctx,
|
|
|
|
DetectEngineThreadCtx *det_ctx,
|
|
|
|
Packet * const p, Flow * const pflow)
|
|
|
|
{
|
|
|
|
AppProto alproto = ALPROTO_UNKNOWN;
|
|
|
|
uint8_t flow_flags = 0; /* flow/state flags */
|
|
|
|
bool app_decoder_events = false;
|
|
|
|
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_SETUP);
|
|
|
|
|
|
|
|
#ifdef UNITTESTS
|
|
|
|
p->alerts.cnt = 0;
|
|
|
|
#endif
|
|
|
|
det_ctx->ticker++;
|
|
|
|
det_ctx->filestore_cnt = 0;
|
|
|
|
det_ctx->base64_decoded_len = 0;
|
|
|
|
det_ctx->raw_stream_progress = 0;
|
|
|
|
det_ctx->match_array_cnt = 0;
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
if (p->flags & PKT_STREAM_ADD) {
|
|
|
|
det_ctx->pkt_stream_add_cnt++;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* grab the protocol state we will detect on */
|
|
|
|
if (p->flags & PKT_HAS_FLOW) {
|
|
|
|
DEBUG_VALIDATE_BUG_ON(pflow == NULL);
|
|
|
|
|
|
|
|
if (p->flowflags & FLOW_PKT_TOSERVER) {
|
|
|
|
flow_flags = STREAM_TOSERVER;
|
|
|
|
SCLogDebug("flag STREAM_TOSERVER set");
|
|
|
|
} else if (p->flowflags & FLOW_PKT_TOCLIENT) {
|
|
|
|
flow_flags = STREAM_TOCLIENT;
|
|
|
|
SCLogDebug("flag STREAM_TOCLIENT set");
|
|
|
|
}
|
|
|
|
SCLogDebug("p->flowflags 0x%02x", p->flowflags);
|
|
|
|
|
|
|
|
if (p->flags & PKT_STREAM_EOF) {
|
|
|
|
flow_flags |= STREAM_EOF;
|
|
|
|
SCLogDebug("STREAM_EOF set");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* store tenant_id in the flow so that we can use it
|
|
|
|
* for creating pseudo packets */
|
|
|
|
if (p->tenant_id > 0 && pflow->tenant_id == 0) {
|
|
|
|
pflow->tenant_id = p->tenant_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* live ruleswap check for flow updates */
|
|
|
|
if (pflow->de_ctx_version == 0) {
|
|
|
|
/* first time this flow is inspected, set id */
|
|
|
|
pflow->de_ctx_version = de_ctx->version;
|
|
|
|
} else if (pflow->de_ctx_version != de_ctx->version) {
|
|
|
|
/* first time we inspect flow with this de_ctx, reset */
|
|
|
|
pflow->flags &= ~FLOW_SGH_TOSERVER;
|
|
|
|
pflow->flags &= ~FLOW_SGH_TOCLIENT;
|
|
|
|
pflow->sgh_toserver = NULL;
|
|
|
|
pflow->sgh_toclient = NULL;
|
|
|
|
|
|
|
|
pflow->de_ctx_version = de_ctx->version;
|
|
|
|
GenericVarFree(pflow->flowvar);
|
|
|
|
pflow->flowvar = NULL;
|
|
|
|
|
|
|
|
DetectEngineStateResetTxs(pflow);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Retrieve the app layer state and protocol and the tcp reassembled
|
|
|
|
* stream chunks. */
|
|
|
|
if ((p->proto == IPPROTO_TCP && (p->flags & PKT_STREAM_EST)) ||
|
|
|
|
(p->proto == IPPROTO_UDP) ||
|
|
|
|
(p->proto == IPPROTO_SCTP && (p->flowflags & FLOW_PKT_ESTABLISHED)))
|
|
|
|
{
|
|
|
|
/* update flow flags with knowledge on disruptions */
|
|
|
|
flow_flags = FlowGetDisruptionFlags(pflow, flow_flags);
|
|
|
|
alproto = FlowGetAppProtocol(pflow);
|
|
|
|
if (p->proto == IPPROTO_TCP && pflow->protoctx &&
|
|
|
|
StreamReassembleRawHasDataReady(pflow->protoctx, p)) {
|
|
|
|
p->flags |= PKT_DETECT_HAS_STREAMDATA;
|
detect/http: flush bodies when inspecting stream
The HTTP bodies (http_client_body and http_server_body/file_data) use
settings to control how much data we have before doing first inspection:
request-body-minimal-inspect-size
response-body-minimal-inspect-size
These settings default to 32k as quite some existing rules need this.
At the same time, the 'raw stream' inspection uses its own limits. By
default it inspects the data in blocks of about 2.5k. This could lead
to a situation where rules would not match.
For example, with 2 rules like this:
content:"abc"; content:"data="; http_client_body; depth:5; sid:1;
content:"xyz"; sid:2;
Sid 1 would only be inspected when the POST body reached the 32k limit
or when it was complete. Observed case shows the POST body to be 18k.
Sid 2 is inspected as soon as the 2.5k limit is reached, and then again
for each 2.5k increment. This moves the raw stream tracker forward.
So by the time sid 1 is inspected, some 18/19k into the stream, the
raw stream tracker is actually already moved forward for approximately
17.5k, this leads to the stream match of sid 1 possibly not matching.
Since the body match is at the start of the buffer, it makes sense
that the body and stream are inspected together.
The body inspection uses a tracker 'body_inspected', that keeps track
of how far into the body both MPM and per signature inspection has
moved.
This patch updates the logic in 2 ways:
1. it triggers earlier HTTP body inspection, which is matched to the
stream inspection. When the detection engine finds it has stream
data available for inspection, it passes the new 'STREAM_FLUSH'
flag to the HTTP body inspection code. Which will then do an
early inspection, even if still before the min inspect size.
2. to still somewhat adhere to the min inspect size, the body
tracker is not updated until the min inspect size is reached.
This will lead to some re-evaluation of the same body data.
If raw stream reassembly is disabled, this 'STREAM_FLUSH' flag is
never set, and the old behavior is used.
Bug #2522.
7 years ago
|
|
|
flow_flags |= STREAM_FLUSH;
|
|
|
|
}
|
|
|
|
SCLogDebug("alproto %u", alproto);
|
|
|
|
} else {
|
|
|
|
SCLogDebug("packet doesn't have established flag set (proto %d)", p->proto);
|
|
|
|
}
|
|
|
|
|
|
|
|
app_decoder_events = AppLayerParserHasDecoderEvents(pflow->alparser);
|
|
|
|
}
|
|
|
|
|
|
|
|
DetectRunScratchpad pad = { alproto, flow_flags, app_decoder_events, NULL, 0 };
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_SETUP);
|
|
|
|
return pad;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void DetectRunPostRules(
|
|
|
|
ThreadVars *tv,
|
|
|
|
DetectEngineCtx *de_ctx,
|
|
|
|
DetectEngineThreadCtx *det_ctx,
|
|
|
|
Packet * const p,
|
|
|
|
Flow * const pflow,
|
|
|
|
DetectRunScratchpad *scratch)
|
|
|
|
{
|
|
|
|
/* see if we need to increment the inspect_id and reset the de_state */
|
|
|
|
if (pflow && pflow->alstate) {
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_TX_UPDATE);
|
|
|
|
DeStateUpdateInspectTransactionId(pflow, scratch->flow_flags, (scratch->sgh == NULL));
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_TX_UPDATE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* so now let's iterate the alerts and remove the ones after a pass rule
|
|
|
|
* matched (if any). This is done inside PacketAlertFinalize() */
|
|
|
|
/* PR: installed "tag" keywords are handled after the threshold inspection */
|
|
|
|
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_ALERT);
|
|
|
|
PacketAlertFinalize(de_ctx, det_ctx, p);
|
|
|
|
if (p->alerts.cnt > 0) {
|
|
|
|
StatsAddUI64(tv, det_ctx->counter_alerts, (uint64_t)p->alerts.cnt);
|
|
|
|
}
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_ALERT);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void DetectRunCleanup(DetectEngineThreadCtx *det_ctx,
|
|
|
|
Packet *p, Flow * const pflow)
|
|
|
|
{
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_CLEANUP);
|
|
|
|
/* cleanup pkt specific part of the patternmatcher */
|
|
|
|
PacketPatternCleanup(det_ctx);
|
|
|
|
InspectionBufferClean(det_ctx);
|
|
|
|
|
|
|
|
if (pflow != NULL) {
|
|
|
|
/* update inspected tracker for raw reassembly */
|
|
|
|
if (p->proto == IPPROTO_TCP && pflow->protoctx != NULL &&
|
|
|
|
(p->flags & PKT_STREAM_EST))
|
|
|
|
{
|
|
|
|
StreamReassembleRawUpdateProgress(pflow->protoctx, p,
|
|
|
|
det_ctx->raw_stream_progress);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_CLEANUP);
|
|
|
|
SCReturn;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RuleMatchCandidateTxArrayInit(DetectEngineThreadCtx *det_ctx, uint32_t size)
|
|
|
|
{
|
|
|
|
DEBUG_VALIDATE_BUG_ON(det_ctx->tx_candidates);
|
|
|
|
det_ctx->tx_candidates = SCCalloc(size, sizeof(RuleMatchCandidateTx));
|
|
|
|
if (det_ctx->tx_candidates == NULL) {
|
|
|
|
FatalError(SC_ERR_MEM_ALLOC, "failed to allocate %"PRIu64" bytes",
|
|
|
|
(uint64_t)(size * sizeof(RuleMatchCandidateTx)));
|
|
|
|
}
|
|
|
|
det_ctx->tx_candidates_size = size;
|
|
|
|
SCLogDebug("array initialized to %u elements (%"PRIu64" bytes)",
|
|
|
|
size, (uint64_t)(size * sizeof(RuleMatchCandidateTx)));
|
|
|
|
}
|
|
|
|
|
|
|
|
void RuleMatchCandidateTxArrayFree(DetectEngineThreadCtx *det_ctx)
|
|
|
|
{
|
|
|
|
SCFree(det_ctx->tx_candidates);
|
|
|
|
det_ctx->tx_candidates_size = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if size >= cur_space */
|
|
|
|
static inline bool RuleMatchCandidateTxArrayHasSpace(const DetectEngineThreadCtx *det_ctx,
|
|
|
|
const uint32_t need)
|
|
|
|
{
|
|
|
|
if (det_ctx->tx_candidates_size >= need)
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* realloc */
|
|
|
|
static int RuleMatchCandidateTxArrayExpand(DetectEngineThreadCtx *det_ctx, const uint32_t needed)
|
|
|
|
{
|
|
|
|
const uint32_t old_size = det_ctx->tx_candidates_size;
|
|
|
|
uint32_t new_size = needed;
|
|
|
|
void *ptmp = SCRealloc(det_ctx->tx_candidates, (new_size * sizeof(RuleMatchCandidateTx)));
|
|
|
|
if (ptmp == NULL) {
|
|
|
|
FatalError(SC_ERR_MEM_ALLOC, "failed to expand to %"PRIu64" bytes",
|
|
|
|
(uint64_t)(new_size * sizeof(RuleMatchCandidateTx)));
|
|
|
|
// TODO can this be handled more gracefully?
|
|
|
|
}
|
|
|
|
det_ctx->tx_candidates = ptmp;
|
|
|
|
det_ctx->tx_candidates_size = new_size;
|
|
|
|
SCLogDebug("array expanded from %u to %u elements (%"PRIu64" bytes -> %"PRIu64" bytes)",
|
|
|
|
old_size, new_size, (uint64_t)(old_size * sizeof(RuleMatchCandidateTx)),
|
|
|
|
(uint64_t)(new_size * sizeof(RuleMatchCandidateTx))); (void)old_size;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* TODO maybe let one with flags win if equal? */
|
|
|
|
static int
|
|
|
|
DetectRunTxSortHelper(const void *a, const void *b)
|
|
|
|
{
|
|
|
|
const RuleMatchCandidateTx *s0 = a;
|
|
|
|
const RuleMatchCandidateTx *s1 = b;
|
|
|
|
if (s1->id == s0->id)
|
|
|
|
return 0;
|
|
|
|
else
|
|
|
|
return s0->id > s1->id ? -1 : 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
#define TRACE_SID_TXS(sid,txs,...) \
|
|
|
|
do { \
|
|
|
|
char _trace_buf[2048]; \
|
|
|
|
snprintf(_trace_buf, sizeof(_trace_buf), __VA_ARGS__); \
|
|
|
|
SCLogNotice("%p/%"PRIu64"/%u: %s", txs->tx_ptr, txs->tx_id, sid, _trace_buf); \
|
|
|
|
} while(0)
|
|
|
|
#else
|
|
|
|
#define TRACE_SID_TXS(sid,txs,...)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/** \internal
|
|
|
|
* \brief inspect a rule against a transaction
|
|
|
|
*
|
|
|
|
* Inspect a rule. New detection or continued stateful
|
|
|
|
* detection.
|
|
|
|
*
|
|
|
|
* \param stored_flags pointer to stored flags or NULL.
|
|
|
|
* If stored_flags is set it means we're continueing
|
|
|
|
* inspection from an earlier run.
|
|
|
|
*
|
|
|
|
* \retval bool true sig matched, false didn't match
|
|
|
|
*/
|
|
|
|
static bool DetectRunTxInspectRule(ThreadVars *tv,
|
|
|
|
DetectEngineCtx *de_ctx,
|
|
|
|
DetectEngineThreadCtx *det_ctx,
|
|
|
|
Packet *p,
|
|
|
|
Flow *f,
|
|
|
|
const uint8_t in_flow_flags, // direction, EOF, etc
|
|
|
|
void *alstate,
|
|
|
|
DetectTransaction *tx,
|
|
|
|
const Signature *s,
|
|
|
|
uint32_t *stored_flags,
|
|
|
|
RuleMatchCandidateTx *can,
|
|
|
|
DetectRunScratchpad *scratch)
|
|
|
|
{
|
|
|
|
uint8_t flow_flags = in_flow_flags;
|
|
|
|
const int direction = (flow_flags & STREAM_TOSERVER) ? 0 : 1;
|
|
|
|
uint32_t inspect_flags = stored_flags ? *stored_flags : 0;
|
|
|
|
int total_matches = 0;
|
|
|
|
int file_no_match = 0;
|
|
|
|
bool retval = false;
|
|
|
|
bool mpm_before_progress = false; // is mpm engine before progress?
|
|
|
|
bool mpm_in_progress = false; // is mpm engine in a buffer we will revisit?
|
|
|
|
|
|
|
|
/* see if we want to pass on the FLUSH flag */
|
|
|
|
if ((s->flags & SIG_FLAG_FLUSH) == 0)
|
|
|
|
flow_flags &=~ STREAM_FLUSH;
|
|
|
|
|
|
|
|
TRACE_SID_TXS(s->id, tx, "starting %s", direction ? "toclient" : "toserver");
|
|
|
|
TRACE_SID_TXS(s->id, tx, "FLUSH? %s", (flow_flags & STREAM_FLUSH)?"true":"false");
|
|
|
|
|
|
|
|
/* for a new inspection we inspect pkt header and packet matches */
|
|
|
|
if (likely(stored_flags == NULL)) {
|
|
|
|
TRACE_SID_TXS(s->id, tx, "first inspect, run packet matches");
|
|
|
|
if (DetectRunInspectRuleHeader(p, f, s, s->flags, s->proto.flags) == 0) {
|
|
|
|
TRACE_SID_TXS(s->id, tx, "DetectRunInspectRuleHeader() no match");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (DetectEnginePktInspectionRun(tv, det_ctx, s, f, p, NULL) == false) {
|
|
|
|
TRACE_SID_TXS(s->id, tx, "DetectEnginePktInspectionRun no match");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
/* stream mpm and negated mpm sigs can end up here with wrong proto */
|
|
|
|
if (!(f->alproto == s->alproto || s->alproto == ALPROTO_UNKNOWN)) {
|
|
|
|
TRACE_SID_TXS(s->id, tx, "alproto mismatch");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const DetectEngineAppInspectionEngine *engine = s->app_inspect;
|
|
|
|
while (engine != NULL) { // TODO could be do {} while as s->app_inspect cannot be null
|
|
|
|
TRACE_SID_TXS(s->id, tx, "engine %p inspect_flags %x", engine, inspect_flags);
|
|
|
|
if (!(inspect_flags & BIT_U32(engine->id)) &&
|
|
|
|
direction == engine->dir)
|
|
|
|
{
|
|
|
|
const bool skip_engine = (engine->alproto != 0 && engine->alproto != f->alproto);
|
|
|
|
/* special case: file_data on 'alert tcp' will have engines
|
|
|
|
* in the list that are not for us. */
|
|
|
|
if (unlikely(skip_engine)) {
|
|
|
|
engine = engine->next;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* engines are sorted per progress, except that the one with
|
|
|
|
* mpm/prefilter enabled is first */
|
|
|
|
if (tx->tx_progress < engine->progress) {
|
|
|
|
SCLogDebug("tx progress %d < engine progress %d",
|
|
|
|
tx->tx_progress, engine->progress);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (engine->mpm) {
|
|
|
|
if (tx->tx_progress > engine->progress) {
|
|
|
|
mpm_before_progress = true;
|
|
|
|
} else if (tx->tx_progress == engine->progress) {
|
|
|
|
mpm_in_progress = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* run callback: but bypass stream callback if we can */
|
|
|
|
int match;
|
|
|
|
if (unlikely(engine->stream && can->stream_stored)) {
|
|
|
|
match = can->stream_result;
|
|
|
|
TRACE_SID_TXS(s->id, tx, "stream skipped, stored result %d used instead", match);
|
|
|
|
} else {
|
|
|
|
KEYWORD_PROFILING_SET_LIST(det_ctx, engine->sm_list);
|
|
|
|
if (engine->Callback) {
|
|
|
|
match = engine->Callback(tv, de_ctx, det_ctx,
|
|
|
|
s, engine->smd, f, flow_flags, alstate, tx->tx_ptr, tx->tx_id);
|
|
|
|
} else {
|
|
|
|
BUG_ON(engine->v2.Callback == NULL);
|
|
|
|
match = engine->v2.Callback(de_ctx, det_ctx, engine,
|
|
|
|
s, f, flow_flags, alstate, tx->tx_ptr, tx->tx_id);
|
|
|
|
}
|
|
|
|
TRACE_SID_TXS(s->id, tx, "engine %p match %d", engine, match);
|
|
|
|
if (engine->stream) {
|
|
|
|
can->stream_stored = true;
|
|
|
|
can->stream_result = match;
|
|
|
|
TRACE_SID_TXS(s->id, tx, "stream ran, store result %d for next tx (if any)", match);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (match == DETECT_ENGINE_INSPECT_SIG_MATCH) {
|
|
|
|
inspect_flags |= BIT_U32(engine->id);
|
|
|
|
engine = engine->next;
|
|
|
|
total_matches++;
|
|
|
|
continue;
|
|
|
|
} else if (match == DETECT_ENGINE_INSPECT_SIG_MATCH_MORE_FILES) {
|
|
|
|
/* if the file engine matched, but indicated more
|
|
|
|
* files are still in progress, we don't set inspect
|
|
|
|
* flags as these would end inspection for this tx */
|
|
|
|
engine = engine->next;
|
|
|
|
total_matches++;
|
|
|
|
continue;
|
|
|
|
} else if (match == DETECT_ENGINE_INSPECT_SIG_CANT_MATCH) {
|
|
|
|
inspect_flags |= DE_STATE_FLAG_SIG_CANT_MATCH;
|
|
|
|
inspect_flags |= BIT_U32(engine->id);
|
|
|
|
} else if (match == DETECT_ENGINE_INSPECT_SIG_CANT_MATCH_FILES) {
|
|
|
|
inspect_flags |= DE_STATE_FLAG_SIG_CANT_MATCH;
|
|
|
|
inspect_flags |= BIT_U32(engine->id);
|
|
|
|
file_no_match = 1;
|
|
|
|
}
|
|
|
|
/* implied DETECT_ENGINE_INSPECT_SIG_NO_MATCH */
|
|
|
|
if (engine->mpm && mpm_before_progress) {
|
|
|
|
inspect_flags |= DE_STATE_FLAG_SIG_CANT_MATCH;
|
|
|
|
inspect_flags |= BIT_U32(engine->id);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
engine = engine->next;
|
|
|
|
}
|
|
|
|
TRACE_SID_TXS(s->id, tx, "inspect_flags %x, total_matches %u, engine %p",
|
|
|
|
inspect_flags, total_matches, engine);
|
|
|
|
|
|
|
|
if (engine == NULL && total_matches) {
|
|
|
|
inspect_flags |= DE_STATE_FLAG_FULL_INSPECT;
|
|
|
|
TRACE_SID_TXS(s->id, tx, "MATCH");
|
|
|
|
retval = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stored_flags) {
|
|
|
|
*stored_flags = inspect_flags;
|
|
|
|
TRACE_SID_TXS(s->id, tx, "continue inspect flags %08x", inspect_flags);
|
|
|
|
} else {
|
|
|
|
// store... or? If tx is done we might not want to come back to this tx
|
|
|
|
|
|
|
|
// also... if mpmid tracking is enabled, we won't do a sig again for this tx...
|
|
|
|
TRACE_SID_TXS(s->id, tx, "start inspect flags %08x", inspect_flags);
|
|
|
|
if (inspect_flags & DE_STATE_FLAG_SIG_CANT_MATCH) {
|
|
|
|
if (file_no_match) {
|
|
|
|
/* if we have a mismatch on a file sig, we need to keep state.
|
|
|
|
* We may get another file on the same tx (for http and smtp
|
|
|
|
* at least), so for a new file we need to re-eval the sig.
|
|
|
|
* Thoughts / TODO:
|
|
|
|
* - not for some protos that have 1 file per tx (e.g. nfs)
|
|
|
|
* - maybe we only need this for file sigs that mix with
|
|
|
|
* other matches? E.g. 'POST + filename', is different than
|
|
|
|
* just 'filename'.
|
|
|
|
*/
|
|
|
|
DetectRunStoreStateTx(scratch->sgh, f, tx->tx_ptr, tx->tx_id, s,
|
|
|
|
inspect_flags, flow_flags, file_no_match);
|
|
|
|
}
|
|
|
|
} else if ((inspect_flags & DE_STATE_FLAG_FULL_INSPECT) && mpm_before_progress) {
|
|
|
|
TRACE_SID_TXS(s->id, tx, "no need to store match sig, "
|
|
|
|
"mpm won't trigger for it anymore");
|
|
|
|
|
|
|
|
if (inspect_flags & DE_STATE_FLAG_FILE_INSPECT) {
|
|
|
|
TRACE_SID_TXS(s->id, tx, "except that for new files, "
|
|
|
|
"we may have to revisit anyway");
|
|
|
|
DetectRunStoreStateTx(scratch->sgh, f, tx->tx_ptr, tx->tx_id, s,
|
|
|
|
inspect_flags, flow_flags, file_no_match);
|
|
|
|
}
|
|
|
|
} else if ((inspect_flags & DE_STATE_FLAG_FULL_INSPECT) == 0 && mpm_in_progress) {
|
|
|
|
TRACE_SID_TXS(s->id, tx, "no need to store no-match sig, "
|
|
|
|
"mpm will revisit it");
|
|
|
|
} else {
|
|
|
|
TRACE_SID_TXS(s->id, tx, "storing state: flags %08x", inspect_flags);
|
|
|
|
DetectRunStoreStateTx(scratch->sgh, f, tx->tx_ptr, tx->tx_id, s,
|
|
|
|
inspect_flags, flow_flags, file_no_match);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \internal
|
|
|
|
* \brief get a DetectTransaction object
|
|
|
|
* \retval struct filled with relevant info or all nulls/0s
|
|
|
|
*/
|
|
|
|
static DetectTransaction GetDetectTx(const uint8_t ipproto, const AppProto alproto,
|
|
|
|
void *alstate, const uint64_t tx_id, void *tx_ptr, const int tx_end_state,
|
|
|
|
const uint8_t flow_flags)
|
|
|
|
{
|
|
|
|
uint64_t detect_flags;
|
|
|
|
AppLayerTxData *txd = AppLayerParserGetTxData(ipproto, alproto, tx_ptr);
|
|
|
|
if (likely(txd != NULL)) {
|
|
|
|
detect_flags = (flow_flags & STREAM_TOSERVER) ? txd->detect_flags_ts : txd->detect_flags_tc;
|
|
|
|
} else {
|
|
|
|
detect_flags = 0;
|
|
|
|
}
|
|
|
|
if (detect_flags & APP_LAYER_TX_INSPECTED_FLAG) {
|
|
|
|
SCLogDebug("%"PRIu64" tx already fully inspected for %s. Flags %016"PRIx64,
|
|
|
|
tx_id, flow_flags & STREAM_TOSERVER ? "toserver" : "toclient",
|
|
|
|
detect_flags);
|
|
|
|
DetectTransaction no_tx = { NULL, 0, NULL, NULL, 0, 0, 0, 0, 0, };
|
|
|
|
return no_tx;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int tx_progress = AppLayerParserGetStateProgress(ipproto, alproto, tx_ptr, flow_flags);
|
|
|
|
const int dir_int = (flow_flags & STREAM_TOSERVER) ? 0 : 1;
|
|
|
|
DetectEngineState *tx_de_state = AppLayerParserGetTxDetectState(ipproto, alproto, tx_ptr);
|
|
|
|
DetectEngineStateDirection *tx_dir_state = tx_de_state ? &tx_de_state->dir_state[dir_int] : NULL;
|
|
|
|
uint64_t prefilter_flags = detect_flags & APP_LAYER_TX_PREFILTER_MASK;
|
|
|
|
|
|
|
|
DetectTransaction tx = {
|
|
|
|
.tx_ptr = tx_ptr,
|
|
|
|
.tx_id = tx_id,
|
|
|
|
.tx_data_ptr = (struct AppLayerTxData *)txd,
|
|
|
|
.de_state = tx_dir_state,
|
|
|
|
.detect_flags = detect_flags,
|
|
|
|
.prefilter_flags = prefilter_flags,
|
|
|
|
.prefilter_flags_orig = prefilter_flags,
|
|
|
|
.tx_progress = tx_progress,
|
|
|
|
.tx_end_state = tx_end_state,
|
|
|
|
};
|
|
|
|
return tx;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void StoreDetectFlags(DetectTransaction *tx, const uint8_t flow_flags,
|
|
|
|
const uint8_t ipproto, const AppProto alproto, const uint64_t detect_flags)
|
|
|
|
{
|
|
|
|
AppLayerTxData *txd = (AppLayerTxData *)tx->tx_data_ptr;
|
|
|
|
if (likely(txd != NULL)) {
|
|
|
|
if (flow_flags & STREAM_TOSERVER) {
|
|
|
|
txd->detect_flags_ts = detect_flags;
|
|
|
|
} else {
|
|
|
|
txd->detect_flags_tc = detect_flags;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void DetectRunTx(ThreadVars *tv,
|
|
|
|
DetectEngineCtx *de_ctx,
|
|
|
|
DetectEngineThreadCtx *det_ctx,
|
|
|
|
Packet *p,
|
|
|
|
Flow *f,
|
|
|
|
DetectRunScratchpad *scratch)
|
|
|
|
{
|
|
|
|
const uint8_t flow_flags = scratch->flow_flags;
|
|
|
|
const SigGroupHead * const sgh = scratch->sgh;
|
|
|
|
void * const alstate = f->alstate;
|
|
|
|
const uint8_t ipproto = f->proto;
|
|
|
|
const AppProto alproto = f->alproto;
|
|
|
|
|
|
|
|
const uint64_t total_txs = AppLayerParserGetTxCnt(f, alstate);
|
|
|
|
uint64_t tx_id_min = AppLayerParserGetTransactionInspectId(f->alparser, flow_flags);
|
|
|
|
const int tx_end_state = AppLayerParserGetStateProgressCompletionStatus(alproto, flow_flags);
|
|
|
|
|
|
|
|
AppLayerGetTxIteratorFunc IterFunc = AppLayerGetTxIterator(ipproto, alproto);
|
|
|
|
AppLayerGetTxIterState state;
|
|
|
|
memset(&state, 0, sizeof(state));
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
AppLayerGetTxIterTuple ires = IterFunc(ipproto, alproto, alstate, tx_id_min, total_txs, &state);
|
|
|
|
if (ires.tx_ptr == NULL)
|
|
|
|
break;
|
|
|
|
|
|
|
|
DetectTransaction tx = GetDetectTx(ipproto, alproto,
|
|
|
|
alstate, ires.tx_id, ires.tx_ptr, tx_end_state, flow_flags);
|
|
|
|
if (tx.tx_ptr == NULL) {
|
|
|
|
SCLogDebug("%p/%"PRIu64" no transaction to inspect",
|
|
|
|
tx.tx_ptr, tx_id_min);
|
|
|
|
|
|
|
|
tx_id_min++; // next (if any) run look for +1
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
tx_id_min = tx.tx_id + 1; // next look for cur + 1
|
|
|
|
|
|
|
|
uint32_t array_idx = 0;
|
|
|
|
uint32_t total_rules = det_ctx->match_array_cnt;
|
|
|
|
total_rules += (tx.de_state ? tx.de_state->cnt : 0);
|
|
|
|
|
|
|
|
/* run prefilter engines and merge results into a candidates array */
|
|
|
|
if (sgh->tx_engines) {
|
|
|
|
PACKET_PROFILING_DETECT_START(p, PROF_DETECT_PF_TX);
|
|
|
|
DetectRunPrefilterTx(det_ctx, sgh, p, ipproto, flow_flags, alproto,
|
|
|
|
alstate, &tx);
|
|
|
|
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_PF_TX);
|
|
|
|
SCLogDebug("%p/%"PRIu64" rules added from prefilter: %u candidates",
|
|
|
|
tx.tx_ptr, tx.tx_id, det_ctx->pmq.rule_id_array_cnt);
|
|
|
|
|
|
|
|
total_rules += det_ctx->pmq.rule_id_array_cnt;
|
|
|
|
if (!(RuleMatchCandidateTxArrayHasSpace(det_ctx, total_rules))) {
|
|
|
|
RuleMatchCandidateTxArrayExpand(det_ctx, total_rules);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < det_ctx->pmq.rule_id_array_cnt; i++) {
|
|
|
|
const Signature *s = de_ctx->sig_array[det_ctx->pmq.rule_id_array[i]];
|
|
|
|
const SigIntId id = s->num;
|
|
|
|
det_ctx->tx_candidates[array_idx].s = s;
|
|
|
|
det_ctx->tx_candidates[array_idx].id = id;
|
|
|
|
det_ctx->tx_candidates[array_idx].flags = NULL;
|
|
|
|
det_ctx->tx_candidates[array_idx].stream_reset = 0;
|
|
|
|
array_idx++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!(RuleMatchCandidateTxArrayHasSpace(det_ctx, total_rules))) {
|
|
|
|
RuleMatchCandidateTxArrayExpand(det_ctx, total_rules);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* merge 'state' rules from the regular prefilter */
|
|
|
|
uint32_t x = array_idx;
|
|
|
|
for (uint32_t i = 0; i < det_ctx->match_array_cnt; i++) {
|
|
|
|
const Signature *s = det_ctx->match_array[i];
|
|
|
|
if (s->app_inspect != NULL) {
|
|
|
|
const SigIntId id = s->num;
|
|
|
|
det_ctx->tx_candidates[array_idx].s = s;
|
|
|
|
det_ctx->tx_candidates[array_idx].id = id;
|
|
|
|
det_ctx->tx_candidates[array_idx].flags = NULL;
|
|
|
|
det_ctx->tx_candidates[array_idx].stream_reset = 0;
|
|
|
|
array_idx++;
|
|
|
|
|
|
|
|
SCLogDebug("%p/%"PRIu64" rule %u (%u) added from 'match' list",
|
|
|
|
tx.tx_ptr, tx.tx_id, s->id, id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SCLogDebug("%p/%"PRIu64" rules added from 'match' list: %u",
|
|
|
|
tx.tx_ptr, tx.tx_id, array_idx - x); (void)x;
|
|
|
|
|
|
|
|
/* merge stored state into results */
|
|
|
|
if (tx.de_state != NULL) {
|
|
|
|
const uint32_t old = array_idx;
|
|
|
|
|
|
|
|
/* if tx.de_state->flags has 'new file' set and sig below has
|
|
|
|
* 'file inspected' flag, reset the file part of the state */
|
|
|
|
const bool have_new_file = (tx.de_state->flags & DETECT_ENGINE_STATE_FLAG_FILE_NEW);
|
|
|
|
if (have_new_file) {
|
|
|
|
SCLogDebug("%p/%"PRIu64" destate: need to consider new file",
|
|
|
|
tx.tx_ptr, tx.tx_id);
|
|
|
|
tx.de_state->flags &= ~DETECT_ENGINE_STATE_FLAG_FILE_NEW;
|
|
|
|
}
|
|
|
|
|
|
|
|
SigIntId state_cnt = 0;
|
|
|
|
DeStateStore *tx_store = tx.de_state->head;
|
|
|
|
for (; tx_store != NULL; tx_store = tx_store->next) {
|
|
|
|
SCLogDebug("tx_store %p", tx_store);
|
|
|
|
|
|
|
|
SigIntId store_cnt = 0;
|
|
|
|
for (store_cnt = 0;
|
|
|
|
store_cnt < DE_STATE_CHUNK_SIZE && state_cnt < tx.de_state->cnt;
|
|
|
|
store_cnt++, state_cnt++)
|
|
|
|
{
|
|
|
|
DeStateStoreItem *item = &tx_store->store[store_cnt];
|
|
|
|
SCLogDebug("rule id %u, inspect_flags %u", item->sid, item->flags);
|
|
|
|
if (have_new_file && (item->flags & DE_STATE_FLAG_FILE_INSPECT)) {
|
|
|
|
/* remove part of the state. File inspect engine will now
|
|
|
|
* be able to run again */
|
|
|
|
item->flags &= ~(DE_STATE_FLAG_SIG_CANT_MATCH|DE_STATE_FLAG_FULL_INSPECT|DE_STATE_FLAG_FILE_INSPECT);
|
|
|
|
SCLogDebug("rule id %u, post file reset inspect_flags %u", item->sid, item->flags);
|
|
|
|
}
|
|
|
|
det_ctx->tx_candidates[array_idx].s = de_ctx->sig_array[item->sid];
|
|
|
|
det_ctx->tx_candidates[array_idx].id = item->sid;
|
|
|
|
det_ctx->tx_candidates[array_idx].flags = &item->flags;
|
|
|
|
det_ctx->tx_candidates[array_idx].stream_reset = 0;
|
|
|
|
array_idx++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (old && old != array_idx) {
|
|
|
|
qsort(det_ctx->tx_candidates, array_idx, sizeof(RuleMatchCandidateTx),
|
|
|
|
DetectRunTxSortHelper);
|
|
|
|
|
|
|
|
SCLogDebug("%p/%"PRIu64" rules added from 'continue' list: %u",
|
|
|
|
tx.tx_ptr, tx.tx_id, array_idx - old);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
det_ctx->tx_id = tx.tx_id;
|
|
|
|
det_ctx->tx_id_set = 1;
|
|
|
|
det_ctx->p = p;
|
|
|
|
|
|
|
|
/* run rules: inspect the match candidates */
|
|
|
|
for (uint32_t i = 0; i < array_idx; i++) {
|
|
|
|
RuleMatchCandidateTx *can = &det_ctx->tx_candidates[i];
|
|
|
|
const Signature *s = det_ctx->tx_candidates[i].s;
|
|
|
|
uint32_t *inspect_flags = det_ctx->tx_candidates[i].flags;
|
|
|
|
|
|
|
|
/* deduplicate: rules_array is sorted, but not deduplicated:
|
|
|
|
* both mpm and stored state could give us the same sid.
|
|
|
|
* As they are back to back in that case we can check for it
|
|
|
|
* here. We select the stored state one. */
|
|
|
|
if ((i + 1) < array_idx) {
|
|
|
|
if (det_ctx->tx_candidates[i].s == det_ctx->tx_candidates[i+1].s) {
|
|
|
|
if (det_ctx->tx_candidates[i].flags != NULL) {
|
|
|
|
i++;
|
|
|
|
SCLogDebug("%p/%"PRIu64" inspecting SKIP NEXT: sid %u (%u), flags %08x",
|
|
|
|
tx.tx_ptr, tx.tx_id, s->id, s->num, inspect_flags ? *inspect_flags : 0);
|
|
|
|
} else if (det_ctx->tx_candidates[i+1].flags != NULL) {
|
|
|
|
SCLogDebug("%p/%"PRIu64" inspecting SKIP CURRENT: sid %u (%u), flags %08x",
|
|
|
|
tx.tx_ptr, tx.tx_id, s->id, s->num, inspect_flags ? *inspect_flags : 0);
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
// if it's all the same, inspect the current one and skip next.
|
|
|
|
i++;
|
|
|
|
SCLogDebug("%p/%"PRIu64" inspecting SKIP NEXT: sid %u (%u), flags %08x",
|
|
|
|
tx.tx_ptr, tx.tx_id, s->id, s->num, inspect_flags ? *inspect_flags : 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SCLogDebug("%p/%"PRIu64" inspecting: sid %u (%u), flags %08x",
|
|
|
|
tx.tx_ptr, tx.tx_id, s->id, s->num, inspect_flags ? *inspect_flags : 0);
|
|
|
|
|
|
|
|
if (inspect_flags) {
|
|
|
|
if (*inspect_flags & (DE_STATE_FLAG_FULL_INSPECT|DE_STATE_FLAG_SIG_CANT_MATCH)) {
|
|
|
|
SCLogDebug("%p/%"PRIu64" inspecting: sid %u (%u), flags %08x ALREADY COMPLETE",
|
|
|
|
tx.tx_ptr, tx.tx_id, s->id, s->num, *inspect_flags);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inspect_flags) {
|
|
|
|
/* continue previous inspection */
|
|
|
|
SCLogDebug("%p/%"PRIu64" Continueing sid %u", tx.tx_ptr, tx.tx_id, s->id);
|
|
|
|
} else {
|
|
|
|
/* start new inspection */
|
|
|
|
SCLogDebug("%p/%"PRIu64" Start sid %u", tx.tx_ptr, tx.tx_id, s->id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* call individual rule inspection */
|
|
|
|
RULE_PROFILING_START(p);
|
|
|
|
const int r = DetectRunTxInspectRule(tv, de_ctx, det_ctx, p, f, flow_flags,
|
|
|
|
alstate, &tx, s, inspect_flags, can, scratch);
|
|
|
|
if (r == 1) {
|
|
|
|
/* match */
|
|
|
|
DetectRunPostMatch(tv, det_ctx, p, s);
|
|
|
|
|
|
|
|
uint8_t alert_flags = (PACKET_ALERT_FLAG_STATE_MATCH|PACKET_ALERT_FLAG_TX);
|
|
|
|
if (s->action & ACTION_DROP)
|
|
|
|
alert_flags |= PACKET_ALERT_FLAG_DROP_FLOW;
|
|
|
|
|
|
|
|
SCLogDebug("%p/%"PRIu64" sig %u (%u) matched", tx.tx_ptr, tx.tx_id, s->id, s->num);
|
|
|
|
if (!(s->flags & SIG_FLAG_NOALERT)) {
|
|
|
|
PacketAlertAppend(det_ctx, s, p, tx.tx_id, alert_flags);
|
|
|
|
} else {
|
|
|
|
DetectSignatureApplyActions(p, s, alert_flags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DetectVarProcessList(det_ctx, p->flow, p);
|
|
|
|
RULE_PROFILING_END(det_ctx, s, r, p);
|
|
|
|
}
|
|
|
|
|
|
|
|
det_ctx->tx_id = 0;
|
|
|
|
det_ctx->tx_id_set = 0;
|
|
|
|
det_ctx->p = NULL;
|
|
|
|
|
|
|
|
/* see if we have any updated state to store in the tx */
|
|
|
|
|
|
|
|
uint64_t new_detect_flags = 0;
|
|
|
|
/* this side of the tx is done */
|
|
|
|
if (tx.tx_progress >= tx.tx_end_state) {
|
|
|
|
new_detect_flags |= APP_LAYER_TX_INSPECTED_FLAG;
|
|
|
|
SCLogDebug("%p/%"PRIu64" tx is done for direction %s. Flag %016"PRIx64,
|
|
|
|
tx.tx_ptr, tx.tx_id,
|
|
|
|
flow_flags & STREAM_TOSERVER ? "toserver" : "toclient",
|
|
|
|
new_detect_flags);
|
|
|
|
}
|
|
|
|
if (tx.prefilter_flags != tx.prefilter_flags_orig) {
|
|
|
|
new_detect_flags |= tx.prefilter_flags;
|
|
|
|
SCLogDebug("%p/%"PRIu64" updated prefilter flags %016"PRIx64" "
|
|
|
|
"(was: %016"PRIx64") for direction %s. Flag %016"PRIx64,
|
|
|
|
tx.tx_ptr, tx.tx_id, tx.prefilter_flags, tx.prefilter_flags_orig,
|
|
|
|
flow_flags & STREAM_TOSERVER ? "toserver" : "toclient",
|
|
|
|
new_detect_flags);
|
|
|
|
}
|
|
|
|
if (new_detect_flags != 0 &&
|
|
|
|
(new_detect_flags | tx.detect_flags) != tx.detect_flags)
|
|
|
|
{
|
|
|
|
new_detect_flags |= tx.detect_flags;
|
|
|
|
SCLogDebug("%p/%"PRIu64" Storing new flags %016"PRIx64" (was %016"PRIx64")",
|
|
|
|
tx.tx_ptr, tx.tx_id, new_detect_flags, tx.detect_flags);
|
|
|
|
|
|
|
|
StoreDetectFlags(&tx, flow_flags, ipproto, alproto, new_detect_flags);
|
|
|
|
}
|
|
|
|
next:
|
|
|
|
InspectionBufferClean(det_ctx);
|
|
|
|
|
|
|
|
if (!ires.has_next)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \brief Apply action(s) and Set 'drop' sig info,
|
|
|
|
* if applicable */
|
|
|
|
void DetectSignatureApplyActions(Packet *p,
|
|
|
|
const Signature *s, const uint8_t alert_flags)
|
|
|
|
{
|
|
|
|
PACKET_UPDATE_ACTION(p, s->action);
|
|
|
|
|
|
|
|
if (s->action & ACTION_DROP) {
|
|
|
|
if (p->alerts.drop.action == 0) {
|
|
|
|
p->alerts.drop.num = s->num;
|
|
|
|
p->alerts.drop.action = s->action;
|
|
|
|
p->alerts.drop.s = (Signature *)s;
|
|
|
|
}
|
|
|
|
} else if (s->action & ACTION_PASS) {
|
|
|
|
/* if an stream/app-layer match we enforce the pass for the flow */
|
|
|
|
if ((p->flow != NULL) &&
|
|
|
|
(alert_flags & (PACKET_ALERT_FLAG_STATE_MATCH|PACKET_ALERT_FLAG_STREAM_MATCH)))
|
|
|
|
{
|
|
|
|
FlowSetNoPacketInspectionFlag(p->flow);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static DetectEngineThreadCtx *GetTenantById(HashTable *h, uint32_t id)
|
|
|
|
{
|
|
|
|
/* technically we need to pass a DetectEngineThreadCtx struct with the
|
|
|
|
* tentant_id member. But as that member is the first in the struct, we
|
|
|
|
* can use the id directly. */
|
|
|
|
return HashTableLookup(h, &id, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void DetectFlow(ThreadVars *tv,
|
|
|
|
DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
|
|
|
|
Packet *p)
|
|
|
|
{
|
|
|
|
if (p->flags & PKT_NOPACKET_INSPECTION) {
|
|
|
|
/* hack: if we are in pass the entire flow mode, we need to still
|
|
|
|
* update the inspect_id forward. So test for the condition here,
|
|
|
|
* and call the update code if necessary. */
|
|
|
|
const int pass = ((p->flow->flags & FLOW_NOPACKET_INSPECTION));
|
|
|
|
if (pass) {
|
|
|
|
uint8_t flags;
|
|
|
|
if (p->flowflags & FLOW_PKT_TOSERVER) {
|
|
|
|
flags = STREAM_TOSERVER;
|
|
|
|
} else {
|
|
|
|
flags = STREAM_TOCLIENT;
|
|
|
|
}
|
|
|
|
flags = FlowGetDisruptionFlags(p->flow, flags);
|
|
|
|
DeStateUpdateInspectTransactionId(p->flow, flags, true);
|
|
|
|
}
|
|
|
|
SCLogDebug("p->pcap %"PRIu64": no detection on packet, "
|
|
|
|
"PKT_NOPACKET_INSPECTION is set", p->pcap_cnt);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* see if the packet matches one or more of the sigs */
|
|
|
|
(void)DetectRun(tv, de_ctx, det_ctx, p);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void DetectNoFlow(ThreadVars *tv,
|
|
|
|
DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
|
|
|
|
Packet *p)
|
|
|
|
{
|
|
|
|
/* No need to perform any detection on this packet, if the the given flag is set.*/
|
|
|
|
if ((p->flags & PKT_NOPACKET_INSPECTION) ||
|
|
|
|
(PACKET_TEST_ACTION(p, ACTION_DROP)))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* see if the packet matches one or more of the sigs */
|
|
|
|
DetectRun(tv, de_ctx, det_ctx, p);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \brief Detection engine thread wrapper.
|
|
|
|
* \param tv thread vars
|
|
|
|
* \param p packet to inspect
|
|
|
|
* \param data thread specific data
|
|
|
|
* \param pq packet queue
|
|
|
|
* \retval TM_ECODE_FAILED error
|
|
|
|
* \retval TM_ECODE_OK ok
|
|
|
|
*/
|
|
|
|
TmEcode Detect(ThreadVars *tv, Packet *p, void *data)
|
|
|
|
{
|
|
|
|
DEBUG_VALIDATE_PACKET(p);
|
|
|
|
|
|
|
|
DetectEngineCtx *de_ctx = NULL;
|
|
|
|
DetectEngineThreadCtx *det_ctx = (DetectEngineThreadCtx *)data;
|
|
|
|
if (det_ctx == NULL) {
|
|
|
|
printf("ERROR: Detect has no thread ctx\n");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unlikely(SC_ATOMIC_GET(det_ctx->so_far_used_by_detect) == 0)) {
|
|
|
|
(void)SC_ATOMIC_SET(det_ctx->so_far_used_by_detect, 1);
|
|
|
|
SCLogDebug("Detect Engine using new det_ctx - %p",
|
|
|
|
det_ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if in MT mode _and_ we have tenants registered, use
|
|
|
|
* MT logic. */
|
|
|
|
if (det_ctx->mt_det_ctxs_cnt > 0 && det_ctx->TenantGetId != NULL)
|
|
|
|
{
|
|
|
|
uint32_t tenant_id = p->tenant_id;
|
|
|
|
if (tenant_id == 0)
|
|
|
|
tenant_id = det_ctx->TenantGetId(det_ctx, p);
|
|
|
|
if (tenant_id > 0 && tenant_id < det_ctx->mt_det_ctxs_cnt) {
|
|
|
|
p->tenant_id = tenant_id;
|
|
|
|
det_ctx = GetTenantById(det_ctx->mt_det_ctxs_hash, tenant_id);
|
|
|
|
if (det_ctx == NULL)
|
|
|
|
return TM_ECODE_OK;
|
|
|
|
de_ctx = det_ctx->de_ctx;
|
|
|
|
if (de_ctx == NULL)
|
|
|
|
return TM_ECODE_OK;
|
|
|
|
|
|
|
|
if (unlikely(SC_ATOMIC_GET(det_ctx->so_far_used_by_detect) == 0)) {
|
|
|
|
(void)SC_ATOMIC_SET(det_ctx->so_far_used_by_detect, 1);
|
|
|
|
SCLogDebug("MT de_ctx %p det_ctx %p (tenant %u)", de_ctx, det_ctx, tenant_id);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* use default if no tenants are registered for this packet */
|
|
|
|
de_ctx = det_ctx->de_ctx;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
de_ctx = det_ctx->de_ctx;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p->flow) {
|
|
|
|
DetectFlow(tv, de_ctx, det_ctx, p);
|
|
|
|
} else {
|
|
|
|
DetectNoFlow(tv, de_ctx, det_ctx, p);
|
|
|
|
}
|
|
|
|
return TM_ECODE_OK;
|
|
|
|
error:
|
|
|
|
return TM_ECODE_FAILED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \brief disable file features we don't need
|
|
|
|
* Called if we have no detection engine.
|
|
|
|
*/
|
|
|
|
void DisableDetectFlowFileFlags(Flow *f)
|
|
|
|
{
|
|
|
|
DetectPostInspectFileFlagsUpdate(f, NULL /* no sgh */, STREAM_TOSERVER);
|
|
|
|
DetectPostInspectFileFlagsUpdate(f, NULL /* no sgh */, STREAM_TOCLIENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef UNITTESTS
|
|
|
|
/**
|
|
|
|
* \brief wrapper for old tests
|
|
|
|
*/
|
|
|
|
void SigMatchSignatures(ThreadVars *th_v,
|
|
|
|
DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
|
|
|
|
Packet *p)
|
|
|
|
{
|
|
|
|
DetectRun(th_v, de_ctx, det_ctx, p);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TESTS
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef UNITTESTS
|
|
|
|
#include "tests/detect.c"
|
|
|
|
#endif
|
|
|
|
|