• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OISF / suricata / 23374838686

21 Mar 2026 07:29AM UTC coverage: 59.341% (-20.0%) from 79.315%
23374838686

Pull #15075

github

web-flow
Merge 90b4e834f into 6587e363a
Pull Request #15075: Stack 8001 v16.4

38 of 70 new or added lines in 10 files covered. (54.29%)

34165 existing lines in 563 files now uncovered.

119621 of 201584 relevant lines covered (59.34%)

650666.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.12
/src/detect-http-client-body.c
1
/* Copyright (C) 2007-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
 * \ingroup httplayer
20
 *
21
 * @{
22
 */
23

24

25
/**
26
 * \file
27
 *
28
 * \author Anoop Saldanha <anoopsaldanha@gmail.com>
29
 *
30
 * Implements support for the http_client_body keyword
31
 */
32

33
#include "suricata-common.h"
34
#include "threads.h"
35
#include "decode.h"
36

37
#include "detect.h"
38
#include "detect-parse.h"
39
#include "detect-engine.h"
40
#include "detect-engine-buffer.h"
41
#include "detect-engine-mpm.h"
42
#include "detect-engine-state.h"
43
#include "detect-engine-prefilter.h"
44
#include "detect-engine-content-inspection.h"
45
#include "detect-content.h"
46
#include "detect-pcre.h"
47
// PrefilterMpmFiledata
48
#include "detect-file-data.h"
49

50
#include "flow.h"
51
#include "flow-var.h"
52
#include "flow-util.h"
53

54
#include "util-debug.h"
55
#include "util-unittest.h"
56
#include "util-unittest-helper.h"
57
#include "util-spm.h"
58

59
#include "app-layer.h"
60
#include "app-layer-parser.h"
61
#include "app-layer-htp.h"
62
#include "detect-http-client-body.h"
63
#include "stream-tcp.h"
64
#include "util-profiling.h"
65

66
static int DetectHttpClientBodySetup(DetectEngineCtx *, Signature *, const char *);
67
static int DetectHttpClientBodySetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str);
68
#ifdef UNITTESTS
69
static void DetectHttpClientBodyRegisterTests(void);
70
#endif
71
static void DetectHttpClientBodySetupCallback(
72
        const DetectEngineCtx *de_ctx, Signature *s, const DetectBufferType *map);
73
static int g_http_client_body_buffer_id = 0;
74

75
static uint8_t DetectEngineInspectBufferHttpBody(DetectEngineCtx *de_ctx,
76
        DetectEngineThreadCtx *det_ctx, const DetectEngineAppInspectionEngine *engine,
77
        const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id);
78

79
static int PrefilterMpmHttpRequestBodyRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh,
80
        MpmCtx *mpm_ctx, const DetectBufferMpmRegistry *mpm_reg, int list_id);
81

82
/**
83
 * \brief Registers the keyword handlers for the "http_client_body" keyword.
84
 */
85
void DetectHttpClientBodyRegister(void)
86
{
3✔
87
    /* http_client_body content modifier */
88
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].name = "http_client_body";
3✔
89
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].desc =
3✔
90
            "content modifier to match only on HTTP request-body";
3✔
91
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].url = "/rules/http-keywords.html#http-client-body";
3✔
92
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].Setup = DetectHttpClientBodySetup;
3✔
93
#ifdef UNITTESTS
94
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].RegisterTests = DetectHttpClientBodyRegisterTests;
95
#endif
96
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].flags |= SIGMATCH_NOOPT;
3✔
97
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].flags |= SIGMATCH_INFO_CONTENT_MODIFIER;
3✔
98
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].alternative = DETECT_HTTP_REQUEST_BODY;
3✔
99

100
    /* http.request_body sticky buffer */
101
    sigmatch_table[DETECT_HTTP_REQUEST_BODY].name = "http.request_body";
3✔
102
    sigmatch_table[DETECT_HTTP_REQUEST_BODY].desc = "sticky buffer to match the HTTP request body buffer";
3✔
103
    sigmatch_table[DETECT_HTTP_REQUEST_BODY].url = "/rules/http-keywords.html#http-client-body";
