Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/output-json-frame.c
Line
Count
Source
1
/* Copyright (C) 2013-2021 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
/**
19
 * \file
20
 *
21
 * \author Victor Julien <victor@inliniac.net>
22
 *
23
 * Logs frames in JSON format.
24
 *
25
 */
26
27
#include "suricata-common.h"
28
#include "detect.h"
29
#include "flow.h"
30
#include "conf.h"
31
32
#include "threads.h"
33
#include "tm-threads.h"
34
#include "threadvars.h"
35
#include "util-debug.h"
36
37
#include "util-logopenfile.h"
38
#include "util-misc.h"
39
#include "util-unittest.h"
40
#include "util-unittest-helper.h"
41
42
#include "detect-parse.h"
43
#include "detect-engine.h"
44
#include "detect-engine-mpm.h"
45
#include "detect-reference.h"
46
#include "detect-metadata.h"
47
#include "app-layer-parser.h"
48
#include "app-layer-frames.h"
49
#include "app-layer-dnp3.h"
50
#include "app-layer-htp.h"
51
#include "app-layer-htp-xff.h"
52
#include "app-layer-ftp.h"
53
#include "util-classification-config.h"
54
#include "stream-tcp.h"
55
56
#include "output.h"
57
#include "output-json.h"
58
#include "output-json-frame.h"
59
60
#include "util-byte.h"
61
#include "util-privs.h"
62
#include "util-print.h"
63
#include "util-proto-name.h"
64
#include "util-optimize.h"
65
#include "util-buffer.h"
66
#include "util-validate.h"
67
68
71
#define MODULE_NAME "JsonFrameLog"
69
70
#define JSON_STREAM_BUFFER_SIZE 4096
71
72
typedef struct FrameJsonOutputCtx_ {
73
    LogFileCtx *file_ctx;
74
    uint16_t flags;
75
    uint32_t payload_buffer_size;
76
    OutputJsonCtx *eve_ctx;
77
} FrameJsonOutputCtx;
78
79
typedef struct JsonFrameLogThread_ {
80
    MemBuffer *payload_buffer;
81
    FrameJsonOutputCtx *json_output_ctx;
82
    OutputJsonThreadCtx *ctx;
83
} JsonFrameLogThread;
84
85
#if 0 // TODO see if this is useful in some way
86
static inline bool NeedsAsHex(uint8_t c)
87
{
88
    if (!isprint(c))
89
        return true;
90
91
    switch (c) {
92
        case '/':
93
        case ';':
94
        case ':':
95
        case '\\':
96
        case ' ':
97
        case '|':
98
        case '"':
99
        case '`':
100
        case '\'':
101
            return true;
102
    }
103
    return false;
104
}
105
106
static void PayloadAsHex(const uint8_t *data, uint32_t data_len, char *str, size_t str_len)
107
{
108
    bool hex = false;
109
    for (uint32_t i = 0; i < data_len; i++) {
110
        if (NeedsAsHex(data[i])) {
111
            char hex_str[4];
112
            snprintf(hex_str, sizeof(hex_str), "%s%02X", !hex ? "|" : " ", data[i]);
113
            strlcat(str, hex_str, str_len);
114
            hex = true;
115
        } else {
116
            char p_str[3];
117
            snprintf(p_str, sizeof(p_str), "%s%c", hex ? "|" : "", data[i]);
118
            strlcat(str, p_str, str_len);
119
            hex = false;
120
        }
121
    }
122
    if (hex) {
123
        strlcat(str, "|", str_len);
124
    }
125
}
126
#endif
127
128
struct FrameJsonStreamDataCallbackData {
129
    MemBuffer *payload;
130
    const Frame *frame;
131
    uint64_t last_re; /**< used to detect gaps */
132
};
133
134
static int FrameJsonStreamDataCallback(
135
        void *cb_data, const uint8_t *input, const uint32_t input_len, const uint64_t input_offset)
