diff --git a/src/Makefile.am b/src/Makefile.am index f7edaaa518..61fdfae5e8 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,6 +22,7 @@ noinst_HEADERS = \ app-layer-enip.h \ app-layer-events.h \ app-layer-expectation.h \ + app-layer-frames.h \ app-layer-ftp.h \ app-layer.h \ app-layer-htp-body.h \ @@ -611,6 +612,7 @@ libsuricata_c_a_SOURCES = \ app-layer-events.c \ app-layer-expectation.c \ app-layer-ftp.c \ + app-layer-frames.c \ app-layer-htp-body.c \ app-layer-htp.c \ app-layer-htp-file.c \ diff --git a/src/app-layer-frames.c b/src/app-layer-frames.c new file mode 100644 index 0000000000..87c0fdac50 --- /dev/null +++ b/src/app-layer-frames.c @@ -0,0 +1,781 @@ +/* Copyright (C) 2007-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 frameeived 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 + * + */ + +#include "suricata-common.h" +#include "debug.h" +#include "util-print.h" + +#include "stream-tcp.h" +#include "app-layer-frames.h" + +static void FrameDebug(const char *prefix, const Frames *frames, const Frame *frame) +{ +#ifdef DEBUG + const char *type_name = + frames ? AppLayerParserGetFrameNameById(frames->ipproto, frames->alproto, frame->type) + : ""; + SCLogDebug("[%s] %p: frame: %p type %u/%s id %" PRIi64 " flags %02x rel_offset:%" PRIi64 + ", len:%" PRIi64 ", events:%u %u/%u/%u/%u", + prefix, frames, frame, frame->type, type_name, frame->id, frame->flags, + frame->rel_offset, frame->len, frame->event_cnt, frame->events[0], frame->events[1], + frame->events[2], frame->events[3]); +#endif +} + +Frame *FrameGetById(Frames *frames, const int64_t id) +{ + for (uint16_t i = 0; i < frames->cnt; i++) { + if (i < FRAMES_STATIC_CNT) { + Frame *frame = &frames->sframes[i]; + if (frame->id == id) + return frame; + } else { + const uint16_t o = i - FRAMES_STATIC_CNT; + Frame *frame = &frames->dframes[o]; + if (frame->id == id) + return frame; + } + } + return NULL; +} + +Frame *FrameGetByIndex(Frames *frames, const uint32_t idx) +{ + if (idx >= frames->cnt) + return NULL; + + if (idx < FRAMES_STATIC_CNT) { + Frame *frame = &frames->sframes[idx]; + FrameDebug("get_by_idx(s)", frames, frame); + return frame; + } else { + const uint16_t o = idx - FRAMES_STATIC_CNT; + Frame *frame = &frames->dframes[o]; + FrameDebug("get_by_idx(d)", frames, frame); + return frame; + } +} + +// TODO review rel_offset logic. App-layer passes STREAM_APP_PROGRESS as +// offset, but I think we're using rel_offset relative to BASE_PROGRESS +// here which changes only on slide. +static Frame *FrameNew(Frames *frames, int64_t rel_offset, int64_t len) +{ + BUG_ON(frames == NULL); + + if (frames->cnt < FRAMES_STATIC_CNT) { + Frame *frame = &frames->sframes[frames->cnt]; + frames->sframes[frames->cnt].rel_offset = rel_offset; + frames->sframes[frames->cnt].len = len; + frames->sframes[frames->cnt].id = ++frames->base_id; + frames->cnt++; + return frame; + } else if (frames->dframes == NULL) { + BUG_ON(frames->dyn_size != 0); + BUG_ON(frames->cnt != FRAMES_STATIC_CNT); + + frames->dframes = SCCalloc(8, sizeof(Frame)); + if (frames->dframes == NULL) { + return NULL; + } + frames->cnt++; + BUG_ON(frames->cnt != FRAMES_STATIC_CNT + 1); + + frames->dyn_size = 8; + frames->dframes[0].rel_offset = rel_offset; + frames->dframes[0].len = len; + frames->dframes[0].id = ++frames->base_id; + return &frames->dframes[0]; + } else { + BUG_ON(frames->cnt < FRAMES_STATIC_CNT); + + /* need to handle dynamic storage of frames now */ + const uint16_t dyn_cnt = frames->cnt - FRAMES_STATIC_CNT; + if (dyn_cnt < frames->dyn_size) { + BUG_ON(frames->dframes == NULL); + + // fall through + } else { + if (frames->dyn_size == 256) { + SCLogDebug("limit reached! 256 dynamic frames already"); + // limit reached + // TODO figure out if this should lead to an event of sorts + return NULL; + } + + /* realloc time */ + uint16_t new_dyn_size = frames->dyn_size * 2; + uint32_t new_alloc_size = new_dyn_size * sizeof(Frame); + + void *ptr = SCRealloc(frames->dframes, new_alloc_size); + if (ptr == NULL) { + return NULL; + } + + memset((uint8_t *)ptr + (frames->dyn_size * sizeof(Frame)), 0x00, + (frames->dyn_size * sizeof(Frame))); + frames->dframes = ptr; + frames->dyn_size = new_dyn_size; + } + + frames->cnt++; + frames->dframes[dyn_cnt].rel_offset = rel_offset; + frames->dframes[dyn_cnt].len = len; + frames->dframes[dyn_cnt].id = ++frames->base_id; + return &frames->dframes[dyn_cnt]; + } +} + +static void FrameClean(Frame *frame) +{ + memset(frame, 0, sizeof(*frame)); +} + +static void FrameCopy(Frame *dst, Frame *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +static void AppLayerFrameDumpForFrames(const char *prefix, const Frames *frames) +{ + SCLogDebug("prefix: %s", prefix); + for (uint16_t i = 0; i < frames->cnt; i++) { + if (i < FRAMES_STATIC_CNT) { + const Frame *frame = &frames->sframes[i]; + FrameDebug(prefix, frames, frame); + } else { + const uint16_t o = i - FRAMES_STATIC_CNT; + const Frame *frame = &frames->dframes[o]; + FrameDebug(prefix, frames, frame); + } + } + SCLogDebug("prefix: %s", prefix); +} + +static inline uint64_t FrameLeftEdge( + const TcpStream *stream, const Frame *frame, const int64_t base_offset) +{ + const int64_t app_progress = STREAM_APP_PROGRESS(stream); + BUG_ON(base_offset > app_progress); + + const int64_t frame_offset = base_offset + frame->rel_offset; + const int64_t frame_data = app_progress - frame_offset; + + SCLogDebug("base_offset %" PRIi64 ", app_progress %" PRIi64, base_offset, app_progress); + SCLogDebug("frame_offset %" PRIi64 ", frame_data %" PRIi64 ", frame->len %" PRIi64, + frame_offset, frame_data, frame->len); + BUG_ON(frame_offset < 0); + BUG_ON(frame_offset > app_progress); + + /* length unknown, make sure to have at least 2500 */ + if (frame->len < 0) { + if (frame_data <= 2500) { + SCLogDebug("got <= 2500 bytes (%" PRIu64 "), returning offset %" PRIu64, frame_data, + frame_offset); + return frame_offset; + } else { + SCLogDebug("got > 2500 bytes (%" PRIu64 "), returning offset %" PRIu64, frame_data, + (frame_offset + (frame_data - 2500))); + return frame_offset + (frame_data - 2500); + } + + /* length specified */ + } else { + /* have all data for the frame, we can skip it */ + if (frame->len <= frame_data) { + uint64_t x = frame_offset + frame_data; + SCLogDebug("x %" PRIu64, x); + return x; + /* + + [ stream ] + [ frame .......] + + */ + } else if (frame_data < 2500) { + uint64_t x = frame_offset; + SCLogDebug("x %" PRIu64, x); + return x; + } else { + uint64_t x = frame_offset + (frame_data - 2500); + SCLogDebug("x %" PRIu64, x); + return x; + } + } +} +#if 0 +static inline uint64_t FramesLeftEdge(const TcpStream *stream, const Frames *frames) +{ + uint64_t le = STREAM_APP_PROGRESS(stream); + for (uint16_t i = 0; i < frames->cnt; i++) { + if (i < FRAMES_STATIC_CNT) { + const Frame *frame = &frames->sframes[i]; + le = MIN(le, FrameLeftEdge(stream, frame)); + } else { + const uint16_t o = i - FRAMES_STATIC_CNT; + const Frame *frame = &frames->dframes[o]; + le = MIN(le, FrameLeftEdge(stream, frame)); + } + } + return le; +} +#endif + +/** Stream buffer slides forward, we need to update and age out + * frame offsets/frames. Aging out means we move existing frames + * into the slots we'd free up. + * + * Start: + * + * [ stream ] + * [ frame ...........] + * rel_offset: 2 + * len: 19 + * + * Slide: + * [ stream ] + * [ frame .... .] + * rel_offset: -10 + * len: 19 + * + * Slide: + * [ stream ] + * [ frame ........... ] + * rel_offset: -16 + * len: 19 + */ +static int FrameSlide(const char *ds, Frames *frames, const TcpStream *stream, const uint32_t slide) +{ + SCLogDebug("start: left edge %" PRIu64 ", left_edge_rel %u, stream base %" PRIu64 + ", next %" PRIu64, + (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream), frames->left_edge_rel, + STREAM_BASE_OFFSET(stream), STREAM_BASE_OFFSET(stream) + slide); + BUG_ON(frames == NULL); + SCLogDebug("%s frames %p: sliding %u bytes", ds, frames, slide); + uint64_t le = STREAM_APP_PROGRESS(stream); + + if (slide >= frames->progress_rel) + frames->progress_rel = 0; + else + frames->progress_rel -= slide; + + const uint64_t next_base = STREAM_BASE_OFFSET(stream) + slide; + const uint16_t start = frames->cnt; + uint16_t removed = 0; + uint16_t x = 0; + for (uint16_t i = 0; i < frames->cnt; i++) { + if (i < FRAMES_STATIC_CNT) { + Frame *frame = &frames->sframes[i]; + FrameDebug("slide(s)", frames, frame); + if (frame->len >= 0 && + frame->rel_offset + frame->len <= (int64_t)slide) { // TODO check seems off + // remove by not incrementing 'x' + SCLogDebug("removing %p id %" PRIi64, frame, frame->id); + FrameClean(frame); + removed++; + } else { + Frame *nframe = &frames->sframes[x]; + FrameCopy(nframe, frame); + nframe->rel_offset -= slide; /* turns negative if start if before window */ + if (frame != nframe) { + FrameClean(frame); + } + le = MIN(le, FrameLeftEdge(stream, nframe, next_base)); + x++; + } + } else { + const uint16_t o = i - FRAMES_STATIC_CNT; + Frame *frame = &frames->dframes[o]; + FrameDebug("slide(d)", frames, frame); + if (frame->len >= 0 && frame->rel_offset + frame->len <= (int64_t)slide) { + // remove by not incrementing 'x' + SCLogDebug("removing %p id %" PRIi64, frame, frame->id); + FrameClean(frame); + removed++; + } else { + Frame *nframe; + if (x >= FRAMES_STATIC_CNT) { + nframe = &frames->dframes[x - FRAMES_STATIC_CNT]; + } else { + nframe = &frames->sframes[x]; + } + FrameCopy(nframe, frame); + nframe->rel_offset -= slide; /* turns negative if start is before window */ + if (frame != nframe) { + FrameClean(frame); + } + le = MIN(le, FrameLeftEdge(stream, nframe, next_base)); + x++; + } + } + } + frames->cnt = x; + uint64_t o = STREAM_BASE_OFFSET(stream) + slide; + frames->left_edge_rel = le - (STREAM_BASE_OFFSET(stream) + slide); + +#ifdef DEBUG + SCLogDebug("end: left edge %" PRIu64 ", left_edge_rel %u, stream base %" PRIu64 + " (+slide), cnt %u, removed %u, start %u", + (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream) + slide, + frames->left_edge_rel, STREAM_BASE_OFFSET(stream) + slide, frames->cnt, removed, start); + char pf[32] = ""; + snprintf(pf, sizeof(pf), "%s:post_slide", ds); + AppLayerFrameDumpForFrames(pf, frames); +#endif + BUG_ON(o > le); + BUG_ON(x != start - removed); + return 0; +} + +void AppLayerFramesUpdateProgress( + Flow *f, TcpStream *stream, const uint64_t progress, const uint8_t direction) +{ + FramesContainer *frames_container = AppLayerFramesGetContainer(f); + if (frames_container == NULL) + return; + + Frames *frames; + if (direction == STREAM_TOSERVER) { + frames = &frames_container->toserver; + } else { + frames = &frames_container->toclient; + } + + const uint32_t slide = progress - STREAM_APP_PROGRESS(stream); + frames->progress_rel += slide; +} + +void AppLayerFramesSlide(Flow *f, const uint32_t slide, const uint8_t direction) +{ + FramesContainer *frames_container = AppLayerFramesGetContainer(f); + if (frames_container == NULL) + return; + Frames *frames; + TcpSession *ssn = f->protoctx; + TcpStream *stream; + if (direction == STREAM_TOSERVER) { + stream = &ssn->client; + frames = &frames_container->toserver; + FrameSlide("toserver", frames, stream, slide); + } else { + stream = &ssn->server; + frames = &frames_container->toclient; + FrameSlide("toclient", frames, stream, slide); + } +} + +static void FrameFreeSingleFrame(Frames *frames, Frame *r) +{ + FrameDebug("free", frames, r); + FrameClean(r); +} + +void FramesFree(Frames *frames) +{ + BUG_ON(frames == NULL); + + for (uint16_t i = 0; i < frames->cnt; i++) { + if (i < FRAMES_STATIC_CNT) { + Frame *r = &frames->sframes[i]; + FrameFreeSingleFrame(frames, r); + } else { + const uint16_t o = i - FRAMES_STATIC_CNT; + Frame *r = &frames->dframes[o]; + FrameFreeSingleFrame(frames, r); + } + } + SCFree(frames->dframes); + frames->dframes = NULL; +} + +/** \brief create new frame using a pointer to start of the frame + */ +Frame *AppLayerFrameNewByPointer(Flow *f, const StreamSlice *stream_slice, + const uint8_t *frame_start, const int64_t len, int dir, uint8_t frame_type) +{ + SCLogDebug("stream_slice offset %" PRIu64, stream_slice->offset); + SCLogDebug("frame_start %p stream_slice->input %p", frame_start, stream_slice->input); + + /* workarounds for many (unit|fuzz)tests not handling TCP data properly */ +#if defined(UNITTESTS) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + if (f->protoctx == NULL) + return NULL; + if (frame_start < stream_slice->input || + frame_start >= stream_slice->input + stream_slice->input_len) + return NULL; +#endif + BUG_ON(frame_start < stream_slice->input); + BUG_ON(stream_slice->input == NULL); + BUG_ON(f->proto != IPPROTO_TCP); + BUG_ON(f->protoctx == NULL); + + ptrdiff_t ptr_offset = frame_start - stream_slice->input; +#ifdef DEBUG + uint64_t offset = ptr_offset + stream_slice->offset; + SCLogDebug("flow %p direction %s frame %p starting at %" PRIu64 " len %" PRIi64 + " (offset %" PRIu64 ")", + f, dir == 0 ? "toserver" : "toclient", frame_start, offset, len, stream_slice->offset); +#endif + BUG_ON(f->alparser == NULL); + + FramesContainer *frames_container = AppLayerFramesSetupContainer(f); + if (frames_container == NULL) + return NULL; + + TcpStream *stream; + TcpSession *ssn = f->protoctx; + Frames *frames; + if (dir == 0) { + frames = &frames_container->toserver; + stream = &ssn->client; + } else { + frames = &frames_container->toclient; + stream = &ssn->server; + } + + int64_t abs_frame_offset = stream_slice->offset + (int64_t)ptr_offset; + int64_t rel_offset = abs_frame_offset - STREAM_BASE_OFFSET(stream); + + Frame *r = FrameNew(frames, rel_offset, len); + if (r != NULL) { + r->type = frame_type; + } + return r; +} + +/** \brief create new frame using a relative offset from the start of the stream slice + */ +Frame *AppLayerFrameNewByRelativeOffset(Flow *f, const StreamSlice *stream_slice, + const uint32_t frame_start_rel, const int64_t len, int dir, uint8_t frame_type) +{ + /* workarounds for many (unit|fuzz)tests not handling TCP data properly */ +#if defined(UNITTESTS) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + if (f->protoctx == NULL) + return NULL; + if (stream_slice->input == NULL) + return NULL; +#endif + BUG_ON(stream_slice->input == NULL); + BUG_ON(f->proto != IPPROTO_TCP); + BUG_ON(f->protoctx == NULL); + BUG_ON(f->alparser == NULL); + + FramesContainer *frames_container = AppLayerFramesSetupContainer(f); + if (frames_container == NULL) + return NULL; + + TcpStream *stream; + TcpSession *ssn = f->protoctx; + Frames *frames; + if (dir == 0) { + frames = &frames_container->toserver; + stream = &ssn->client; + } else { + frames = &frames_container->toclient; + stream = &ssn->server; + } + + const uint64_t base = STREAM_BASE_OFFSET(stream); +#ifdef DEBUG + const uint64_t app = STREAM_APP_PROGRESS(stream); + const uint64_t app_offset = app - base; + const uint64_t slice_offset = stream_slice->offset - base; + + SCLogDebug("app %" PRIu64 ", base %" PRIu64 ", slice %" PRIu64, app, base, slice_offset); + SCLogDebug("app_offset %" PRIu64 ", slice_offset %" PRIu64, app_offset, slice_offset); +#endif + const uint64_t frame_abs_offset = (uint64_t)frame_start_rel + stream_slice->offset; + const uint64_t frame_base_offset = frame_abs_offset - base; + + SCLogDebug("frame_start_rel %u frame_abs_offset %" PRIu64 ", frame_base_offset %" PRIu64, + frame_start_rel, frame_abs_offset, frame_base_offset); + + int64_t rel_offset = frame_base_offset; +#ifdef DEBUG + const char *type_name = AppLayerParserGetFrameNameById(f->proto, f->alproto, frame_type); + SCLogDebug("flow %p direction %s frame offset %u rel_offset %" PRIi64 " (abs %" PRIu64 + ") starting at %" PRIu64 " len %" PRIi64 " (offset %" PRIu64 ") type %u/%s", + f, dir == 0 ? "toserver" : "toclient", frame_start_rel, rel_offset, frame_abs_offset, + frame_abs_offset, len, stream_slice->offset, frame_type, type_name); +#endif + + Frame *r = FrameNew(frames, rel_offset, len); + if (r != NULL) { + r->type = frame_type; + } + return r; +} + +void AppLayerFrameDump(Flow *f) +{ + if (f->proto == IPPROTO_TCP && f->protoctx && f->alparser) { + FramesContainer *frames_container = AppLayerFramesGetContainer(f); + if (frames_container != NULL) { + AppLayerFrameDumpForFrames("toserver::dump", &frames_container->toserver); + AppLayerFrameDumpForFrames("toclient::dump", &frames_container->toclient); + } + } +} + +/** \brief create new frame using the absolute offset from the start of the stream + */ +Frame *AppLayerFrameNewByAbsoluteOffset(Flow *f, const StreamSlice *stream_slice, + const uint64_t frame_start, const int64_t len, int dir, uint8_t frame_type) +{ + /* workarounds for many (unit|fuzz)tests not handling TCP data properly */ +#if defined(UNITTESTS) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + if (f->protoctx == NULL) + return NULL; + if (stream_slice->input == NULL) + return NULL; +#endif + BUG_ON(stream_slice->input == NULL); + BUG_ON(f->proto != IPPROTO_TCP); + BUG_ON(f->protoctx == NULL); + BUG_ON(f->alparser == NULL); + BUG_ON(frame_start < stream_slice->offset); + BUG_ON(frame_start - stream_slice->offset >= (uint64_t)INT_MAX); + + FramesContainer *frames_container = AppLayerFramesSetupContainer(f); + if (frames_container == NULL) + return NULL; + + TcpSession *ssn = f->protoctx; + TcpStream *stream; + Frames *frames; + if (dir == 0) { + stream = &ssn->client; + frames = &frames_container->toserver; + } else { + stream = &ssn->server; + frames = &frames_container->toclient; + } + + const uint64_t frame_start_rel = frame_start - STREAM_BASE_OFFSET(stream); +#ifdef DEBUG + SCLogDebug("flow %p direction %s frame offset %" PRIu64 " (abs %" PRIu64 + ") starting at %" PRIu64 " len %" PRIi64 " (offset %" PRIu64 ")", + f, dir == 0 ? "toserver" : "toclient", frame_start_rel, frame_start, frame_start, len, + stream_slice->offset); +#endif + Frame *r = FrameNew(frames, (uint32_t)frame_start_rel, len); + if (r != NULL) { + r->type = frame_type; + } + return r; +} + +void AppLayerFrameAddEvent(Frame *r, uint8_t e) +{ + if (r != NULL) { + if (r->event_cnt < 4) { // TODO + r->events[r->event_cnt++] = e; + } + FrameDebug("add_event", NULL, r); + } +} + +void AppLayerFrameAddEventById(Flow *f, const int dir, const FrameId id, uint8_t e) +{ + Frame *frame = AppLayerFrameGetById(f, dir, id); + AppLayerFrameAddEvent(frame, e); +} + +FrameId AppLayerFrameGetId(Frame *r) +{ + if (r != NULL) { + return r->id; + } else { + return -1; + } +} + +void AppLayerFrameSetLength(Frame *frame, int64_t len) +{ + if (frame != NULL) { + frame->len = len; + FrameDebug("set_length", NULL, frame); + } +} + +void AppLayerFrameSetLengthById(Flow *f, const int dir, const FrameId id, int64_t len) +{ + Frame *frame = AppLayerFrameGetById(f, dir, id); + AppLayerFrameSetLength(frame, len); +} + +void AppLayerFrameSetTxId(Frame *r, uint64_t tx_id) +{ + if (r != NULL) { + r->flags |= FRAME_FLAG_TX_ID_SET; + r->tx_id = tx_id; + FrameDebug("set_txid", NULL, r); + } +} + +void AppLayerFrameSetTxIdById(Flow *f, const int dir, const FrameId id, uint64_t tx_id) +{ + Frame *frame = AppLayerFrameGetById(f, dir, id); + AppLayerFrameSetTxId(frame, tx_id); +} + +Frame *AppLayerFrameGetById(Flow *f, const int dir, const FrameId frame_id) +{ + FramesContainer *frames_container = AppLayerFramesGetContainer(f); + if (frames_container == NULL) + return NULL; + + Frames *frames; + if (dir == 0) { + frames = &frames_container->toserver; + } else { + frames = &frames_container->toclient; + } + return FrameGetById(frames, frame_id); +} + +static inline bool FrameIsDone( + const Frame *frame, const uint64_t abs_offset, const uint64_t abs_right_edge) +{ + /* frame with negative length means we don't know the size yet. */ + if (frame->len < 0) + return false; + + const int64_t frame_abs_offset = (int64_t)abs_offset + frame->rel_offset; + const int64_t frame_right_edge = frame_abs_offset + frame->len; + if ((uint64_t)frame_right_edge <= abs_right_edge) { + SCLogDebug("frame %p id %" PRIi64 " is done", frame, frame->id); + return true; + } + return false; +} + +static void FramePrune(Frames *frames, const TcpStream *stream, const bool eof) +{ + const uint64_t frames_le_start = (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream); + SCLogDebug("start: left edge %" PRIu64 ", left_edge_rel %u, stream base %" PRIu64, + (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream), frames->left_edge_rel, + STREAM_BASE_OFFSET(stream)); + const uint64_t abs_offset = STREAM_BASE_OFFSET(stream) + (uint64_t)frames->progress_rel; + const uint64_t acked = StreamTcpGetUsable(stream, eof); + uint64_t le = STREAM_APP_PROGRESS(stream); + + const uint16_t start = frames->cnt; + uint16_t removed = 0; + uint16_t x = 0; + for (uint16_t i = 0; i < frames->cnt; i++) { + if (i < FRAMES_STATIC_CNT) { + Frame *frame = &frames->sframes[i]; + FrameDebug("prune(s)", frames, frame); + if (eof || FrameIsDone(frame, abs_offset, acked)) { + // remove by not incrementing 'x' + SCLogDebug("removing %p id %" PRIi64, frame, frame->id); + FrameDebug("remove(s)", frames, frame); + FrameClean(frame); + removed++; + } else { + const uint64_t fle = FrameLeftEdge(stream, frame, STREAM_BASE_OFFSET(stream)); + le = MIN(le, fle); + SCLogDebug("le %" PRIu64 ", frame fle %" PRIu64, le, fle); + Frame *nframe = &frames->sframes[x]; + FrameCopy(nframe, frame); + if (frame != nframe) { + FrameClean(frame); + } + x++; + } + } else { + const uint16_t o = i - FRAMES_STATIC_CNT; + Frame *frame = &frames->dframes[o]; + FrameDebug("prune(d)", frames, frame); + if (eof || FrameIsDone(frame, abs_offset, acked)) { + // remove by not incrementing 'x' + SCLogDebug("removing %p id %" PRIi64, frame, frame->id); + FrameDebug("remove(d)", frames, frame); + FrameClean(frame); + removed++; + } else { + const uint64_t fle = FrameLeftEdge(stream, frame, STREAM_BASE_OFFSET(stream)); + le = MIN(le, fle); + SCLogDebug("le %" PRIu64 ", frame fle %" PRIu64, le, fle); + Frame *nframe; + if (x >= FRAMES_STATIC_CNT) { + nframe = &frames->dframes[x - FRAMES_STATIC_CNT]; + } else { + nframe = &frames->sframes[x]; + } + FrameCopy(nframe, frame); + if (frame != nframe) { + FrameClean(frame); + } + x++; + } + } + } + frames->cnt = x; + frames->left_edge_rel = le - STREAM_BASE_OFFSET(stream); +#ifdef DEBUG + SCLogDebug("end: left edge %" PRIu64 ", left_edge_rel %u, stream base %" PRIu64 + ", cnt %u, removed %u, start %u", + (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream), frames->left_edge_rel, + STREAM_BASE_OFFSET(stream), frames->cnt, removed, start); + AppLayerFrameDumpForFrames("post_slide", frames); +#endif + BUG_ON(le < STREAM_BASE_OFFSET(stream)); + if (frames->cnt > 0) { // if we removed all this can fail + BUG_ON(frames_le_start > le); + } + BUG_ON(x != start - removed); +} + +void FramesPrune(Flow *f, Packet *p) +{ + if (f->protoctx == NULL) + return; + FramesContainer *frames_container = AppLayerFramesGetContainer(f); + if (frames_container == NULL) + return; + + Frames *frames; + TcpSession *ssn = f->protoctx; + + if (ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) { + AppLayerFramesFreeContainer(f); + return; + } + + TcpStream *stream; + if (PKT_IS_TOSERVER(p)) { + stream = &ssn->client; + frames = &frames_container->toserver; + } else { + stream = &ssn->server; + frames = &frames_container->toclient; + } + + const bool eof = ssn->state == TCP_CLOSED || PKT_IS_PSEUDOPKT(p) || + (ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED); + SCLogDebug("eof %s", eof ? "TRUE" : "false"); + FramePrune(frames, stream, eof); +} diff --git a/src/app-layer-frames.h b/src/app-layer-frames.h new file mode 100644 index 0000000000..dce2cc36a4 --- /dev/null +++ b/src/app-layer-frames.h @@ -0,0 +1,110 @@ +/* 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 frameeived 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 + */ + +#ifndef __APP_LAYER_FRAMES_H__ +#define __APP_LAYER_FRAMES_H__ + +#include "app-layer-events.h" +#include "detect-engine-state.h" +#include "util-file.h" +#include "stream-tcp-private.h" +#include "rust.h" +#include "app-layer-parser.h" + +typedef int64_t FrameId; + +enum { + FRAME_FLAGE_TX_ID_SET, +#define FRAME_FLAG_TX_ID_SET BIT_U8(FRAME_FLAGE_TX_ID_SET) + FRAME_FLAGE_ENDS_AT_EOF, +#define FRAME_FLAG_ENDS_AT_EOF BIT_U8(FRAME_FLAGE_ENDS_AT_EOF) +}; + +typedef struct Frame { + uint8_t type; /**< protocol specific field type. E.g. NBSS.HDR or SMB.DATA */ + uint8_t flags; /**< frame flags: FRAME_FLAG_* */ + uint8_t event_cnt; + // TODO one event per frame enough? + uint8_t events[4]; /**< per frame store for events */ + int64_t rel_offset; /**< relative offset in the stream on top of Stream::stream_offset (if + negative the start if before the stream data) */ + int64_t len; + int64_t id; + uint64_t tx_id; /**< tx_id to match this frame. UINT64T_MAX if not used. */ +} Frame; +// size 40 + +#define FRAMES_STATIC_CNT 3 + +typedef struct Frames { + uint16_t cnt; + uint16_t dyn_size; /**< size in elements of `dframes` */ + uint32_t progress_rel; /**< processing depth relative to STREAM_BASE_OFFSET */ + uint64_t base_id; + uint32_t left_edge_rel; + Frame sframes[FRAMES_STATIC_CNT]; /**< static frames */ + Frame *dframes; /**< dynamically allocated space for more frames */ +#ifdef DEBUG + uint8_t ipproto; + AppProto alproto; +#endif +} Frames; +// size 136 + +typedef struct FramesContainer { + Frames toserver; + Frames toclient; +} FramesContainer; +// size 272 + +void FramesFree(Frames *frames); +void FramesPrune(Flow *f, Packet *p); + +Frame *AppLayerFrameNewByPointer(Flow *f, const StreamSlice *stream_slice, + const uint8_t *frame_start, const int64_t len, int dir, uint8_t frame_type); +Frame *AppLayerFrameNewByRelativeOffset(Flow *f, const StreamSlice *stream_slice, + const uint32_t frame_start_rel, const int64_t len, int dir, uint8_t frame_type); +Frame *AppLayerFrameNewByAbsoluteOffset(Flow *f, const StreamSlice *stream_slice, + const uint64_t frame_start, const int64_t len, int dir, uint8_t frame_type); +void AppLayerFrameDump(Flow *f); + +Frame *FrameGetByIndex(Frames *frames, const uint32_t idx); +Frame *FrameGetById(Frames *frames, const int64_t id); + +Frame *AppLayerFrameGetById(Flow *f, const int direction, const FrameId frame_id); +FrameId AppLayerFrameGetId(Frame *r); +void AppLayerFrameAddEvent(Frame *frame, uint8_t e); +void AppLayerFrameAddEventById(Flow *f, const int dir, const FrameId id, uint8_t e); +void AppLayerFrameSetLength(Frame *frame, int64_t len); +void AppLayerFrameSetLengthById(Flow *f, const int dir, const FrameId id, int64_t len); +void AppLayerFrameSetTxId(Frame *r, uint64_t tx_id); +void AppLayerFrameSetTxIdById(Flow *f, const int dir, const FrameId id, uint64_t tx_id); + +void AppLayerFramesUpdateProgress( + Flow *f, TcpStream *stream, const uint64_t progress, const uint8_t direction); +void AppLayerFramesSlide(Flow *f, const uint32_t slide, const uint8_t direction); + +FramesContainer *AppLayerFramesGetContainer(Flow *f); +FramesContainer *AppLayerFramesSetupContainer(Flow *f); + +#endif diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index 806d5e249e..e4fb82d2e1 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -165,8 +165,56 @@ struct AppLayerParserState_ { /* Used to store decoder events. */ AppLayerDecoderEvents *decoder_events; + + FramesContainer *frames; }; +static void AppLayerParserFramesFreeContainer(FramesContainer *frames) +{ + if (frames != NULL) { + FramesFree(&frames->toserver); + FramesFree(&frames->toclient); + SCFree(frames); + } +} + +void AppLayerFramesFreeContainer(Flow *f) +{ + if (f == NULL || f->alparser == NULL || f->alparser->frames == NULL) + return; + AppLayerParserFramesFreeContainer(f->alparser->frames); + f->alparser->frames = NULL; +} + +FramesContainer *AppLayerFramesGetContainer(Flow *f) +{ + if (f == NULL || f->alparser == NULL) + return NULL; + return f->alparser->frames; +} + +FramesContainer *AppLayerFramesSetupContainer(Flow *f) +{ +#ifdef UNITTESTS + if (f == NULL || f->alparser == NULL || f->protoctx == NULL) + return NULL; +#endif + DEBUG_VALIDATE_BUG_ON(f == NULL || f->alparser == NULL); + if (f->alparser->frames == NULL) { + f->alparser->frames = SCCalloc(1, sizeof(FramesContainer)); + if (f->alparser->frames == NULL) { + return NULL; + } +#ifdef DEBUG + f->alparser->frames->toserver.ipproto = f->proto; + f->alparser->frames->toserver.alproto = f->alproto; + f->alparser->frames->toclient.ipproto = f->proto; + f->alparser->frames->toclient.alproto = f->alproto; +#endif + } + return f->alparser->frames; +} + #ifdef UNITTESTS void UTHAppLayerParserStateGetIds(void *ptr, uint64_t *i1, uint64_t *i2, uint64_t *log, uint64_t *min) { @@ -208,6 +256,7 @@ void AppLayerParserStateFree(AppLayerParserState *pstate) if (pstate->decoder_events != NULL) AppLayerDecoderEventsFreeEvents(&pstate->decoder_events); + AppLayerParserFramesFreeContainer(pstate->frames); SCFree(pstate); SCReturn; diff --git a/src/app-layer-parser.h b/src/app-layer-parser.h index ec585ee394..daeaa31431 100644 --- a/src/app-layer-parser.h +++ b/src/app-layer-parser.h @@ -26,6 +26,7 @@ #define __APP_LAYER_PARSER_H__ #include "app-layer-events.h" +#include "app-layer-frames.h" #include "detect-engine-state.h" #include "util-file.h" #include "stream-tcp-private.h" @@ -315,4 +316,6 @@ void AppLayerParserRestoreParserTable(void); void UTHAppLayerParserStateGetIds(void *ptr, uint64_t *i1, uint64_t *i2, uint64_t *log, uint64_t *min); #endif +void AppLayerFramesFreeContainer(Flow *f); + #endif /* __APP_LAYER_PARSER_H__ */ diff --git a/src/app-layer.c b/src/app-layer.c index 6fc0339e1f..dbe390e2df 100644 --- a/src/app-layer.c +++ b/src/app-layer.c @@ -32,6 +32,7 @@ #include "app-layer-expectation.h" #include "app-layer-ftp.h" #include "app-layer-detect-proto.h" +#include "app-layer-frames.h" #include "stream-tcp-reassemble.h" #include "stream-tcp-private.h" #include "stream-tcp-inline.h" diff --git a/src/flow-worker.c b/src/flow-worker.c index 984b81a875..81c1cdd98c 100644 --- a/src/flow-worker.c +++ b/src/flow-worker.c @@ -428,6 +428,7 @@ static void FlowWorkerFlowTimeout(ThreadVars *tv, Packet *p, FlowWorkerThreadDat /* Prune any stored files. */ FlowPruneFiles(p); + FramesPrune(p->flow, p); /* Release tcp segments. Done here after alerting can use them. */ FLOWWORKER_PROFILING_START(p, PROFILE_FLOWWORKER_TCPPRUNE); StreamTcpPruneSession(p->flow, p->flowflags & FLOW_PKT_TOSERVER ? @@ -568,6 +569,7 @@ static TmEcode FlowWorker(ThreadVars *tv, Packet *p, void *data) StreamTcpSessionCleanup(p->flow->protoctx); } } else if (p->proto == IPPROTO_TCP && p->flow->protoctx) { + FramesPrune(p->flow, p); FLOWWORKER_PROFILING_START(p, PROFILE_FLOWWORKER_TCPPRUNE); StreamTcpPruneSession(p->flow, p->flowflags & FLOW_PKT_TOSERVER ? STREAM_TOSERVER : STREAM_TOCLIENT); diff --git a/src/stream-tcp-list.c b/src/stream-tcp-list.c index 5800251d17..cc9204abcf 100644 --- a/src/stream-tcp-list.c +++ b/src/stream-tcp-list.c @@ -31,6 +31,7 @@ #include "util-streaming-buffer.h" #include "util-print.h" #include "util-validate.h" +#include "app-layer-frames.h" static void StreamTcpRemoveSegmentFromStream(TcpStream *stream, TcpSegment *seg); @@ -676,7 +677,24 @@ static inline bool StreamTcpReturnSegmentCheck(const TcpStream *stream, const Tc SCReturnInt(true); } -static inline uint64_t GetLeftEdge(TcpSession *ssn, TcpStream *stream) +static inline uint64_t GetLeftEdgeForApp(Flow *f, TcpSession *ssn, TcpStream *stream) +{ + const FramesContainer *frames_container = AppLayerFramesGetContainer(f); + if (frames_container == NULL) + return STREAM_APP_PROGRESS(stream); + + const Frames *frames = + stream == &ssn->client ? &frames_container->toserver : &frames_container->toclient; + // const uint64_t x = FramesLeftEdge(stream, frames); + // BUG_ON(x != (frames->left_edge_rel + STREAM_BASE_OFFSET(stream))); + // return x; + const uint64_t o = (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream); + SCLogDebug( + "%s: frames left edge: %" PRIu64, &ssn->client == stream ? "toserver" : "toclient", o); + return o; +} + +static inline uint64_t GetLeftEdge(Flow *f, TcpSession *ssn, TcpStream *stream) { bool use_app = true; bool use_raw = true; @@ -694,6 +712,8 @@ static inline uint64_t GetLeftEdge(TcpSession *ssn, TcpStream *stream) use_log = false; } + SCLogDebug("use_app %d use_raw %d use_log %d", use_app, use_raw, use_log); + if (use_raw) { uint64_t raw_progress = STREAM_RAW_PROGRESS(stream); @@ -721,18 +741,19 @@ static inline uint64_t GetLeftEdge(TcpSession *ssn, TcpStream *stream) } if (use_app) { - left_edge = MIN(STREAM_APP_PROGRESS(stream), raw_progress); - SCLogDebug("left_edge %"PRIu64", using both app:%"PRIu64", raw:%"PRIu64, - left_edge, STREAM_APP_PROGRESS(stream), raw_progress); + const uint64_t app_le = GetLeftEdgeForApp(f, ssn, stream); + left_edge = MIN(app_le, raw_progress); + SCLogDebug("left_edge %" PRIu64 ", using both app:%" PRIu64 ", raw:%" PRIu64, left_edge, + app_le, raw_progress); } else { left_edge = raw_progress; SCLogDebug("left_edge %"PRIu64", using only raw:%"PRIu64, left_edge, raw_progress); } } else if (use_app) { - left_edge = STREAM_APP_PROGRESS(stream); - SCLogDebug("left_edge %"PRIu64", using only app:%"PRIu64, - left_edge, STREAM_APP_PROGRESS(stream)); + const uint64_t app_le = GetLeftEdgeForApp(f, ssn, stream); + left_edge = app_le; + SCLogDebug("left_edge %" PRIu64 ", using only app:%" PRIu64, left_edge, app_le); } else { left_edge = STREAM_BASE_OFFSET(stream) + stream->sb.buf_offset; SCLogDebug("no app & raw: left_edge %"PRIu64" (full stream)", left_edge); @@ -847,10 +868,14 @@ void StreamTcpPruneSession(Flow *f, uint8_t flags) return; } - const uint64_t left_edge = GetLeftEdge(ssn, stream); + const uint64_t left_edge = GetLeftEdge(f, ssn, stream); if (left_edge && left_edge > STREAM_BASE_OFFSET(stream)) { uint32_t slide = left_edge - STREAM_BASE_OFFSET(stream); SCLogDebug("buffer sliding %u to offset %"PRIu64, slide, left_edge); + + if (!(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED)) { + AppLayerFramesSlide(f, slide, flags & (STREAM_TOSERVER | STREAM_TOCLIENT)); + } StreamingBufferSlideToOffset(&stream->sb, left_edge); stream->base_seq += slide; diff --git a/src/stream-tcp-reassemble.c b/src/stream-tcp-reassemble.c index ad2e30310d..99f0912d1d 100644 --- a/src/stream-tcp-reassemble.c +++ b/src/stream-tcp-reassemble.c @@ -61,6 +61,7 @@ #include "app-layer.h" #include "app-layer-events.h" #include "app-layer-parser.h" +#include "app-layer-frames.h" #include "detect-engine-state.h" @@ -1199,6 +1200,7 @@ static int ReassembleUpdateAppLayer (ThreadVars *tv, (void)AppLayerHandleTCPData(tv, ra_ctx, p, p->flow, ssn, stream, (uint8_t *)mydata, mydata_len, flags); AppLayerProfilingStore(ra_ctx->app_tctx, p); + AppLayerFrameDump(p->flow); uint64_t new_app_progress = STREAM_APP_PROGRESS(*stream); if (new_app_progress == app_progress || FlowChangeProto(p->flow)) break; @@ -1388,6 +1390,14 @@ void StreamReassembleRawUpdateProgress(TcpSession *ssn, Packet *p, uint64_t prog stream = &ssn->server; } + /* Record updates */ + if (!(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED)) { + if (progress > STREAM_APP_PROGRESS(stream)) { + AppLayerFramesUpdateProgress(p->flow, stream, progress, + PKT_IS_TOSERVER(p) ? STREAM_TOSERVER : STREAM_TOCLIENT); + } + } + if (progress > STREAM_RAW_PROGRESS(stream)) { uint32_t slide = progress - STREAM_RAW_PROGRESS(stream); stream->raw_progress_rel += slide;