mirror of https://github.com/OISF/suricata
detect/engine: support frames
Implement the low level detect engine support for inspecting frames, including MPM, transforms and inspect API's.pull/6809/head
parent
c0ec3984fa
commit
f6f124f283
@ -0,0 +1,357 @@
|
||||
/* Copyright (C) 2021 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>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "suricata-common.h"
|
||||
#include "suricata.h"
|
||||
|
||||
#include "app-layer-parser.h"
|
||||
#include "app-layer-frames.h"
|
||||
|
||||
#include "detect-engine.h"
|
||||
#include "detect-engine-prefilter.h"
|
||||
#include "detect-engine-content-inspection.h"
|
||||
#include "detect-engine-mpm.h"
|
||||
#include "detect-engine-frame.h"
|
||||
|
||||
#include "stream-tcp.h"
|
||||
|
||||
#include "util-profiling.h"
|
||||
#include "util-validate.h"
|
||||
#include "util-print.h"
|
||||
|
||||
void DetectRunPrefilterFrame(DetectEngineThreadCtx *det_ctx, const SigGroupHead *sgh, Packet *p,
|
||||
const Frames *frames, const Frame *frame, const AppProto alproto, const uint32_t idx)
|
||||
{
|
||||
SCLogDebug("pcap_cnt %" PRIu64, p->pcap_cnt);
|
||||
PrefilterEngine *engine = sgh->frame_engines;
|
||||
do {
|
||||
BUG_ON(engine->alproto == ALPROTO_UNKNOWN);
|
||||
if (engine->alproto == alproto && engine->ctx.frame_type == frame->type) {
|
||||
SCLogDebug("frame %p engine %p", frame, engine);
|
||||
PREFILTER_PROFILING_START;
|
||||
engine->cb.PrefilterFrame(det_ctx, engine->pectx, p, frames, frame, idx);
|
||||
PREFILTER_PROFILING_END(det_ctx, engine->gid);
|
||||
}
|
||||
if (engine->is_last)
|
||||
break;
|
||||
engine++;
|
||||
} while (1);
|
||||
}
|
||||
|
||||
/* generic mpm for frame engines */
|
||||
|
||||
// TODO same as Generic?
|
||||
typedef struct PrefilterMpmFrameCtx {
|
||||
int list_id;
|
||||
const MpmCtx *mpm_ctx;
|
||||
const DetectEngineTransforms *transforms;
|
||||
} PrefilterMpmFrameCtx;
|
||||
|
||||
/** \brief Generic Mpm prefilter callback
|
||||
*
|
||||
* \param det_ctx detection engine thread ctx
|
||||
* \param frames container for the frames
|
||||
* \param frame frame to inspect
|
||||
* \param pectx inspection context
|
||||
*/
|
||||
static void PrefilterMpmFrame(DetectEngineThreadCtx *det_ctx, const void *pectx, Packet *p,
|
||||
const Frames *frames, const Frame *frame, const uint32_t idx)
|
||||
{
|
||||
SCEnter();
|
||||
|
||||
const PrefilterMpmFrameCtx *ctx = (const PrefilterMpmFrameCtx *)pectx;
|
||||
const MpmCtx *mpm_ctx = ctx->mpm_ctx;
|
||||
SCLogDebug("running on list %d -> frame field type %u", ctx->list_id, frame->type);
|
||||
// BUG_ON(frame->type != ctx->type);
|
||||
|
||||
InspectionBuffer *buffer = DetectFrame2InspectBuffer(
|
||||
det_ctx, ctx->transforms, p, frames, frame, ctx->list_id, idx, true);
|
||||
if (buffer == NULL)
|
||||
return;
|
||||
|
||||
const uint32_t data_len = buffer->inspect_len;
|
||||
const uint8_t *data = buffer->inspect;
|
||||
|
||||
SCLogDebug("mpm'ing buffer:");
|
||||
// SCLogDebug("frame: %p", frame);
|
||||
// PrintRawDataFp(stdout, data, MIN(32, data_len));
|
||||
|
||||
if (data != NULL && data_len >= mpm_ctx->minlen) {
|
||||
(void)mpm_table[mpm_ctx->mpm_type].Search(
|
||||
mpm_ctx, &det_ctx->mtcu, &det_ctx->pmq, data, data_len);
|
||||
SCLogDebug("det_ctx->pmq.rule_id_array_cnt %u", det_ctx->pmq.rule_id_array_cnt);
|
||||
}
|
||||
}
|
||||
|
||||
static void PrefilterMpmFrameFree(void *ptr)
|
||||
{
|
||||
SCFree(ptr);
|
||||
}
|
||||
|
||||
int PrefilterGenericMpmFrameRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, MpmCtx *mpm_ctx,
|
||||
const DetectBufferMpmRegistery *mpm_reg, int list_id)
|
||||
{
|
||||
SCEnter();
|
||||
PrefilterMpmFrameCtx *pectx = SCCalloc(1, sizeof(*pectx));
|
||||
if (pectx == NULL)
|
||||
return -1;
|
||||
pectx->list_id = list_id;
|
||||
BUG_ON(mpm_reg->frame_v1.alproto == ALPROTO_UNKNOWN);
|
||||
pectx->mpm_ctx = mpm_ctx;
|
||||
pectx->transforms = &mpm_reg->transforms;
|
||||
|
||||
int r = PrefilterAppendFrameEngine(de_ctx, sgh, PrefilterMpmFrame, mpm_reg->frame_v1.alproto,
|
||||
mpm_reg->frame_v1.type, pectx, PrefilterMpmFrameFree, mpm_reg->pname);
|
||||
if (r != 0) {
|
||||
SCFree(pectx);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int DetectRunFrameInspectRule(ThreadVars *tv, DetectEngineThreadCtx *det_ctx, const Signature *s,
|
||||
Flow *f, Packet *p, const Frames *frames, const Frame *frame, const uint32_t idx)
|
||||
{
|
||||
BUG_ON(s->frame_inspect == NULL);
|
||||
|
||||
SCLogDebug("inspecting rule %u against frame %p/%" PRIi64 "/%s", s->id, frame, frame->id,
|
||||
AppLayerParserGetFrameNameById(f->proto, f->alproto, frame->type));
|
||||
|
||||
for (DetectEngineFrameInspectionEngine *e = s->frame_inspect; e != NULL; e = e->next) {
|
||||
if (frame->type == e->type) {
|
||||
// TODO check alproto, direction?
|
||||
|
||||
// TODO there should be only one inspect engine for this frame, ever?
|
||||
|
||||
if (e->v1.Callback(det_ctx, e, s, p, frames, frame, idx) == true) {
|
||||
SCLogDebug("sid %u: e %p Callback returned true", s->id, e);
|
||||
return true;
|
||||
}
|
||||
SCLogDebug("sid %u: e %p Callback returned false", s->id, e);
|
||||
} else {
|
||||
SCLogDebug(
|
||||
"sid %u: e %p not for frame type %u (want %u)", s->id, e, frame->type, e->type);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
InspectionBuffer *DetectFrame2InspectBuffer(DetectEngineThreadCtx *det_ctx,
|
||||
const DetectEngineTransforms *transforms, Packet *p, const Frames *frames,
|
||||
const Frame *frame, const int list_id, const uint32_t idx, const bool first)
|
||||
{
|
||||
// TODO do we really need multiple buffer support here?
|
||||
InspectionBuffer *buffer = InspectionBufferMultipleForListGet(det_ctx, list_id, idx);
|
||||
if (buffer == NULL)
|
||||
return NULL;
|
||||
if (!first && buffer->inspect != NULL)
|
||||
return buffer;
|
||||
|
||||
BUG_ON(p->flow == NULL);
|
||||
BUG_ON(p->flow->protoctx == NULL);
|
||||
TcpSession *ssn = p->flow->protoctx;
|
||||
TcpStream *stream;
|
||||
if (PKT_IS_TOSERVER(p)) {
|
||||
stream = &ssn->client;
|
||||
} else {
|
||||
stream = &ssn->server;
|
||||
}
|
||||
|
||||
/*
|
||||
stream: [s ]
|
||||
frame: [r ]
|
||||
progress: |>p
|
||||
rel_offset: 10, len 100
|
||||
progress: 20
|
||||
avail: 90 (complete)
|
||||
|
||||
stream: [s ]
|
||||
frame: [r ]
|
||||
progress: |>p
|
||||
stream: 0, len 59
|
||||
rel_offset: 10, len 100
|
||||
progress: 20
|
||||
avail: 30 (incomplete)
|
||||
|
||||
stream: [s ]
|
||||
frame: [r ]
|
||||
progress: |>p
|
||||
stream: 0, len 200
|
||||
rel_offset: -30, len 100
|
||||
progress: 20
|
||||
avail: 50 (complete)
|
||||
*/
|
||||
|
||||
SCLogDebug("frame %" PRIi64 ", len %" PRIi64, frame->id, frame->len);
|
||||
|
||||
uint32_t data_len = 0;
|
||||
const uint8_t *data = NULL;
|
||||
|
||||
uint64_t offset = STREAM_BASE_OFFSET(stream);
|
||||
if (frame->rel_offset > 0 || frames->progress_rel) {
|
||||
uint64_t frame_offset = 0;
|
||||
if (frame->rel_offset >= 0) {
|
||||
frame_offset = MAX((uint64_t)frame->rel_offset, (uint64_t)frames->progress_rel);
|
||||
} else {
|
||||
frame_offset = (uint64_t)frames->progress_rel;
|
||||
}
|
||||
offset += frame_offset;
|
||||
}
|
||||
|
||||
const bool eof = ssn->state == TCP_CLOSED || PKT_IS_PSEUDOPKT(p);
|
||||
|
||||
const uint64_t usable = StreamTcpGetUsable(stream, eof);
|
||||
if (usable <= offset)
|
||||
return NULL;
|
||||
|
||||
// TODO GAP handling
|
||||
if (StreamingBufferGetDataAtOffset(&stream->sb, &data, &data_len, offset) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const uint64_t data_right_edge = offset + data_len;
|
||||
if (data_right_edge > usable)
|
||||
data_len = usable - offset;
|
||||
|
||||
const int64_t frame_start_abs_offset = frame->rel_offset + (int64_t)STREAM_BASE_OFFSET(stream);
|
||||
const uint64_t usable_right_edge = MIN(data_right_edge, usable);
|
||||
|
||||
bool have_end = false;
|
||||
|
||||
if (frame->len > 0) {
|
||||
const int64_t frame_avail_data_abs = (int64_t)usable_right_edge;
|
||||
const int64_t frame_end_abs_offset = frame_start_abs_offset + frame->len;
|
||||
have_end = (int64_t)usable_right_edge >= frame_end_abs_offset;
|
||||
|
||||
SCLogDebug("frame_end_abs_offset %" PRIi64 ", usable_right_edge %" PRIu64,
|
||||
frame_end_abs_offset, usable_right_edge);
|
||||
|
||||
const int64_t avail_from_frame = MIN(frame_end_abs_offset, frame_avail_data_abs) - offset;
|
||||
if (avail_from_frame < (int64_t)data_len) {
|
||||
SCLogDebug("adjusted data len from %u to %" PRIi64, data_len, avail_from_frame);
|
||||
data_len = (uint32_t)avail_from_frame;
|
||||
}
|
||||
}
|
||||
const bool have_start = frame_start_abs_offset == (int64_t)offset;
|
||||
|
||||
if (data == NULL || data_len == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// TODO use eof too?
|
||||
SCLogDebug("stream->min_inspect_depth %u", stream->min_inspect_depth);
|
||||
if (data_len < frame->len && data_len < stream->min_inspect_depth) {
|
||||
if (ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED || ssn->state == TCP_CLOSED ||
|
||||
stream->flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED) {
|
||||
SCLogDebug("EOF use available data: %u bytes", data_len);
|
||||
} else {
|
||||
SCLogDebug("not enough data to inspect now: have %u, want %u", data_len,
|
||||
stream->min_inspect_depth);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t ci_flags =
|
||||
(have_start ? DETECT_CI_FLAGS_START : 0) | (have_end ? DETECT_CI_FLAGS_END : 0);
|
||||
SCLogDebug("packet %" PRIu64 " -> frame %p/%" PRIi64 "/%s rel_offset %" PRIi64
|
||||
" type %u len %" PRIi64 " ci_flags %02x (start:%s, end:%s)",
|
||||
p->pcap_cnt, frame, frame->id,
|
||||
AppLayerParserGetFrameNameById(p->flow->proto, p->flow->alproto, frame->type),
|
||||
frame->rel_offset, frame->type, frame->len, ci_flags,
|
||||
(ci_flags & DETECT_CI_FLAGS_START) ? "true" : "false",
|
||||
(ci_flags & DETECT_CI_FLAGS_END) ? "true" : "false");
|
||||
// PrintRawDataFp(stdout, data, MIN(32,data_len));
|
||||
|
||||
InspectionBufferSetupMulti(buffer, transforms, data, data_len);
|
||||
buffer->inspect_offset = frame->rel_offset < 0 ? -1 * frame->rel_offset : 0; // TODO review/test
|
||||
buffer->flags = ci_flags;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Do the content inspection & validation for a signature
|
||||
*
|
||||
* \param de_ctx Detection engine context
|
||||
* \param det_ctx Detection engine thread context
|
||||
* \param s Signature to inspect
|
||||
* \param p Packet
|
||||
* \param frame stream frame to inspect
|
||||
*
|
||||
* \retval 0 no match.
|
||||
* \retval 1 match.
|
||||
*/
|
||||
int DetectEngineInspectFrameBufferGeneric(DetectEngineThreadCtx *det_ctx,
|
||||
const DetectEngineFrameInspectionEngine *engine, const Signature *s, Packet *p,
|
||||
const Frames *frames, const Frame *frame, const uint32_t idx)
|
||||
{
|
||||
const int list_id = engine->sm_list;
|
||||
SCLogDebug("running inspect on %d", list_id);
|
||||
|
||||
SCLogDebug("list %d transforms %p", engine->sm_list, engine->v1.transforms);
|
||||
|
||||
/* if prefilter didn't already run, we need to consider transformations */
|
||||
const DetectEngineTransforms *transforms = NULL;
|
||||
if (!engine->mpm) {
|
||||
transforms = engine->v1.transforms;
|
||||
}
|
||||
|
||||
const InspectionBuffer *buffer =
|
||||
DetectFrame2InspectBuffer(det_ctx, transforms, p, frames, frame, list_id, idx, false);
|
||||
if (unlikely(buffer == NULL)) {
|
||||
return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
|
||||
}
|
||||
|
||||
const uint32_t data_len = buffer->inspect_len;
|
||||
const uint8_t *data = buffer->inspect;
|
||||
const uint64_t offset = buffer->inspect_offset;
|
||||
|
||||
det_ctx->discontinue_matching = 0;
|
||||
det_ctx->buffer_offset = 0;
|
||||
det_ctx->inspection_recursion_counter = 0;
|
||||
#ifdef DEBUG
|
||||
const uint8_t ci_flags = buffer->flags;
|
||||
SCLogDebug("frame %p rel_offset %" PRIi64 " type %u len %" PRIi64
|
||||
" ci_flags %02x (start:%s, end:%s)",
|
||||
frame, frame->rel_offset, frame->type, frame->len, ci_flags,
|
||||
(ci_flags & DETECT_CI_FLAGS_START) ? "true" : "false",
|
||||
(ci_flags & DETECT_CI_FLAGS_END) ? "true" : "false");
|
||||
SCLogDebug("buffer %p offset %" PRIu64 " len %u ci_flags %02x (start:%s, end:%s)", buffer,
|
||||
buffer->inspect_offset, buffer->inspect_len, ci_flags,
|
||||
(ci_flags & DETECT_CI_FLAGS_START) ? "true" : "false",
|
||||
(ci_flags & DETECT_CI_FLAGS_END) ? "true" : "false");
|
||||
// PrintRawDataFp(stdout, data, data_len);
|
||||
// PrintRawDataFp(stdout, data, MIN(64, data_len));
|
||||
#endif
|
||||
BUG_ON((int64_t)data_len > frame->len);
|
||||
|
||||
// TODO don't call if matching needs frame end and DETECT_CI_FLAGS_END not set
|
||||
// TODO same for start
|
||||
int r = DetectEngineContentInspection(det_ctx->de_ctx, det_ctx, s, engine->smd, p, p->flow,
|
||||
(uint8_t *)data, data_len, offset, buffer->flags,
|
||||
DETECT_ENGINE_CONTENT_INSPECTION_MODE_FRAME);
|
||||
if (r == 1) {
|
||||
return DETECT_ENGINE_INSPECT_SIG_MATCH;
|
||||
} else {
|
||||
return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
/* Copyright (C) 2021 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>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "app-layer-frames.h"
|
||||
|
||||
void DetectRunPrefilterFrame(DetectEngineThreadCtx *det_ctx, const SigGroupHead *sgh, Packet *p,
|
||||
const Frames *frames, const Frame *frame, const AppProto alproto, const uint32_t idx);
|
||||
int DetectRunFrameInspectRule(ThreadVars *tv, DetectEngineThreadCtx *det_ctx, const Signature *s,
|
||||
Flow *f, Packet *p, const Frames *frames, const Frame *frame, const uint32_t idx);
|
||||
|
||||
int PrefilterGenericMpmFrameRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, MpmCtx *mpm_ctx,
|
||||
const DetectBufferMpmRegistery *mpm_reg, int list_id);
|
||||
InspectionBuffer *DetectFrame2InspectBuffer(DetectEngineThreadCtx *det_ctx,
|
||||
const DetectEngineTransforms *transforms, Packet *p, const Frames *frames,
|
||||
const Frame *frame, const int list_id, const uint32_t idx, const bool first);
|
||||
int DetectEngineInspectFrameBufferGeneric(DetectEngineThreadCtx *det_ctx,
|
||||
const DetectEngineFrameInspectionEngine *engine, const Signature *s, Packet *p,
|
||||
const Frames *frames, const Frame *frame, const uint32_t idx);
|
||||
Loading…
Reference in New Issue