Coverage Report

Created: 2025-07-23 07:29

/src/suricata7/src/detect-rpc.c
Line
Count
Source (jump to first uncovered line)
1
/* Copyright (C) 2007-2020 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 Pablo Rincon <pablo.rincon.crespo@gmail.com>
22
 *
23
 * Implements RPC keyword
24
 */
25
26
#include "suricata-common.h"
27
#include "decode.h"
28
29
#include "detect.h"
30
#include "detect-rpc.h"
31
#include "detect-parse.h"
32
#include "detect-engine.h"
33
#include "detect-engine-mpm.h"
34
#include "detect-engine-siggroup.h"
35
#include "detect-engine-address.h"
36
#include "detect-engine-build.h"
37
38
#include "util-unittest.h"
39
#include "util-unittest-helper.h"
40
#include "util-debug.h"
41
#include "util-byte.h"
42
43
/**
44
 * \brief Regex for parsing our rpc options
45
 */
46
73
#define PARSE_REGEX  "^\\s*([0-9]{0,10})\\s*(?:,\\s*([0-9]{0,10}|[*])\\s*(?:,\\s*([0-9]{0,10}|[*]))?)?\\s*$"
47
48
static DetectParseRegex parse_regex;
49
50
static int DetectRpcMatch (DetectEngineThreadCtx *, Packet *,
51
        const Signature *, const SigMatchCtx *);
52
static int DetectRpcSetup (DetectEngineCtx *, Signature *, const char *);
53
#ifdef UNITTESTS
54
static void DetectRpcRegisterTests(void);
55
#endif
56
void DetectRpcFree(DetectEngineCtx *, void *);
57
58
/**
59
 * \brief Registration function for rpc keyword
60
 */
61
void DetectRpcRegister (void)
62
73
{
63
73
    sigmatch_table[DETECT_RPC].name = "rpc";
64
73
    sigmatch_table[DETECT_RPC].desc = "match RPC procedure numbers and RPC version";
65
73
    sigmatch_table[DETECT_RPC].url = "/rules/payload-keywords.html#rpc";
66
73
    sigmatch_table[DETECT_RPC].Match = DetectRpcMatch;
67
73
    sigmatch_table[DETECT_RPC].Setup = DetectRpcSetup;
68
73
    sigmatch_table[DETECT_RPC].Free  = DetectRpcFree;
69
#ifdef UNITTESTS
70
    sigmatch_table[DETECT_RPC].RegisterTests = DetectRpcRegisterTests;
71
#endif
72
73
    DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
73
73
}
74
75
/*
76
 * returns 0: no match
77
 *         1: match
78
 *        -1: error
79
 */
80
81
/**
82
 * \brief This function is used to match rpc request set on a packet with those passed via rpc
83
 *
84
 * \param t pointer to thread vars
85
 * \param det_ctx pointer to the pattern matcher thread
86
 * \param p pointer to the current packet
87
 * \param m pointer to the sigmatch that we will cast into DetectRpcData
88
 *
89
 * \retval 0 no match
90
 * \retval 1 match
91
 */
92
static int DetectRpcMatch (DetectEngineThreadCtx *det_ctx, Packet *p,
93
        const Signature *s, const SigMatchCtx *ctx)