3✔
104
    sigmatch_table[DETECT_HTTP_REQUEST_BODY].Setup = DetectHttpClientBodySetupSticky;
3✔
105
    sigmatch_table[DETECT_HTTP_REQUEST_BODY].flags |= SIGMATCH_NOOPT;
3✔
106
    sigmatch_table[DETECT_HTTP_REQUEST_BODY].flags |= SIGMATCH_INFO_STICKY_BUFFER;
3✔
107

108
    DetectAppLayerInspectEngineRegister("http_client_body", ALPROTO_HTTP1, SIG_FLAG_TOSERVER,
3✔
109
            HTP_REQUEST_PROGRESS_BODY, DetectEngineInspectBufferHttpBody, NULL);
3✔
110

111
    DetectAppLayerMpmRegister("http_client_body", SIG_FLAG_TOSERVER, 2,
3✔
112
            PrefilterMpmHttpRequestBodyRegister, NULL, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_BODY);
3✔
113

114
    DetectAppLayerInspectEngineRegister("http_client_body", ALPROTO_HTTP2, SIG_FLAG_TOSERVER,
3✔
115
            HTTP2StateDataClient, DetectEngineInspectFiledata, NULL);
3✔
116
    DetectAppLayerMpmRegister("http_client_body", SIG_FLAG_TOSERVER, 2,
3✔
117
            PrefilterMpmFiledataRegister, NULL, ALPROTO_HTTP2, HTTP2StateDataClient);
3✔
118

119
    DetectBufferTypeSetDescriptionByName("http_client_body",
3✔
120
            "http request body");
3✔
121

122
    DetectBufferTypeRegisterSetupCallback("http_client_body",
3✔
123
            DetectHttpClientBodySetupCallback);
3✔
124

125
    g_http_client_body_buffer_id = DetectBufferTypeGetByName("http_client_body");
3✔
126
}
3✔
127

128
static void DetectHttpClientBodySetupCallback(
129
        const DetectEngineCtx *de_ctx, Signature *s, const DetectBufferType *map)
130
{
16,080✔
131
    SCLogDebug("callback invoked by %u", s->id);
16,080✔
132
    AppLayerHtpEnableRequestBodyCallback();
16,080✔
133

134
    /* client body needs to be inspected in sync with stream if possible */
135
    s->init_data->init_flags |= SIG_FLAG_INIT_NEED_FLUSH;
16,080✔
136
}
16,080✔
137

138
/**
139
 * \brief The setup function for the http_client_body keyword for a signature.
140
 *
141
 * \param de_ctx Pointer to the detection engine context.
142
 * \param s      Pointer to signature for the current Signature being parsed
143
 *               from the rules.
144
 * \param m      Pointer to the head of the SigMatchs for the current rule
145
 *               being parsed.
146
 * \param arg    Pointer to the string holding the keyword value.
147
 *
148
 * \retval  0 On success
149
 * \retval -1 On failure
150
 */
151
int DetectHttpClientBodySetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
152
{
181✔
153
    return DetectEngineContentModifierBufferSetup(
181✔
154
            de_ctx, s, arg, DETECT_HTTP_CLIENT_BODY, g_http_client_body_buffer_id, ALPROTO_HTTP1);
181✔
155
}
181✔
156

157
/**
158
 * \brief this function setup the http.request_body keyword used in the rule
159
 *
160
 * \param de_ctx   Pointer to the Detection Engine Context
161
 * \param s        Pointer to the Signature to which the current keyword belongs
162
 * \param str      Should hold an empty string always
163
 *
164
 * \retval 0       On success
165
 */
166
static int DetectHttpClientBodySetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str)
167
{
12,338✔
168
    if (SCDetectBufferSetActiveList(de_ctx, s, g_http_client_body_buffer_id) < 0)
12,338✔
169
        return -1;
73✔
170
    if (SCDetectSignatureSetAppProto(s, ALPROTO_HTTP) < 0)
12,265✔
171
        return -1;
178✔
172
    // we cannot use a transactional rule with a fast pattern to client and this
173
    if (s->init_data->init_flags & SIG_FLAG_INIT_TXDIR_FAST_TOCLIENT) {
12,087✔
174
        SCLogError("fast_pattern cannot be used on to_client keyword for "
1✔
175
                   "transactional rule with a streaming buffer to server %u",
1✔
176
                s->id);
1✔
177
        return -1;
1✔
178
    }
1✔
179
    s->init_data->init_flags |= SIG_FLAG_INIT_TXDIR_STREAMING_TOSERVER;
12,086✔
180
    return 0;
12,086✔
181
}
12,087✔
182