136
46.8k
{
137
46.8k
    struct FrameJsonStreamDataCallbackData *cbd = cb_data;
138
46.8k
    const Frame *frame = cbd->frame;
139
140
46.8k
    uint32_t write_size = input_len;
141
46.8k
    int done = 0;
142
143
46.8k
    if (frame->len >= 0) {
144
34.8k
        const uint64_t data_re = input_offset + input_len;
145
34.8k
        const uint64_t frame_re = frame->offset + (uint64_t)frame->len;
146
147
        /* data entirely after frame, we're done */
148
34.8k
        if (input_offset >= frame_re) {
149
27
            return 1;
150
27
        }
151
        /* make sure to only log data belonging to the frame */
152
34.7k
        if (data_re >= frame_re) {
153
21.5k
            const uint64_t to_write = frame_re - input_offset;
154
21.5k
            if (to_write < (uint64_t)write_size) {
155
1.84k
                write_size = (uint32_t)to_write;
156
1.84k
            }
157
21.5k
            done = 1;
158
21.5k
        }
159
34.7k
    }
160
46.8k
    if (input_offset > cbd->last_re) {
161
205
        MemBufferWriteString(
162
205
                cbd->payload, "[%" PRIu64 " bytes missing]", input_offset - cbd->last_re);
163
205
    }
164
165
46.8k
    if (write_size > 0) {
166
46.8k
        uint32_t written = MemBufferWriteRaw(cbd->payload, input, write_size);
167
46.8k
        if (written < write_size)
168
6.49k
            done = 1;
169
46.8k
    }
170
46.8k
    cbd->last_re = input_offset + write_size;
171
46.8k
    return done;
172
46.8k
}
173
174
/** \internal
175
 *  \brief try to log frame's stream data into payload/payload_printable
176
 */
177
static void FrameAddPayloadTCP(Flow *f, const TcpSession *ssn, const TcpStream *stream,
178
        const Frame *frame, JsonBuilder *jb, MemBuffer *buffer)
179
47.5k
{
180
47.5k
    MemBufferReset(buffer);
181
182
    /* consider all data, ACK'd and non-ACK'd */
183
47.5k
    const uint64_t stream_data_re = StreamDataRightEdge(stream, true);
184
47.5k
    bool complete = false;
185
47.5k
    if (frame->len >= 0 && frame->offset + (uint64_t)frame->len <= stream_data_re) {
186
21.5k
        complete = true;
187
21.5k
    }
188
189
47.5k
    struct FrameJsonStreamDataCallbackData cbd = {
190
47.5k
        .payload = buffer, .frame = frame, .last_re = frame->offset
191
47.5k
    };
192
47.5k
    uint64_t unused = 0;
193
47.5k
    StreamReassembleLog(
194
47.5k
            ssn, stream, FrameJsonStreamDataCallback, &cbd, frame->offset, &unused, false);
195
    /* if we have all data, but didn't log until the end of the frame, we have a gap at the
196
     * end of the frame
197
     * TODO what about not logging due to buffer full? */
198
47.5k
    if (complete && frame->len >= 0 && cbd.last_re < frame->offset + (uint64_t)frame->len) {
199
65
        MemBufferWriteString(cbd.payload, "[%" PRIu64 " bytes missing]",
200
65
                (frame->offset + (uint64_t)frame->len) - cbd.last_re);
201
65
    }
202
203
47.5k
    if (cbd.payload->offset) {
204
46.8k
        jb_set_base64(jb, "payload", cbd.payload->buffer, cbd.payload->offset);
205
46.8k
        SCJbSetPrintAsciiString(jb, "payload_printable", cbd.payload->buffer, cbd.payload->offset);
206
46.8k
        jb_set_bool(jb, "complete", complete);
207
46.8k
    }
208
47.5k
}
209
210
static void FrameAddPayloadUDP(JsonBuilder *js, const Packet *p, const Frame *frame)
211
221
{
212
221
    DEBUG_VALIDATE_BUG_ON(frame->offset >= p->payload_len);
213
221
    if (frame->offset >= p->payload_len)
214
0
        return;
215
216
221
    uint32_t frame_len;
217
221
    if (frame->len == -1) {
218
0
        frame_len = p->payload_len - frame->offset;
219
221
    } else {
220
221
        frame_len = (uint32_t)frame->len;
221
221
    }
222
221
    if (frame->offset + frame_len > p->payload_len) {
223
0
        frame_len = p->payload_len - frame->offset;
224
0
        JB_SET_FALSE(js, "complete");
225
221
    } else {
226
221
        JB_SET_TRUE(js, "complete");
227
221
    }
228
221
    const uint8_t *data = p->payload + frame->offset;
229
221
    const uint32_t data_len = frame_len;
230
231
221
    const uint32_t log_data_len = MIN(data_len, 256);
232
221
    jb_set_base64(js, "payload", data, log_data_len);
233
234
221
    SCJbSetPrintAsciiString(js, "payload_printable", data, log_data_len);
235
#if 0
236
    char pretty_buf[data_len * 4 + 1];
237
    pretty_buf[0] = '\0';
238
    PayloadAsHex(data, data_len, pretty_buf, data_len * 4 + 1);
239
    jb_set_string(js, "payload_hex", pretty_buf);
240
#endif
241
221
}
242
243
// TODO separate between stream_offset and frame_offset
244
/** \brief log a single frame
245
 *  \note ipproto argument is passed to assist static code analyzers
246
 */