94
23.8k
{
95
    /* PrintRawDataFp(stdout, p->payload, p->payload_len); */
96
23.8k
    const DetectRpcData *rd = (const DetectRpcData *)ctx;
97
23.8k
    char *rpcmsg = (char *)p->payload;
98
99
23.8k
    if (PKT_IS_TCP(p)) {
100
        /* if Rpc msg too small */
101
19.2k
        if (p->payload_len < 28) {
102
12.6k
            SCLogDebug("TCP packet to small for the rpc msg (%u)", p->payload_len);
103
12.6k
            return 0;
104
12.6k
        }
105
6.65k
        rpcmsg += 4;
106
6.65k
    } else if (PKT_IS_UDP(p)) {
107
        /* if Rpc msg too small */
108
1.25k
        if (p->payload_len < 24) {
109
2
            SCLogDebug("UDP packet to small for the rpc msg (%u)", p->payload_len);
110
2
            return 0;
111
2
        }
112
3.31k
    } else {
113
3.31k
        SCLogDebug("No valid proto for the rpc message");
114
3.31k
        return 0;
115
3.31k
    }
116
117
    /* Point through the rpc msg structure. Use SCNtohl() to compare values */
118
7.90k
    RpcMsg *msg = (RpcMsg *)rpcmsg;
119
120
    /* If its not a call, no match */
121
7.90k
    if (SCNtohl(msg->type) != 0) {
122
6.32k
        SCLogDebug("RPC message type is not a call");
123
6.32k
        return 0;
124
6.32k
    }
125
126
1.58k
    if (SCNtohl(msg->prog) != rd->program)
127
1.56k
        return 0;
128
129
25
    if ((rd->flags & DETECT_RPC_CHECK_VERSION) && SCNtohl(msg->vers) != rd->program_version)
130
0
        return 0;
131
132
25
    if ((rd->flags & DETECT_RPC_CHECK_PROCEDURE) && SCNtohl(msg->proc) != rd->procedure)
133
0
        return 0;
134
135
25
    SCLogDebug("prog:%u pver:%u proc:%u matched", SCNtohl(msg->prog), SCNtohl(msg->vers), SCNtohl(msg->proc));
136
25
    return 1;
137
25
}
138
139
/**
140
 * \brief This function is used to parse rpc options passed via rpc keyword
141
 *
142
 * \param de_ctx Pointer to the detection engine context
143
 * \param rpcstr Pointer to the user provided rpc options
144
 *
145
 * \retval rd pointer to DetectRpcData on success
146
 * \retval NULL on failure
147
 */
148
static DetectRpcData *DetectRpcParse (DetectEngineCtx *de_ctx, const char *rpcstr)
149
6.32k
{
150
6.32k
    DetectRpcData *rd = NULL;
151
6.32k
    char *args[3] = {NULL,NULL,NULL};
152
6.32k
    int res = 0;
153
6.32k
    size_t pcre2_len;
154
155
6.32k
    pcre2_match_data *match = NULL;
156
6.32k
    int ret = DetectParsePcreExec(&parse_regex, &match, rpcstr, 0, 0);
157
6.32k
    if (ret < 1 || ret > 4) {
158
242
        SCLogError("parse error, ret %" PRId32 ", string %s", ret, rpcstr);
159
242
        goto error;
160
242
    }
161
162
6.07k
    if (ret > 1) {
163
6.07k
        const char *str_ptr;
164
6.07k
        res = pcre2_substring_get_bynumber(match, 1, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len);
165
6.07k
        if (res < 0) {
166
0
            SCLogError("pcre2_substring_get_bynumber failed");
167
0
            goto error;
168
0
        }
169
6.07k
        args[0] = (char *)str_ptr;
170
171
6.07k
        if (ret > 2) {
172
4.84k
            res = pcre2_substring_get_bynumber(match, 2, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len);
173
4.84k
            if (res < 0) {
174
0
                SCLogError("pcre2_substring_get_bynumber failed");
175
0
                goto error;
176
0
            }
177
4.84k
            args[1] = (char *)str_ptr;
178
4.84k
        }
179
6.07k
        if (ret > 3) {
180
3.68k
            res = pcre2_substring_get_bynumber(match, 3, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len);
181
3.68k
            if (res < 0) {
182
0
                SCLogError("pcre2_substring_get_bynumber failed");
183
0
                goto error;
184
0
            }
185
3.68k
            args[2] = (char *)str_ptr;
186
3.68k
        }
187
6.07k
    }
188
189
6.07k
    rd = SCMalloc(sizeof(DetectRpcData));
190
6.07k
    if (unlikely(rd == NULL))
191
0
        goto error;
192
6.07k
    rd->flags = 0;
193
6.07k
    rd->program = 0;
194
6.07k
    rd->program_version = 0;
195
6.07k
    rd->procedure = 0;
196
197
6.07k
    int i;
198
20.6k
    for (i = 0; i < (ret - 1); i++) {
199
14.6k
        if (args[i]) {
200
14.6k
            switch (i) {
201
6.07k
                case 0:
202
6.07k
                    if (StringParseUint32(&rd->program, 10, strlen(args[i]), args[i]) <= 0) {
203
6
                        SCLogError("Invalid size specified for the rpc program:\"%s\"", args[i]);
204
6
                        goto error;
205
6
                    }
206
6.07k
                    rd->flags |= DETECT_RPC_CHECK_PROGRAM;
207
6.07k
                    break;
208
4.83k
                case 1:
209
4.83k
                    if (args[i][0] != '*') {
210
2.86k
                        if (StringParseUint32(&rd->program_version, 10, strlen(args[i]), args[i]) <= 0) {
211
5
                            SCLogError(
212
5
                                    "Invalid size specified for the rpc version:\"%s\"", args[i]);
213
5
                            goto error;
214
5
                        }
215
2.85k
                        rd->flags |= DETECT_RPC_CHECK_VERSION;
216
2.85k
                    }
217
4.83k
                    break;
218
4.83k
                case 2:
219
3.68k
                    if (args[i][0] != '*') {
220
613
                        if (StringParseUint32(&rd->procedure, 10, strlen(args[i]), args[i]) <= 0) {
221
39
                            SCLogError(
222
39
                                    "Invalid size specified for the rpc procedure:\"%s\"", args[i]);
223
39
                            goto error;
224
39
                        }
225
574
                        rd->flags |= DETECT_RPC_CHECK_PROCEDURE;
226
574
                    }
227
3.64k
                break;
228
14.6k
            }
229
14.6k
        } else {
230
0
            SCLogError("invalid rpc option %s", rpcstr);
231
0
            goto error;
232
0
        }
233
14.6k
    }
234
20.4k
    for (i = 0; i < (ret -1); i++){
235
14.4k
        if (args[i] != NULL)
236
14.4k
            pcre2_substring_free((PCRE2_UCHAR8 *)args[i]);
237
14.4k
    }
238
6.02k
    pcre2_match_data_free(match);
239
6.02k
    return rd;
240
241
292
error:
242
292
    if (match) {
243
292
        pcre2_match_data_free(match);
244
292
    }
245
428
    for (i = 0; i < (ret -1) && i < 3; i++){
246
136
        if (args[i] != NULL)
247
136
            pcre2_substring_free((PCRE2_UCHAR8 *)args[i]);
248
136
    }
249
292
    if (rd != NULL)
250
50
        DetectRpcFree(de_ctx, rd);
251
292
    return NULL;
252
253
6.07k
}
254
255
/**
256
 * \brief this function is used to add the parsed rpcdata into the current signature
257
 *
258
 * \param de_ctx pointer to the Detection Engine Context
259
 * \param s pointer to the Current Signature
260
 * \param m pointer to the Current SigMatch
261
 * \param rpcstr pointer to the user provided rpc options
262
 *
263
 * \retval 0 on Success
264
 * \retval -1 on Failure
265
 */
