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

OISF / suricata / 22670973087

04 Mar 2026 01:15PM UTC coverage: 73.661% (+31.4%) from 42.258%
22670973087

Pull #14939

github

web-flow
Merge c9ac53dc3 into 8df4e1001
Pull Request #14939: Stack 8001 v14

38294 of 77534 branches covered (49.39%)

Branch coverage included in aggregate %.

11 of 13 new or added lines in 5 files covered. (84.62%)

14 existing lines in 6 files now uncovered.

265663 of 335110 relevant lines covered (79.28%)

5049209.51 hits per line

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

84.3
/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
{
2,193✔
87
    /* http_client_body content modifier */
88
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].name = "http_client_body";
2,193✔
89
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].desc =
2,193✔
90
            "content modifier to match only on HTTP request-body";
2,193✔
91
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].url = "/rules/http-keywords.html#http-client-body";
2,193✔
92
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].Setup = DetectHttpClientBodySetup;
2,193✔
93
#ifdef UNITTESTS
3✔
94
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].RegisterTests = DetectHttpClientBodyRegisterTests;
3✔
95
#endif
3✔
96
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].flags |= SIGMATCH_NOOPT;
2,193✔
97
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].flags |= SIGMATCH_INFO_CONTENT_MODIFIER;
2,193✔
98
    sigmatch_table[DETECT_HTTP_CLIENT_BODY].alternative = DETECT_HTTP_REQUEST_BODY;
2,193✔
99

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

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

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

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

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

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

125
    g_http_client_body_buffer_id = DetectBufferTypeGetByName("http_client_body");
2,193✔
126
}
2,193✔
127

128
static void DetectHttpClientBodySetupCallback(
129
        const DetectEngineCtx *de_ctx, Signature *s, const DetectBufferType *map)
130
{
29,153✔
131
    SCLogDebug("callback invoked by %u", s->id);
29,153!
132
    AppLayerHtpEnableRequestBodyCallback();
29,153✔
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;
29,153✔
136
}
29,153✔
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
{
327✔
153
    return DetectEngineContentModifierBufferSetup(
327✔
154
            de_ctx, s, arg, DETECT_HTTP_CLIENT_BODY, g_http_client_body_buffer_id, ALPROTO_HTTP1);
327✔
155
}
327✔
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
{
18,814✔
168
    if (SCDetectBufferSetActiveList(de_ctx, s, g_http_client_body_buffer_id) < 0)
18,814!
169
        return -1;
73✔
170
    if (SCDetectSignatureSetAppProto(s, ALPROTO_HTTP) < 0)
18,741!
171
        return -1;
179✔
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) {
18,562!
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;
18,561✔
180
    return 0;
18,561✔
181
}
18,562✔
182

183
static inline HtpBody *GetRequestBody(htp_tx_t *tx)
184
{
2,774✔
185
    HtpTxUserData *htud = (HtpTxUserData *)htp_tx_get_user_data(tx);
2,774✔
186
    return &htud->request_body;
2,774✔
187
}
2,774✔
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,216✔
198
    SCFree(ptr);
1,216✔
199
}
1,216✔
200

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

208
    InspectionBufferSetup(det_ctx, list_id, buffer, base_buffer->inspect, base_buffer->inspect_len);
30✔
209
    buffer->inspect_offset = base_buffer->inspect_offset;
30✔
210
    InspectionBufferApplyTransforms(det_ctx, buffer, transforms);
30✔
211
    SCLogDebug("xformed buffer %p size %u", buffer, buffer->inspect_len);
30!
212
    SCReturnPtr(buffer, "InspectionBuffer");
30✔
213
}
57✔
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
{
2,909✔
219
    SCEnter();
2,909✔
220

221
    InspectionBuffer *buffer = InspectionBufferGet(det_ctx, base_id);
2,909✔
222
    if (base_id != list_id && buffer->inspect != NULL)
2,909✔
223
        return HttpRequestBodyXformsGetDataCallback(det_ctx, transforms, list_id, buffer);
34✔
224
    else if (buffer->inspect != NULL)
2,875✔
225
        return buffer;
102✔
226

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

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

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

242
    HtpBodyChunk *cur = body->first;
288✔
243
    if (cur == NULL) {
288!
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
288!
249
               ", request.inspect_min_size %" PRIu32 ", EOF %s, progress > body? %s",
288✔
250
            htp_state->cfg->request.body_limit, body->content_len_so_far,
288✔
251
            htp_state->cfg->request.inspect_min_size, flags & STREAM_EOF ? "true" : "false",
288✔
252
            (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, tx, flags) >
288✔
253
                    HTP_REQUEST_PROGRESS_BODY)
288✔
254
                    ? "true"
288✔
255
                    : "false");
288✔
256

257
    if (!htp_state->cfg->http_body_inline) {
289!
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 ||
289!
261
                    body->content_len_so_far < htp_state->cfg->request.body_limit) &&
289!
262
                body->content_len_so_far < htp_state->cfg->request.inspect_min_size &&
289!
263
                !(AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, tx, flags) >
289✔
264
                        HTP_REQUEST_PROGRESS_BODY) &&
265✔
265
                !(flags & STREAM_EOF)) {
289!
266
            SCLogDebug("we still haven't seen the entire request body.  "
61!
267
                       "Let's defer body inspection till we see the "
61✔
268
                       "entire body.");
61✔
269
            return NULL;
61✔
270
        }
61✔
271
    }
289✔
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;
227✔
280
    if (body->body_inspected > htp_state->cfg->request.inspect_min_size) {
227!
281
        BUG_ON(body->content_len_so_far < body->body_inspected);
19!
282
        uint64_t inspect_win = body->content_len_so_far - body->body_inspected;
19✔
283
        SCLogDebug("inspect_win %"PRIu64, inspect_win);
19!
284
        if (inspect_win < htp_state->cfg->request.inspect_window) {
19!
285
            uint64_t inspect_short = htp_state->cfg->request.inspect_window - inspect_win;
14✔
286
            if (body->body_inspected < inspect_short)
14!
287
                offset = 0;
×
288
            else
14✔
289
                offset = body->body_inspected - inspect_short;
14✔
290
        } else {
16✔
291
            offset = body->body_inspected - (htp_state->cfg->request.inspect_window / 4);
5✔
292
        }
5✔
293
    }
19✔
294

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

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

305
    if (base_id != list_id) {
227✔
306
        buffer = HttpRequestBodyXformsGetDataCallback(det_ctx, transforms, list_id, buffer);
23✔
307
    }
23✔
308
    SCReturnPtr(buffer, "InspectionBuffer");
227✔
309
}
227✔
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
{
201✔
315
    bool eof =
201✔
316
            (AppLayerParserGetStateProgress(f->proto, f->alproto, txv, flags) > engine->progress);
201✔
317
    const InspectionBuffer *buffer = HttpRequestBodyGetDataCallback(
201✔
318
            det_ctx, engine->v2.transforms, f, flags, txv, engine->sm_list, engine->sm_list_base);
201✔
319
    if (buffer == NULL || buffer->inspect == NULL) {
203!
320
        if (eof && engine->match_on_null) {
46✔
321
            return DETECT_ENGINE_INSPECT_SIG_MATCH;
15✔
322
        }
15✔
323
        return eof ? DETECT_ENGINE_INSPECT_SIG_CANT_MATCH : DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
31✔
324
    }
46✔
325

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

330
    uint8_t ci_flags = eof ? DETECT_CI_FLAGS_END : 0;
155!
331
    ci_flags |= (offset == 0 ? DETECT_CI_FLAGS_START : 0);
2,147,483,774!
332
    ci_flags |= buffer->flags;
155✔
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,
155✔
337
            data_len, offset, ci_flags, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE);
155✔
338
    if (match) {
155✔
339
        return DETECT_ENGINE_INSPECT_SIG_MATCH;
113✔
340
    }
113✔
341

342
    if (flags & STREAM_TOSERVER) {
43!
343
        if (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, txv, flags) >
43!
344
                HTP_REQUEST_PROGRESS_BODY)
43✔
345
            return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH;
42✔
346
    } else {
2,147,483,669✔
347
        if (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, txv, flags) >
2,147,483,647!
348
                HTP_RESPONSE_PROGRESS_BODY)
2,147,483,647✔
349
            return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH;
×
350
    }
2,147,483,647✔
UNCOV
351
    return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
×
352
}
42✔
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
{
2,708✔
367
    SCEnter();
2,708✔
368

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

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

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

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

397
    return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxHttpRequestBody, mpm_reg->app_v2.alproto,
635✔
398
            mpm_reg->app_v2.tx_min_progress, pectx, PrefilterMpmHttpRequestBodyFree,
635✔
399
            mpm_reg->pname);
635✔
400
}
635✔
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