183
static inline HtpBody *GetRequestBody(htp_tx_t *tx)
184
{
385✔
185
    HtpTxUserData *htud = (HtpTxUserData *)htp_tx_get_user_data(tx);
385✔
186
    return &htud->request_body;
385✔
187
}
385✔
188

189
typedef struct PrefilterMpmHttpRequestBody {
190
    int list_id;
191
    int base_list_id;
192
    const MpmCtx *mpm_ctx;
193
    const DetectEngineTransforms *transforms;
194
} PrefilterMpmHttpRequestBody;
195

196
static void PrefilterMpmHttpRequestBodyFree(void *ptr)
197
{
1,008✔
198
    SCFree(ptr);
1,008✔
199
}
1,008✔
200

201
static inline InspectionBuffer *HttpRequestBodyXformsGetDataCallback(DetectEngineThreadCtx *det_ctx,
202
        const DetectEngineTransforms *transforms, const int list_id, InspectionBuffer *base_buffer)
203
{
41✔
204
    InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id);
41✔
205
    if (buffer->inspect != NULL)
41✔
206
        return buffer;
18✔
207

208
    InspectionBufferSetup(det_ctx, list_id, buffer, base_buffer->inspect, base_buffer->inspect_len);
23✔
209
    buffer->inspect_offset = base_buffer->inspect_offset;
23✔
210
    InspectionBufferApplyTransforms(det_ctx, buffer, transforms);
23✔
211
    SCLogDebug("xformed buffer %p size %u", buffer, buffer->inspect_len);
23✔
212
    SCReturnPtr(buffer, "InspectionBuffer");
23✔
213
}
41✔
214

215
static InspectionBuffer *HttpRequestBodyGetDataCallback(DetectEngineThreadCtx *det_ctx,
216
        const DetectEngineTransforms *transforms, Flow *f, const uint8_t flow_flags, void *txv,
217
        const int list_id, const int base_id)
218
{
433✔
219
    SCEnter();
433✔
220

221
    InspectionBuffer *buffer = InspectionBufferGet(det_ctx, base_id);
433✔
222
    if (base_id != list_id && buffer->inspect != NULL)
433✔
223
        return HttpRequestBodyXformsGetDataCallback(det_ctx, transforms, list_id, buffer);
21✔
224
    else if (buffer->inspect != NULL)
412✔
225
        return buffer;
27✔
226

227
    htp_tx_t *tx = txv;
385✔
228
    HtpState *htp_state = f->alstate;
385✔
229
    const uint8_t flags = flow_flags;
385✔
230

231
    HtpBody *body = GetRequestBody(tx);
385✔
232
    if (body == NULL) {
385✔
233
        return NULL;
×
234
    }
×
235

236
    /* no new data */
237
    if (body->body_inspected == body->content_len_so_far) {
385✔
238
        SCLogDebug("no new data");
337✔
239
        return NULL;
337✔
240
    }
337✔
241

242
    HtpBodyChunk *cur = body->first;
48✔
243
    if (cur == NULL) {
48✔
244
        SCLogDebug("No http chunks to inspect for this transaction");
×
245
        return NULL;
×
246
    }
×
247

248
    SCLogDebug("request.body_limit %u request_body.content_len_so_far %" PRIu64
48✔
249
               ", request.inspect_min_size %" PRIu32 ", EOF %s, progress > body? %s",
48✔
250
            htp_state->cfg->request.body_limit, body->content_len_so_far,
48✔
251
            htp_state->cfg->request.inspect_min_size, flags & STREAM_EOF ? "true" : "false",
48✔
252
            (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, tx, flags) >
48✔
253
                    HTP_REQUEST_PROGRESS_BODY)
48✔
254
                    ? "true"
48✔
255
                    : "false");
48✔
256