266
int DetectRpcSetup (DetectEngineCtx *de_ctx, Signature *s, const char *rpcstr)
267
4.27k
{
268
4.27k
    DetectRpcData *rd = NULL;
269
4.27k
    SigMatch *sm = NULL;
270
271
4.27k
    rd = DetectRpcParse(de_ctx, rpcstr);
272
4.27k
    if (rd == NULL) goto error;
273
274
4.20k
    sm = SigMatchAlloc();
275
4.20k
    if (sm == NULL)
276
0
        goto error;
277
278
4.20k
    sm->type = DETECT_RPC;
279
4.20k
    sm->ctx = (SigMatchCtx *)rd;
280
281
4.20k
    SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_MATCH);
282
4.20k
    s->flags |= SIG_FLAG_REQUIRE_PACKET;
283
284
4.20k
    return 0;
285
286
65
error:
287
65
    if (rd != NULL) DetectRpcFree(de_ctx, rd);
288
65
    if (sm != NULL) SCFree(sm);
289
65
    return -1;
290
291
4.20k
}
292
293
/**
294
 * \brief this function will free memory associated with DetectRpcData
295
 *
296
 * \param rd pointer to DetectRpcData
297
 */
298
void DetectRpcFree(DetectEngineCtx *de_ctx, void *ptr)
299
6.07k
{
300
6.07k
    SCEnter();
301
302
6.07k
    if (ptr == NULL) {
303
0
        SCReturn;
304
0
    }
305
306
6.07k
    DetectRpcData *rd = (DetectRpcData *)ptr;
307
6.07k
    SCFree(rd);
308
309
6.07k
    SCReturn;
310
6.07k
}
311
312
#ifdef UNITTESTS
313
#include "detect-engine-alert.h"
314
/**
315
 * \test DetectRpcTestParse01 is a test to make sure that we return "something"
316
 *  when given valid rpc opt
317
 */
318
static int DetectRpcTestParse01 (void)
319
{
320
    DetectRpcData *rd = DetectRpcParse(NULL, "123,444,555");
321
    FAIL_IF_NULL(rd);
322
323
    DetectRpcFree(NULL, rd);
324
    PASS;
325
}
326
327
/**
328
 * \test DetectRpcTestParse02 is a test for setting the established rpc opt
329
 */
330
static int DetectRpcTestParse02 (void)
331
{
332
    DetectRpcData *rd = NULL;
333
    rd = DetectRpcParse(NULL, "111,222,333");
334
    FAIL_IF_NULL(rd);
335
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROGRAM);
336
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_VERSION);
337
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROCEDURE);
338
    FAIL_IF_NOT(rd->program == 111);
339
    FAIL_IF_NOT(rd->program_version == 222);
340
    FAIL_IF_NOT(rd->procedure == 333);
341
342
    DetectRpcFree(NULL, rd);
343
344
    PASS;
345
}
346
347
/**
348
 * \test DetectRpcTestParse03 is a test for checking the wildcards
349
 * and not specified fields
350
 */
351
static int DetectRpcTestParse03 (void)
352
{
353
    DetectRpcData *rd = NULL;
354
355
    rd = DetectRpcParse(NULL, "111,*,333");
356
    FAIL_IF_NULL(rd);
357
358
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROGRAM);
359
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_VERSION);
360
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROCEDURE);
361
    FAIL_IF_NOT(rd->program == 111);
362
    FAIL_IF_NOT(rd->program_version == 0);
363
    FAIL_IF_NOT(rd->procedure == 333);
364
365
    DetectRpcFree(NULL, rd);
366
367
    rd = DetectRpcParse(NULL, "111,222,*");
368
    FAIL_IF_NULL(rd);
369
370
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROGRAM);
371
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_VERSION);
372
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_PROCEDURE);
373
    FAIL_IF_NOT(rd->program == 111);
374
    FAIL_IF_NOT(rd->program_version == 222);
375
    FAIL_IF_NOT(rd->procedure == 0);
376
377
    DetectRpcFree(NULL, rd);
378
379
    rd = DetectRpcParse(NULL, "111,*,*");
380
    FAIL_IF_NULL(rd);
381
382
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROGRAM);
383
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_VERSION);
384
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_PROCEDURE);
385
    FAIL_IF_NOT(rd->program == 111);
386
    FAIL_IF_NOT(rd->program_version == 0);
387
    FAIL_IF_NOT(rd->procedure == 0);
388
389
    DetectRpcFree(NULL, rd);
390
391
    rd = DetectRpcParse(NULL, "111,222");
392
    FAIL_IF_NULL(rd);
393
394
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROGRAM);
395
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_VERSION);
396
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_PROCEDURE);
397
    FAIL_IF_NOT(rd->program == 111);
398
    FAIL_IF_NOT(rd->program_version == 222);
399
    FAIL_IF_NOT(rd->procedure == 0);
400
401
    DetectRpcFree(NULL, rd);
402
403
    rd = DetectRpcParse(NULL, "111");
404
    FAIL_IF_NULL(rd);
405
406
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROGRAM);
407
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_VERSION);
408
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_PROCEDURE);
409
    FAIL_IF_NOT(rd->program == 111);
410
    FAIL_IF_NOT(rd->program_version == 0);
411
    FAIL_IF_NOT(rd->procedure == 0);
412
413
    DetectRpcFree(NULL, rd);
414
    PASS;
415
}
416
417
/**
418
 * \test DetectRpcTestParse04 is a test for check the discarding of empty options
419
 */
420
static int DetectRpcTestParse04 (void)
421
{
422
    DetectRpcData *rd = NULL;
423
    rd = DetectRpcParse(NULL, "");
424
425
    FAIL_IF_NOT_NULL(rd);
426
    DetectRpcFree(NULL, rd);
427
428
    PASS;
429
}
430
431
/**
432
 * \test DetectRpcTestParse05 is a test for check invalid values
433
 */
434
static int DetectRpcTestParse05 (void)
435
{
436
    DetectRpcData *rd = NULL;
437
    rd = DetectRpcParse(NULL, "111,aaa,*");
438
439
    FAIL_IF_NOT_NULL(rd);
440
    DetectRpcFree(NULL, rd);
441
442
    PASS;
443
}
444
445
/**
446
 * \test DetectRpcTestParse05 is a test to check the match function
447
 */
