-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Printable/v16 #10592
Printable/v16 #10592
Changes from all commits
cb0c12a
d084856
5e27718
b32bda1
20404c2
49523fc
d7c0d58
8ec62b4
c2df1b0
bdbffed
40f9c7c
6e92e5d
d38f9e1
04204de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ | |
#include "stream.h" | ||
#include "threadvars.h" | ||
#include "util-debug.h" | ||
#include "stream-tcp.h" | ||
|
||
#include "util-logopenfile.h" | ||
#include "util-misc.h" | ||
|
@@ -112,17 +113,6 @@ typedef struct JsonAlertLogThread_ { | |
OutputJsonThreadCtx *ctx; | ||
} JsonAlertLogThread; | ||
|
||
/* Callback function to pack payload contents from a stream into a buffer | ||
* so we can report them in JSON output. */ | ||
static int AlertJsonDumpStreamSegmentCallback( | ||
const Packet *p, TcpSegment *seg, void *data, const uint8_t *buf, uint32_t buflen) | ||
{ | ||
MemBuffer *payload = (MemBuffer *)data; | ||
MemBufferWriteRaw(payload, buf, buflen); | ||
|
||
return 1; | ||
} | ||
|
||
static void AlertJsonSourceTarget(const Packet *p, const PacketAlert *pa, | ||
JsonBuilder *js, JsonAddrInfo *addr) | ||
{ | ||
|
@@ -414,7 +404,8 @@ static void AlertAddFiles(const Packet *p, JsonBuilder *jb, const uint64_t tx_id | |
} | ||
} | ||
|
||
static void AlertAddFrame(const Packet *p, JsonBuilder *jb, const int64_t frame_id) | ||
static void AlertAddFrame( | ||
const Packet *p, const int64_t frame_id, JsonBuilder *jb, MemBuffer *buffer) | ||
{ | ||
if (p->flow == NULL || (p->proto == IPPROTO_TCP && p->flow->protoctx == NULL)) | ||
return; | ||
|
@@ -436,7 +427,7 @@ static void AlertAddFrame(const Packet *p, JsonBuilder *jb, const int64_t frame_ | |
} | ||
Frame *frame = FrameGetById(frames, frame_id); | ||
if (frame != NULL) { | ||
FrameJsonLogOneFrame(IPPROTO_TCP, frame, p->flow, stream, p, jb); | ||
FrameJsonLogOneFrame(IPPROTO_TCP, frame, p->flow, stream, p, jb, buffer); | ||
} | ||
} else if (p->proto == IPPROTO_UDP) { | ||
if (PKT_IS_TOSERVER(p)) { | ||
|
@@ -446,7 +437,7 @@ static void AlertAddFrame(const Packet *p, JsonBuilder *jb, const int64_t frame_ | |
} | ||
Frame *frame = FrameGetById(frames, frame_id); | ||
if (frame != NULL) { | ||
FrameJsonLogOneFrame(IPPROTO_UDP, frame, p->flow, NULL, p, jb); | ||
FrameJsonLogOneFrame(IPPROTO_UDP, frame, p->flow, NULL, p, jb, buffer); | ||
} | ||
} | ||
} | ||
|
@@ -502,9 +493,64 @@ void EveAddVerdict(JsonBuilder *jb, const Packet *p) | |
jb_close(jb); | ||
} | ||
|
||
struct AlertJsonStreamDataCallbackData { | ||
MemBuffer *payload; | ||
uint64_t last_re; | ||
}; | ||
|
||
static int AlertJsonStreamDataCallback( | ||
void *cb_data, const uint8_t *input, const uint32_t input_len, const uint64_t input_offset) | ||
{ | ||
struct AlertJsonStreamDataCallbackData *cbd = cb_data; | ||
if (input_offset > cbd->last_re) { | ||
MemBufferWriteString( | ||
cbd->payload, "[%" PRIu64 " bytes missing]", input_offset - cbd->last_re); | ||
} | ||
|
||
int done = 0; | ||
uint32_t written = MemBufferWriteRaw(cbd->payload, input, input_len); | ||
if (written < input_len) | ||
done = 1; | ||
cbd->last_re = input_offset + input_len; | ||
return done; | ||
} | ||
|
||
/** \internal | ||
* \brief try to log stream data into payload/payload_printable | ||
* \retval true stream data logged | ||
* \retval false stream data not logged | ||
*/ | ||
static bool AlertJsonStreamData(const AlertJsonOutputCtx *json_output_ctx, JsonAlertLogThread *aft, | ||
Flow *f, const Packet *p, JsonBuilder *jb) | ||
{ | ||
TcpSession *ssn = f->protoctx; | ||
TcpStream *stream = (PKT_IS_TOSERVER(p)) ? &ssn->client : &ssn->server; | ||
|
||
MemBufferReset(aft->payload_buffer); | ||
struct AlertJsonStreamDataCallbackData cbd = { .payload = aft->payload_buffer, | ||
.last_re = STREAM_BASE_OFFSET(stream) }; | ||
uint64_t unused = 0; | ||
StreamReassembleLog(ssn, stream, AlertJsonStreamDataCallback, &cbd, STREAM_BASE_OFFSET(stream), | ||
&unused, false); | ||
if (cbd.payload->offset) { | ||
if (json_output_ctx->flags & LOG_JSON_PAYLOAD_BASE64) { | ||
jb_set_base64(jb, "payload", cbd.payload->buffer, cbd.payload->offset); | ||
} | ||
|
||
if (json_output_ctx->flags & LOG_JSON_PAYLOAD) { | ||
uint8_t printable_buf[cbd.payload->offset + 1]; | ||
uint32_t offset = 0; | ||
PrintStringsToBuffer(printable_buf, &offset, sizeof(printable_buf), cbd.payload->buffer, | ||
cbd.payload->offset); | ||
jb_set_string(jb, "payload_printable", (char *)printable_buf); | ||
} | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p) | ||
{ | ||
MemBuffer *payload = aft->payload_buffer; | ||
AlertJsonOutputCtx *json_output_ctx = aft->json_output_ctx; | ||
|
||
if (p->alerts.cnt == 0 && !(p->flags & PKT_HAS_TAG)) | ||
|
@@ -610,36 +656,14 @@ static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p) | |
int stream = (p->proto == IPPROTO_TCP) ? | ||
(pa->flags & (PACKET_ALERT_FLAG_STATE_MATCH | PACKET_ALERT_FLAG_STREAM_MATCH) ? | ||
1 : 0) : 0; | ||
DEBUG_VALIDATE_BUG_ON( | ||
p->flow == NULL); // should be impossible, but scan-build got confused | ||
|
||
/* Is this a stream? If so, pack part of it into the payload field */ | ||
if (stream) { | ||
uint8_t flag; | ||
|
||
MemBufferReset(payload); | ||
|
||
if (p->flowflags & FLOW_PKT_TOSERVER) { | ||
flag = STREAM_DUMP_TOCLIENT; | ||
} else { | ||
flag = STREAM_DUMP_TOSERVER; | ||
} | ||
|
||
StreamSegmentForEach((const Packet *)p, flag, | ||
AlertJsonDumpStreamSegmentCallback, | ||
(void *)payload); | ||
if (payload->offset) { | ||
if (json_output_ctx->flags & LOG_JSON_PAYLOAD_BASE64) { | ||
jb_set_base64(jb, "payload", payload->buffer, payload->offset); | ||
} | ||
|
||
if (json_output_ctx->flags & LOG_JSON_PAYLOAD) { | ||
uint8_t printable_buf[payload->offset + 1]; | ||
uint32_t offset = 0; | ||
PrintStringsToBuffer(printable_buf, &offset, | ||
sizeof(printable_buf), | ||
payload->buffer, payload->offset); | ||
jb_set_string(jb, "payload_printable", (char *)printable_buf); | ||
} | ||
} else if (p->payload_len) { | ||
if (stream && p->flow != NULL) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does it work when Suricata captures the flow midway and the alert wants to dump the payload on the first packet? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes there will be a flow then |
||
const bool stream_data_logged = | ||
AlertJsonStreamData(json_output_ctx, aft, p->flow, p, jb); | ||
if (!stream_data_logged && p->payload_len) { | ||
/* Fallback on packet payload */ | ||
AlertAddPayload(json_output_ctx, jb, p); | ||
} | ||
|
@@ -652,7 +676,7 @@ static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p) | |
} | ||
|
||
if (pa->flags & PACKET_ALERT_FLAG_FRAME) { | ||
AlertAddFrame(p, jb, pa->frame_id); | ||
AlertAddFrame(p, pa->frame_id, jb, aft->payload_buffer); | ||
} | ||
|
||
/* base64-encoded full packet */ | ||
|
@@ -889,14 +913,13 @@ static void JsonAlertLogSetupMetadata(AlertJsonOutputCtx *json_output_ctx, | |
warn_no_meta = true; | ||
} | ||
} | ||
|
||
json_output_ctx->payload_buffer_size = payload_buffer_size; | ||
} | ||
|
||
if (flags & LOG_JSON_RULE_METADATA) { | ||
DetectEngineSetParseMetadata(); | ||
} | ||
|
||
json_output_ctx->payload_buffer_size = payload_buffer_size; | ||
json_output_ctx->flags |= flags; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -125,61 +125,90 @@ static void PayloadAsHex(const uint8_t *data, uint32_t data_len, char *str, size | |
} | ||
#endif | ||
|
||
static void FrameAddPayloadTCP(JsonBuilder *js, const TcpStream *stream, const Frame *frame) | ||
struct FrameJsonStreamDataCallbackData { | ||
MemBuffer *payload; | ||
const Frame *frame; | ||
uint64_t last_re; /**< used to detect gaps */ | ||
}; | ||
|
||
static int FrameJsonStreamDataCallback( | ||
void *cb_data, const uint8_t *input, const uint32_t input_len, const uint64_t input_offset) | ||
{ | ||
uint32_t sb_data_len = 0; | ||
const uint8_t *data = NULL; | ||
uint64_t data_offset = 0; | ||
struct FrameJsonStreamDataCallbackData *cbd = cb_data; | ||
const Frame *frame = cbd->frame; | ||
|
||
// TODO consider ACK'd | ||
uint32_t write_size = input_len; | ||
int done = 0; | ||
|
||
if (frame->offset < STREAM_BASE_OFFSET(stream)) { | ||
if (StreamingBufferGetData(&stream->sb, &data, &sb_data_len, &data_offset) == 0) { | ||
SCLogDebug("NO DATA1"); | ||
return; | ||
if (frame->len >= 0) { | ||
const uint64_t data_re = input_offset + input_len; | ||
const uint64_t frame_re = frame->offset + (uint64_t)frame->len; | ||
|
||
/* data entirely after frame, we're done */ | ||
if (input_offset >= frame_re) { | ||
return 1; | ||
} | ||
} else { | ||
data_offset = (uint64_t)frame->offset; | ||
SCLogDebug("data_offset %" PRIu64, data_offset); | ||
if (StreamingBufferGetDataAtOffset( | ||
&stream->sb, &data, &sb_data_len, (uint64_t)data_offset) == 0) { | ||
SCLogDebug("NO DATA1"); | ||
return; | ||
/* make sure to only log data belonging to the frame */ | ||
if (data_re >= frame_re) { | ||
const uint64_t to_write = frame_re - input_offset; | ||
if (to_write < (uint64_t)write_size) { | ||
write_size = (uint32_t)to_write; | ||
} | ||
done = 1; | ||
} | ||
} | ||
if (data == NULL || sb_data_len == 0) { | ||
SCLogDebug("NO DATA2"); | ||
return; | ||
if (input_offset > cbd->last_re) { | ||
MemBufferWriteString( | ||
cbd->payload, "[%" PRIu64 " bytes missing]", input_offset - cbd->last_re); | ||
} | ||
|
||
if (frame->len >= 0) { | ||
sb_data_len = MIN(frame->len, (int32_t)sb_data_len); | ||
if (write_size > 0) { | ||
uint32_t written = MemBufferWriteRaw(cbd->payload, input, write_size); | ||
if (written < write_size) | ||
done = 1; | ||
} | ||
SCLogDebug("frame data_offset %" PRIu64 ", data_len %u frame len %" PRIi64, data_offset, | ||
sb_data_len, frame->len); | ||
cbd->last_re = input_offset + write_size; | ||
return done; | ||
} | ||
|
||
/** \internal | ||
* \brief try to log frame's stream data into payload/payload_printable | ||
*/ | ||
static void FrameAddPayloadTCP(Flow *f, const TcpSession *ssn, const TcpStream *stream, | ||
const Frame *frame, JsonBuilder *jb, MemBuffer *buffer) | ||
{ | ||
MemBufferReset(buffer); | ||
|
||
/* consider all data, ACK'd and non-ACK'd */ | ||
const uint64_t stream_data_re = StreamDataRightEdge(stream, true); | ||
bool complete = false; | ||
if (frame->len > 0) { | ||
const uint64_t frame_re = frame->offset + (uint64_t)frame->len; | ||
const uint64_t data_re = data_offset + sb_data_len; | ||
complete = frame_re <= data_re; | ||
if (frame->len >= 0 && frame->offset + (uint64_t)frame->len <= stream_data_re) { | ||
complete = true; | ||
} | ||
jb_set_bool(js, "complete", complete); | ||
|
||
uint32_t data_len = MIN(sb_data_len, 256); | ||
jb_set_base64(js, "payload", data, data_len); | ||
struct FrameJsonStreamDataCallbackData cbd = { | ||
.payload = buffer, .frame = frame, .last_re = frame->offset | ||
}; | ||
uint64_t unused = 0; | ||
StreamReassembleLog( | ||
ssn, stream, FrameJsonStreamDataCallback, &cbd, frame->offset, &unused, false); | ||
/* if we have all data, but didn't log until the end of the frame, we have a gap at the | ||
* end of the frame | ||
* TODO what about not logging due to buffer full? */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is todo intended to be left out? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure |
||
if (complete && frame->len >= 0 && cbd.last_re < frame->offset + (uint64_t)frame->len) { | ||
MemBufferWriteString(cbd.payload, "[%" PRIu64 " bytes missing]", | ||
(frame->offset + (uint64_t)frame->len) - cbd.last_re); | ||
} | ||
|
||
uint8_t printable_buf[data_len + 1]; | ||
uint32_t o = 0; | ||
PrintStringsToBuffer(printable_buf, &o, data_len + 1, data, data_len); | ||
printable_buf[data_len] = '\0'; | ||
jb_set_string(js, "payload_printable", (char *)printable_buf); | ||
#if 0 | ||
char pretty_buf[data_len * 4 + 1]; | ||
pretty_buf[0] = '\0'; | ||
PayloadAsHex(data, data_len, pretty_buf, data_len * 4 + 1); | ||
jb_set_string(js, "payload_hex", pretty_buf); | ||
#endif | ||
if (cbd.payload->offset) { | ||
jb_set_base64(jb, "payload", cbd.payload->buffer, cbd.payload->offset); | ||
uint8_t printable_buf[cbd.payload->offset + 1]; | ||
uint32_t offset = 0; | ||
PrintStringsToBuffer(printable_buf, &offset, sizeof(printable_buf), cbd.payload->buffer, | ||
cbd.payload->offset); | ||
jb_set_string(jb, "payload_printable", (char *)printable_buf); | ||
jb_set_bool(jb, "complete", complete); | ||
} | ||
} | ||
|
||
static void FrameAddPayloadUDP(JsonBuilder *js, const Packet *p, const Frame *frame) | ||
|
@@ -223,8 +252,8 @@ static void FrameAddPayloadUDP(JsonBuilder *js, const Packet *p, const Frame *fr | |
/** \brief log a single frame | ||
* \note ipproto argument is passed to assist static code analyzers | ||
*/ | ||
void FrameJsonLogOneFrame(const uint8_t ipproto, const Frame *frame, const Flow *f, | ||
const TcpStream *stream, const Packet *p, JsonBuilder *jb) | ||
void FrameJsonLogOneFrame(const uint8_t ipproto, const Frame *frame, Flow *f, | ||
const TcpStream *stream, const Packet *p, JsonBuilder *jb, MemBuffer *buffer) | ||
{ | ||
DEBUG_VALIDATE_BUG_ON(ipproto != p->proto); | ||
DEBUG_VALIDATE_BUG_ON(ipproto != f->proto); | ||
|
@@ -249,7 +278,7 @@ void FrameJsonLogOneFrame(const uint8_t ipproto, const Frame *frame, const Flow | |
} else { | ||
jb_set_uint(jb, "length", frame->len); | ||
} | ||
FrameAddPayloadTCP(jb, stream, frame); | ||
FrameAddPayloadTCP(f, f->protoctx, stream, frame, jb, buffer); | ||
} else { | ||
jb_set_uint(jb, "length", frame->len); | ||
FrameAddPayloadUDP(jb, p, frame); | ||
|
@@ -287,7 +316,7 @@ static int FrameJsonUdp( | |
return TM_ECODE_OK; | ||
|
||
jb_set_string(jb, "app_proto", AppProtoToString(f->alproto)); | ||
FrameJsonLogOneFrame(IPPROTO_UDP, frame, p->flow, NULL, p, jb); | ||
FrameJsonLogOneFrame(IPPROTO_UDP, frame, p->flow, NULL, p, jb, aft->payload_buffer); | ||
OutputJsonBuilderBuffer(jb, aft->ctx); | ||
jb_free(jb); | ||
frame->flags |= FRAME_FLAG_LOGGED; | ||
|
@@ -359,7 +388,7 @@ static int FrameJson(ThreadVars *tv, JsonFrameLogThread *aft, const Packet *p) | |
return TM_ECODE_OK; | ||
|
||
jb_set_string(jb, "app_proto", AppProtoToString(p->flow->alproto)); | ||
FrameJsonLogOneFrame(IPPROTO_TCP, frame, p->flow, stream, p, jb); | ||
FrameJsonLogOneFrame(IPPROTO_TCP, frame, p->flow, stream, p, jb, aft->payload_buffer); | ||
OutputJsonBuilderBuffer(jb, aft->ctx); | ||
jb_free(jb); | ||
frame->flags |= FRAME_FLAG_LOGGED; | ||
|
@@ -482,8 +511,22 @@ static OutputInitResult JsonFrameLogInitCtxSub(ConfNode *conf, OutputCtx *parent | |
goto error; | ||
} | ||
|
||
uint32_t payload_buffer_size = 4096; | ||
if (conf != NULL) { | ||
const char *payload_buffer_value = ConfNodeLookupChildValue(conf, "payload-buffer-size"); | ||
if (payload_buffer_value != NULL) { | ||
uint32_t value; | ||
if (ParseSizeStringU32(payload_buffer_value, &value) < 0) { | ||
SCLogError("Error parsing payload-buffer-size \"%s\"", payload_buffer_value); | ||
goto error; | ||
} | ||
payload_buffer_size = value; | ||
} | ||
} | ||
|
||
json_output_ctx->file_ctx = ajt->file_ctx; | ||
json_output_ctx->eve_ctx = ajt; | ||
json_output_ctx->payload_buffer_size = payload_buffer_size; | ||
|
||
output_ctx->data = json_output_ctx; | ||
output_ctx->DeInit = JsonFrameLogDeInitCtxSub; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it would make sense to have a variant of
jb_set_string
that takes care of all this?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that seems like a good idea. We could avoid this stack local copy, and perhaps it can be refactored to not even use the membuffer too? Would like to check that post merge though