257
    if (!htp_state->cfg->http_body_inline) {
48✔
258
        /* inspect the body if the transfer is complete or we have hit
259
        * our body size limit */
260
        if ((htp_state->cfg->request.body_limit == 0 ||
48✔
261
                    body->content_len_so_far < htp_state->cfg->request.body_limit) &&
48✔
262
                body->content_len_so_far < htp_state->cfg->request.inspect_min_size &&
48✔
263
                !(AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, tx, flags) >
48✔
264
                        HTP_REQUEST_PROGRESS_BODY) &&
48✔
265
                !(flags & STREAM_EOF)) {
48✔
266
            SCLogDebug("we still haven't seen the entire request body.  "
5✔
267
                       "Let's defer body inspection till we see the "
5✔
268
                       "entire body.");
5✔
269
            return NULL;
5✔
270
        }
5✔
271
    }
48✔
272

273
    /* get the inspect buffer
274
     *
275
     * make sure that we have at least the configured inspect_win size.
276
     * If we have more, take at least 1/4 of the inspect win size before
277
     * the new data.
278
     */
279
    uint64_t offset = 0;
43✔
280
    if (body->body_inspected > htp_state->cfg->request.inspect_min_size) {
43✔
UNCOV
281
        BUG_ON(body->content_len_so_far < body->body_inspected);
×
UNCOV
282
        uint64_t inspect_win = body->content_len_so_far - body->body_inspected;
×
UNCOV
283
        SCLogDebug("inspect_win %"PRIu64, inspect_win);
×
UNCOV
284
        if (inspect_win < htp_state->cfg->request.inspect_window) {
×
UNCOV
285
            uint64_t inspect_short = htp_state->cfg->request.inspect_window - inspect_win;
×
UNCOV
286
            if (body->body_inspected < inspect_short)
×
287
                offset = 0;
×
UNCOV
288
            else
×
UNCOV
289
                offset = body->body_inspected - inspect_short;
×
UNCOV
290
        } else {
×
UNCOV
291
            offset = body->body_inspected - (htp_state->cfg->request.inspect_window / 4);
×
UNCOV
292
        }
×
UNCOV
293
    }
×
294

295
    const uint8_t *data;
43✔
296
    uint32_t data_len;
43✔
297

298
    StreamingBufferGetDataAtOffset(body->sb,
43✔
299
            &data, &data_len, offset);
43✔
300
    InspectionBufferSetup(det_ctx, base_id, buffer, data, data_len);
43✔
301
    buffer->inspect_offset = offset;
43✔
302
    body->body_inspected = body->content_len_so_far;
43✔
303
    SCLogDebug("body->body_inspected now: %" PRIu64, body->body_inspected);
43✔
304

305
    if (base_id != list_id) {
43✔
306
        buffer = HttpRequestBodyXformsGetDataCallback(det_ctx, transforms, list_id, buffer);
20✔
307
    }
20✔
308
    SCReturnPtr(buffer, "InspectionBuffer");
43✔
309
}
43✔
310

311
static uint8_t DetectEngineInspectBufferHttpBody(DetectEngineCtx *de_ctx,
312
        DetectEngineThreadCtx *det_ctx, const DetectEngineAppInspectionEngine *engine,
313
        const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
314
{
72✔
315
    bool eof =
72✔
316
            (AppLayerParserGetStateProgress(f->proto, f->alproto, txv, flags) > engine->progress);
72✔
317
    const InspectionBuffer *buffer = HttpRequestBodyGetDataCallback(
72✔
318
            det_ctx, engine->v2.transforms, f, flags, txv, engine->sm_list, engine->sm_list_base);
72✔
319
    if (buffer == NULL || buffer->inspect == NULL) {
72✔
320
        if (eof && engine->match_on_null) {
21✔
321
            return DETECT_ENGINE_INSPECT_SIG_MATCH;
11✔
322
        }
11✔
323
        return eof ? DETECT_ENGINE_INSPECT_SIG_CANT_MATCH : DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
10✔
324
    }
21✔
325

326
    const uint32_t data_len = buffer->inspect_len;
51✔
327
    const uint8_t *data = buffer->inspect;
51✔
328
    const uint64_t offset = buffer->inspect_offset;
51✔
329

330
    uint8_t ci_flags = eof ? DETECT_CI_FLAGS_END : 0;
51✔
331
    ci_flags |= (offset == 0 ? DETECT_CI_FLAGS_START : 0);
51✔
332
    ci_flags |= buffer->flags;
51✔
333

334
    /* Inspect all the uricontents fetched on each
335
     * transaction at the app layer */
336
    const bool match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, NULL, f, data,
51✔
337
            data_len, offset, ci_flags, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE);
51✔
338
    if (match) {
51✔
339
        return DETECT_ENGINE_INSPECT_SIG_MATCH;
43✔
340
    }
43✔
341

342
    if (flags & STREAM_TOSERVER) {
8✔
343
        if (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, txv, flags) >
8✔
344
                HTP_REQUEST_PROGRESS_BODY)
8✔
345
            return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH;
8✔
346
    } else {
8✔
UNCOV
347
        if (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, txv, flags) >
×
UNCOV
348
                HTP_RESPONSE_PROGRESS_BODY)