448
static int DetectRpcTestSig01(void)
449
{
450
    /* RPC Call */
451
    uint8_t buf[] = {
452
        /* XID */
453
        0x64,0xb2,0xb3,0x75,
454
        /* Message type: Call (0) */
455
        0x00,0x00,0x00,0x00,
456
        /* RPC Version (2) */
457
        0x00,0x00,0x00,0x02,
458
        /* Program portmap (100000) */
459
        0x00,0x01,0x86,0xa0,
460
        /* Program version (2) */
461
        0x00,0x00,0x00,0x02,
462
        /* Program procedure (3) = GETPORT */
463
        0x00,0x00,0x00,0x03,
464
        /* AUTH_NULL */
465
        0x00,0x00,0x00,0x00,
466
        /* Length 0 */
467
        0x00,0x00,0x00,0x00,
468
        /* VERIFIER NULL */
469
        0x00,0x00,0x00,0x00,
470
        /* Length 0 */
471
        0x00,0x00,0x00,0x00,
472
        /* Program portmap */
473
        0x00,0x01,0x86,0xa2,
474
        /* Version 2 */
475
        0x00,0x00,0x00,0x02,
476
        /* Proto UDP */
477
        0x00,0x00,0x00,0x11,
478
        /* Port 0 */
479
        0x00,0x00,0x00,0x00 };
480
    uint16_t buflen = sizeof(buf);
481
    Packet *p = NULL;
482
    Signature *s = NULL;
483
    ThreadVars th_v;
484
    DetectEngineThreadCtx *det_ctx;
485
486
    memset(&th_v, 0, sizeof(th_v));
487
488
    p = UTHBuildPacket(buf, buflen, IPPROTO_UDP);
489
490
    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
491
    FAIL_IF_NULL(de_ctx);
492
493
    de_ctx->flags |= DE_QUIET;
494
495
    s = DetectEngineAppendSig(de_ctx,
496
            "alert udp any any -> any any (msg:\"RPC Get Port Call\"; rpc:100000, 2, 3; sid:1;)");
497
    FAIL_IF_NULL(s);
498
499
    s = DetectEngineAppendSig(de_ctx,
500
            "alert udp any any -> any any (msg:\"RPC Get Port Call\"; rpc:100000, 2, *; sid:2;)");
501
    FAIL_IF_NULL(s);
502
503
    s = DetectEngineAppendSig(de_ctx,
504
            "alert udp any any -> any any (msg:\"RPC Get Port Call\"; rpc:100000, *, 3; sid:3;)");
505
    FAIL_IF_NULL(s);
506
507
    s = DetectEngineAppendSig(de_ctx,
508
            "alert udp any any -> any any (msg:\"RPC Get Port Call\"; rpc:100000, *, *; sid:4;)");
509
    FAIL_IF_NULL(s);
510
511
    s = DetectEngineAppendSig(de_ctx, "alert udp any any -> any any (msg:\"RPC Get XXX Call.. no "
512
                                      "match\"; rpc:123456, *, 3; sid:5;)");
513
    FAIL_IF_NULL(s);
514
515
    SigGroupBuild(de_ctx);
516
    DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
517
518
    SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
519
    FAIL_IF(PacketAlertCheck(p, 1) == 0);
520
    FAIL_IF(PacketAlertCheck(p, 2) == 0);
521
    FAIL_IF(PacketAlertCheck(p, 3) == 0);
522
    FAIL_IF(PacketAlertCheck(p, 4) == 0);
523
    FAIL_IF(PacketAlertCheck(p, 5) > 0);
524
525
    DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
526
    DetectEngineCtxFree(de_ctx);
527
528
    UTHFreePackets(&p, 1);
529
530
    PASS;
531
}
532
533
/**
534
 * \brief this function registers unit tests for DetectRpc
535
 */
536
static void DetectRpcRegisterTests(void)
537
{
538
    UtRegisterTest("DetectRpcTestParse01", DetectRpcTestParse01);
539
    UtRegisterTest("DetectRpcTestParse02", DetectRpcTestParse02);
540
    UtRegisterTest("DetectRpcTestParse03", DetectRpcTestParse03);
541
    UtRegisterTest("DetectRpcTestParse04", DetectRpcTestParse04);
542
    UtRegisterTest("DetectRpcTestParse05", DetectRpcTestParse05);
543
    UtRegisterTest("DetectRpcTestSig01", DetectRpcTestSig01);
544
}
545
#endif /* UNITTESTS */