247
void FrameJsonLogOneFrame(const uint8_t ipproto, const Frame *frame, Flow *f,
248
        const TcpStream *stream, const Packet *p, JsonBuilder *jb, MemBuffer *buffer)
249
47.8k
{
250
47.8k
    DEBUG_VALIDATE_BUG_ON(ipproto != p->proto);
251
47.8k
    DEBUG_VALIDATE_BUG_ON(ipproto != f->proto);
252
253
47.8k
    jb_open_object(jb, "frame");
254
47.8k
    if (frame->type == FRAME_STREAM_TYPE) {
255
6
        jb_set_string(jb, "type", "stream");
256
47.8k
    } else {
257
47.8k
        jb_set_string(jb, "type", AppLayerParserGetFrameNameById(ipproto, f->alproto, frame->type));
258
47.8k
    }
259
47.8k
    jb_set_uint(jb, "id", frame->id);
260
47.8k
    jb_set_string(jb, "direction", PKT_IS_TOSERVER(p) ? "toserver" : "toclient");
261
262
47.8k
    if (ipproto == IPPROTO_TCP) {
263
47.5k
        DEBUG_VALIDATE_BUG_ON(stream == NULL);
264
47.5k
        jb_set_uint(jb, "stream_offset", frame->offset);
265
266
47.5k
        if (frame->len < 0) {
267
12.7k
            uint64_t usable = StreamTcpGetUsable(stream, true);
268
12.7k
            uint64_t len = usable - frame->offset;
269
12.7k
            jb_set_uint(jb, "length", len);
270
34.7k
        } else {
271
34.7k
            jb_set_uint(jb, "length", frame->len);
272
34.7k
        }
273
47.5k
        FrameAddPayloadTCP(f, f->protoctx, stream, frame, jb, buffer);
274
47.5k
    } else {
275
221
        jb_set_uint(jb, "length", frame->len);
276
221
        FrameAddPayloadUDP(jb, p, frame);
277
221
    }
278
47.8k
    if (frame->flags & FRAME_FLAG_TX_ID_SET) {
279
46.3k
        jb_set_uint(jb, "tx_id", frame->tx_id);
280
46.3k
    }
281
47.8k
    jb_close(jb);
282
47.8k
}
283
284
static int FrameJsonUdp(
285
        JsonFrameLogThread *aft, const Packet *p, Flow *f, FramesContainer *frames_container)
286
0
{
287
0
    FrameJsonOutputCtx *json_output_ctx = aft->json_output_ctx;
288
289
0
    Frames *frames;
290
0
    if (PKT_IS_TOSERVER(p)) {
291
0
        frames = &frames_container->toserver;
292
0
    } else {
293
0
        frames = &frames_container->toclient;
294
0
    }
295
296
0
    for (uint32_t idx = 0; idx < frames->cnt; idx++) {
297
0
        Frame *frame = FrameGetByIndex(frames, idx);
298
0
        if (frame == NULL || frame->flags & FRAME_FLAG_LOGGED)
299
0
            continue;
300
301
        /* First initialize the address info (5-tuple). */
302
0
        JsonAddrInfo addr = json_addr_info_zero;
303
0
        JsonAddrInfoInit(p, LOG_DIR_PACKET, &addr);
304
305
0
        JsonBuilder *jb =
306
0
                CreateEveHeader(p, LOG_DIR_PACKET, "frame", &addr, json_output_ctx->eve_ctx);
307
0
        if (unlikely(jb == NULL))
308
0
            return TM_ECODE_OK;
309
310
0
        jb_set_string(jb, "app_proto", AppProtoToString(f->alproto));
311
0
        FrameJsonLogOneFrame(IPPROTO_UDP, frame, p->flow, NULL, p, jb, aft->payload_buffer);
312
0
        OutputJsonBuilderBuffer(jb, aft->ctx);
313
0
        jb_free(jb);
314
0
        frame->flags |= FRAME_FLAG_LOGGED;
315
0
    }
316
0
    return TM_ECODE_OK;
317
0
}
318
319
static int FrameJson(ThreadVars *tv, JsonFrameLogThread *aft, const Packet *p)
320
0
{
321
0
    FrameJsonOutputCtx *json_output_ctx = aft->json_output_ctx;
322
323
0
    BUG_ON(p->flow == NULL);
324
325
0
    FramesContainer *frames_container = AppLayerFramesGetContainer(p->flow);
326
0
    if (frames_container == NULL)
327
0
        return TM_ECODE_OK;
328
329
0
    if (p->proto == IPPROTO_UDP) {
330
0
        return FrameJsonUdp(aft, p, p->flow, frames_container);
331
0
    }
332
333
0
    BUG_ON(p->proto != IPPROTO_TCP);
334
0
    BUG_ON(p->flow->protoctx == NULL);
335
336
    /* TODO can we set these EOF flags once per packet? We have them in detect, tx, file, filedata,
337
     * etc */
338
0
    const bool last_pseudo = (p->flowflags & FLOW_PKT_LAST_PSEUDO) != 0;
339
0
    Frames *frames;
340
0
    TcpSession *ssn = p->flow->protoctx;
341
0
    bool eof = (ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
342
0
    TcpStream *stream;
343
0
    if (PKT_IS_TOSERVER(p)) {
344
0
        stream = &ssn->client;
345
0
        frames = &frames_container->toserver;
346
0
        SCLogDebug("TOSERVER base %" PRIu64 ", app %" PRIu64, STREAM_BASE_OFFSET(stream),
347
0
                STREAM_APP_PROGRESS(stream));
348
0
        eof |= AppLayerParserStateIssetFlag(p->flow->alparser, APP_LAYER_PARSER_EOF_TS) != 0;
349
0
    } else {
350
0
        stream = &ssn->server;
351
0
        frames = &frames_container->toclient;
352
0
        eof |= AppLayerParserStateIssetFlag(p->flow->alparser, APP_LAYER_PARSER_EOF_TC) != 0;
353
0
    }
354
0
    eof |= last_pseudo;
355
0
    SCLogDebug("eof %s", eof ? "true" : "false");
356
357
0
    for (uint32_t idx = 0; idx < frames->cnt; idx++) {
358
0
        Frame *frame = FrameGetByIndex(frames, idx);
359
0
        if (frame != NULL) {
360
0
            if (frame->flags & FRAME_FLAG_LOGGED)
361
0
                continue;
362
363
0
            int64_t abs_offset = (int64_t)frame->offset + (int64_t)STREAM_BASE_OFFSET(stream);
364
0
            int64_t win = STREAM_APP_PROGRESS(stream) - abs_offset;
365
366
0
            if (!eof && win < frame->len && win < 2500) {
367
0
                SCLogDebug("frame id %" PRIi64 " len %" PRIi64 ", win %" PRIi64
368
0
                           ", skipping logging",
369
0
                        frame->id, frame->len, win);
370
0
                continue;
371
0
            }
372
373
            /* First initialize the address info (5-tuple). */
374
0
            JsonAddrInfo addr = json_addr_info_zero;
375
0
            JsonAddrInfoInit(p, LOG_DIR_PACKET, &addr);
376
377
0
            JsonBuilder *jb =
378
0
                    CreateEveHeader(p, LOG_DIR_PACKET, "frame", &addr, json_output_ctx->eve_ctx);
379
0
            if (unlikely(jb == NULL))
380
0
                return TM_ECODE_OK;
381
382
0
            jb_set_string(jb, "app_proto", AppProtoToString(p->flow->alproto));
383
0
            FrameJsonLogOneFrame(IPPROTO_TCP, frame, p->flow, stream, p, jb, aft->payload_buffer);
384
0
            OutputJsonBuilderBuffer(jb, aft->ctx);
385
0
            jb_free(jb);
386
0
            frame->flags |= FRAME_FLAG_LOGGED;
387
0
        } else if (frame != NULL) {
388
0
            SCLogDebug("frame %p id %" PRIi64, frame, frame->id);
389
0
        }
390
0
    }
391
0
    return TM_ECODE_OK;
392
0
}
393
394
static int JsonFrameLogger(ThreadVars *tv, void *thread_data, const Packet *p)
395
0
{
396
0
    JsonFrameLogThread *aft = thread_data;
397
0
    return FrameJson(tv, aft, p);
398
0
}
399
400
static int JsonFrameLogCondition(ThreadVars *tv, void *thread_data, const Packet *p)
401
0
{
402
0
    if (p->flow == NULL || p->flow->alproto == ALPROTO_UNKNOWN)
403
0
        return FALSE;
404
405
0
    if ((p->proto == IPPROTO_TCP || p->proto == IPPROTO_UDP) && p->flow->alparser != NULL) {
406
0
        FramesContainer *frames_container = AppLayerFramesGetContainer(p->flow);
407
0
        if (frames_container == NULL)
408
0
            return FALSE;
409
410
0
        Frames *frames;
411
0
        if (PKT_IS_TOSERVER(p)) {
412
0
            frames = &frames_container->toserver;
413
0
        } else {
414
0
            frames = &frames_container->toclient;
415
0
        }
416
0
        return (frames->cnt != 0);
417
0
    }
418
0
    return FALSE;
419
0
}
420
421
static TmEcode JsonFrameLogThreadInit(ThreadVars *t, const void *initdata, void **data)
422
0
{
423
0
    JsonFrameLogThread *aft = SCCalloc(1, sizeof(JsonFrameLogThread));
424
0
    if (unlikely(aft == NULL))
425
0
        return TM_ECODE_FAILED;
426
427
0
    if (initdata == NULL) {
428
0
        SCLogDebug("Error getting context for EveLogFrame.  \"initdata\" argument NULL");
429
0
        goto error_exit;
430
0
    }
431
432
    /** Use the Output Context (file pointer and mutex) */
433
0
    FrameJsonOutputCtx *json_output_ctx = ((OutputCtx *)initdata)->data;
434
435
0
    aft->payload_buffer = MemBufferCreateNew(json_output_ctx->payload_buffer_size);
436
0
    if (aft->payload_buffer == NULL) {
437
0
        goto error_exit;
438
0
    }
439
0
    aft->ctx = CreateEveThreadCtx(t, json_output_ctx->eve_ctx);
440
0
    if (!aft->ctx) {
441
0
        goto error_exit;
442
0
    }
443
444
0
    aft->json_output_ctx = json_output_ctx;
445
446
0
    *data = (void *)aft;
447
0
    return TM_ECODE_OK;
448
449
0
error_exit:
450
0
    if (aft->payload_buffer != NULL) {
451
0
        MemBufferFree(aft->payload_buffer);
452
0
    }
453
0
    SCFree(aft);
454
0
    return TM_ECODE_FAILED;
455
0
}
456
457
static TmEcode JsonFrameLogThreadDeinit(ThreadVars *t, void *data)
458
0
{
459
0
    JsonFrameLogThread *aft = (JsonFrameLogThread *)data;
460
0
    if (aft == NULL) {
461
0
        return TM_ECODE_OK;
462
0
    }
463
464
0
    MemBufferFree(aft->payload_buffer);
465
0
    FreeEveThreadCtx(aft->ctx);
466
467
    /* clear memory */
468
0
    memset(aft, 0, sizeof(JsonFrameLogThread));
469
470
0
    SCFree(aft);
471
0
    return TM_ECODE_OK;
472
0
}
473
474
static void JsonFrameLogDeInitCtxSub(OutputCtx *output_ctx)
475
0
{
476
0
    SCLogDebug("cleaning up sub output_ctx %p", output_ctx);
477
478
0
    FrameJsonOutputCtx *json_output_ctx = (FrameJsonOutputCtx *)output_ctx->data;
479
480
0
    if (json_output_ctx != NULL) {
481
0
        SCFree(json_output_ctx);
482
0
    }
483
0
    SCFree(output_ctx);
484
0
}
485
486
/**
487
 * \brief Create a new LogFileCtx for "fast" output style.
488
 * \param conf The configuration node for this output.
489
 * \return A LogFileCtx pointer on success, NULL on failure.
490
 */
491
static OutputInitResult JsonFrameLogInitCtxSub(ConfNode *conf, OutputCtx *parent_ctx)
492
0
{
493
0
    OutputInitResult result = { NULL, false };
494
0
    OutputJsonCtx *ajt = parent_ctx->data;
495
0
    FrameJsonOutputCtx *json_output_ctx = NULL;
496
497
0
    OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
498
0
    if (unlikely(output_ctx == NULL))
499
0
        return result;
500
501
0
    json_output_ctx = SCMalloc(sizeof(FrameJsonOutputCtx));
502
0
    if (unlikely(json_output_ctx == NULL)) {
503
0
        goto error;
504
0
    }
505
0
    memset(json_output_ctx, 0, sizeof(FrameJsonOutputCtx));
506
507
0
    uint32_t payload_buffer_size = 4096;
508
0
    if (conf != NULL) {
509
0
        const char *payload_buffer_value = ConfNodeLookupChildValue(conf, "payload-buffer-size");
510
0
        if (payload_buffer_value != NULL) {
511
0
            uint32_t value;
512
0
            if (ParseSizeStringU32(payload_buffer_value, &value) < 0) {
513
0
                SCLogError("Error parsing payload-buffer-size \"%s\"", payload_buffer_value);
514
0
                goto error;
515
0
            }
516
0
            payload_buffer_size = value;
517
0
        }
518
0
    }
519
520
0
    json_output_ctx->file_ctx = ajt->file_ctx;
521
0
    json_output_ctx->eve_ctx = ajt;
522
0
    json_output_ctx->payload_buffer_size = payload_buffer_size;
523
524
0
    output_ctx->data = json_output_ctx;
525
0
    output_ctx->DeInit = JsonFrameLogDeInitCtxSub;
526
527
0
    FrameConfigEnableAll();
528
529
0
    result.ctx = output_ctx;
530
0
    result.ok = true;
531
0
    return result;
532
533
0
error:
534
0
    if (json_output_ctx != NULL) {
535
0
        SCFree(json_output_ctx);
536
0
    }
537
0
    if (output_ctx != NULL) {
538
0
        SCFree(output_ctx);
539
0
    }
540
541
0
    return result;
542
0
}
543
544
void JsonFrameLogRegister(void)
545
71
{
546
71
    OutputRegisterPacketSubModule(LOGGER_JSON_FRAME, "eve-log", MODULE_NAME, "eve-log.frame",
547
71
            JsonFrameLogInitCtxSub, JsonFrameLogger, JsonFrameLogCondition, JsonFrameLogThreadInit,
548
            JsonFrameLogThreadDeinit, NULL);
549
71
}