×
349
            return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH;
×
UNCOV
350
    }
×
UNCOV
351
    return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
×
352
}
8✔
353

354
/** \brief HTTP Request body callback
355
 *
356
 *  \param det_ctx detection engine thread ctx
357
 *  \param pectx inspection context
358
 *  \param p packet to inspect
359
 *  \param f flow to inspect
360
 *  \param txv tx to inspect
361
 *  \param idx transaction id
362
 *  \param flags STREAM_* flags including direction
363
 */
364
static void PrefilterTxHttpRequestBody(DetectEngineThreadCtx *det_ctx, const void *pectx, Packet *p,
365
        Flow *f, void *txv, const uint64_t idx, const AppLayerTxData *_txd, const uint8_t flags)
366
{
361✔
367
    SCEnter();
361✔
368

369
    const PrefilterMpmHttpRequestBody *ctx = (const PrefilterMpmHttpRequestBody *)pectx;
361✔
370
    const MpmCtx *mpm_ctx = ctx->mpm_ctx;
361✔
371
    const int list_id = ctx->list_id;
361✔
372

373
    InspectionBuffer *buffer = HttpRequestBodyGetDataCallback(
361✔
374
            det_ctx, ctx->transforms, f, flags, txv, list_id, ctx->base_list_id);
361✔
375
    if (buffer == NULL)
361✔
376
        return;
321✔
377

378
    if (buffer->inspect_len >= mpm_ctx->minlen) {
40✔
379
        (void)mpm_table[mpm_ctx->mpm_type].Search(
39✔
380
                mpm_ctx, &det_ctx->mtc, &det_ctx->pmq, buffer->inspect, buffer->inspect_len);
39✔
381
        PREFILTER_PROFILING_ADD_BYTES(det_ctx, buffer->inspect_len);
39✔
382
    }
39✔
383
}
40✔
384

385
static int PrefilterMpmHttpRequestBodyRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh,
386
        MpmCtx *mpm_ctx, const DetectBufferMpmRegistry *mpm_reg, int list_id)
387
{
514✔
388
    PrefilterMpmHttpRequestBody *pectx = SCCalloc(1, sizeof(*pectx));
514✔
389
    if (pectx == NULL)
514✔
390
        return -1;
×
391
    pectx->list_id = list_id;
514✔
392
    pectx->base_list_id = mpm_reg->sm_list_base;
514✔
393
    SCLogDebug("list_id %d base_list_id %d", list_id, pectx->base_list_id);
514✔
394
    pectx->mpm_ctx = mpm_ctx;
514✔
395
    pectx->transforms = &mpm_reg->transforms;
514✔
396

397
    return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxHttpRequestBody, mpm_reg->app_v2.alproto,
514✔
398
            mpm_reg->app_v2.tx_min_progress, pectx, PrefilterMpmHttpRequestBodyFree,
514✔
399
            mpm_reg->pname);
514✔
400
}
514✔
401

402
#ifdef UNITTESTS
403
#include "detect-engine-alert.h"
404
#include "tests/detect-http-client-body.c"
405
#endif /* UNITTESTS */
406

407
/**
408
 * @}
409
 */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc