Coverage Report

Created: 2026-05-16 07:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata/src/detect-rpc.c
Line
Count
Source
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
{
95
    /* PrintRawDataFp(stdout, p->payload, p->payload_len); */
96
    const DetectRpcData *rd = (const DetectRpcData *)ctx;
97
    char *rpcmsg = (char *)p->payload;
98
99
    if (PacketIsTCP(p)) {
100
        /* if Rpc msg too small */
101
        if (p->payload_len < 28) {
102
            SCLogDebug("TCP packet to small for the rpc msg (%u)", p->payload_len);
103
            return 0;
104
        }
105
        rpcmsg += 4;
106
    } else if (PacketIsUDP(p)) {
107
        /* if Rpc msg too small */
108
        if (p->payload_len < 24) {
109
            SCLogDebug("UDP packet to small for the rpc msg (%u)", p->payload_len);
110
            return 0;
111
        }
112
    } else {
113
        SCLogDebug("No valid proto for the rpc message");
114
        return 0;
115
    }
116
117
    /* Point through the rpc msg structure. Use SCNtohl() to compare values */
118
    RpcMsg *msg = (RpcMsg *)rpcmsg;
119
120
    /* If its not a call, no match */
121
    if (SCNtohl(msg->type) != 0) {
122
        SCLogDebug("RPC message type is not a call");
123
        return 0;
124
    }
125
126
    if (SCNtohl(msg->prog) != rd->program)
127
        return 0;
128
129
    if ((rd->flags & DETECT_RPC_CHECK_VERSION) && SCNtohl(msg->vers) != rd->program_version)
130
        return 0;
131
132
    if ((rd->flags & DETECT_RPC_CHECK_PROCEDURE) && SCNtohl(msg->proc) != rd->procedure)
133
        return 0;
134
135
    SCLogDebug("prog:%u pver:%u proc:%u matched", SCNtohl(msg->prog), SCNtohl(msg->vers), SCNtohl(msg->proc));
136
    return 1;
137
}
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
4.36k
{
150
4.36k
    DetectRpcData *rd = NULL;
151
4.36k
    char *args[3] = {NULL,NULL,NULL};
152
4.36k
    int res = 0;
153
4.36k
    size_t pcre2_len;
154
155
4.36k
    pcre2_match_data *match = NULL;
156
4.36k
    int ret = DetectParsePcreExec(&parse_regex, &match, rpcstr, 0, 0);
157
4.36k
    if (ret < 1 || ret > 4) {
158
258
        SCLogError("parse error, ret %" PRId32 ", string %s", ret, rpcstr);
159
258
        goto error;
160
258
    }
161
162
4.11k
    if (ret > 1) {
163
4.11k
        const char *str_ptr;
164
4.11k
        res = pcre2_substring_get_bynumber(match, 1, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len);
165
4.11k
        if (res < 0) {
166
0
            SCLogError("pcre2_substring_get_bynumber failed");
167
0
            goto error;
168
0
        }
169
4.11k
        args[0] = (char *)str_ptr;
170
171
4.11k
        if (ret > 2) {
172
2.91k
            res = pcre2_substring_get_bynumber(match, 2, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len);
173
2.91k
            if (res < 0) {
174
0
                SCLogError("pcre2_substring_get_bynumber failed");
175
0
                goto error;
176
0
            }
177
2.91k
            args[1] = (char *)str_ptr;
178
2.91k
        }
179
4.11k
        if (ret > 3) {
180
2.22k
            res = pcre2_substring_get_bynumber(match, 3, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len);
181
2.22k
            if (res < 0) {
182
0
                SCLogError("pcre2_substring_get_bynumber failed");
183
0
                goto error;
184
0
            }
185
2.22k
            args[2] = (char *)str_ptr;
186
2.22k
        }
187
4.11k
    }
188
189
4.11k
    rd = SCMalloc(sizeof(DetectRpcData));
190
4.11k
    if (unlikely(rd == NULL))
191
0
        goto error;
192
4.11k
    rd->flags = 0;
193
4.11k
    rd->program = 0;
194
4.11k
    rd->program_version = 0;
195
4.11k
    rd->procedure = 0;
196
197
4.11k
    int i;
198
13.2k
    for (i = 0; i < (ret - 1); i++) {
199
9.24k
        if (args[i]) {
200
9.24k
            switch (i) {
201
4.11k
                case 0:
202
4.11k
                    if (StringParseUint32(&rd->program, 10, strlen(args[i]), args[i]) <= 0) {
203
7
                        SCLogError("Invalid size specified for the rpc program:\"%s\"", args[i]);
204
7
                        goto error;
205
7
                    }
206
4.10k
                    rd->flags |= DETECT_RPC_CHECK_PROGRAM;
207
4.10k
                    break;
208
2.90k
                case 1:
209
2.90k
                    if (args[i][0] != '*') {
210
1.60k
                        if (StringParseUint32(&rd->program_version, 10, strlen(args[i]), args[i]) <= 0) {
211
1
                            SCLogError(
212
1
                                    "Invalid size specified for the rpc version:\"%s\"", args[i]);
213
1
                            goto error;
214
1
                        }
215
1.60k
                        rd->flags |= DETECT_RPC_CHECK_VERSION;
216
1.60k
                    }
217
2.90k
                    break;
218
2.90k
                case 2:
219
2.22k
                    if (args[i][0] != '*') {
220
638
                        if (StringParseUint32(&rd->procedure, 10, strlen(args[i]), args[i]) <= 0) {
221
104
                            SCLogError(
222
104
                                    "Invalid size specified for the rpc procedure:\"%s\"", args[i]);
223
104
                            goto error;
224
104
                        }
225
534
                        rd->flags |= DETECT_RPC_CHECK_PROCEDURE;
226
534
                    }
227
2.12k
                break;
228
9.24k
            }
229
9.24k
        } else {
230
0
            SCLogError("invalid rpc option %s", rpcstr);
231
0
            goto error;
232
0
        }
233
9.24k
    }
234
12.9k
    for (i = 0; i < (ret -1); i++){
235
8.92k
        if (args[i] != NULL)
236
8.92k
            pcre2_substring_free((PCRE2_UCHAR8 *)args[i]);
237
8.92k
    }
238
3.99k
    pcre2_match_data_free(match);
239
3.99k
    return rd;
240
241
370
error:
242
370
    if (match) {
243
370
        pcre2_match_data_free(match);
244
370
    }
245
695
    for (i = 0; i < (ret -1) && i < 3; i++){
246
325
        if (args[i] != NULL)
247
325
            pcre2_substring_free((PCRE2_UCHAR8 *)args[i]);
248
325
    }
249
370
    if (rd != NULL)
250
112
        DetectRpcFree(de_ctx, rd);
251
370
    return NULL;
252
253
4.11k
}
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
1.57k
{
268
1.57k
    DetectRpcData *rd = NULL;
269
270
1.57k
    rd = DetectRpcParse(de_ctx, rpcstr);
271
1.57k
    if (rd == NULL) goto error;
272
273
1.46k
    if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_RPC, (SigMatchCtx *)rd, DETECT_SM_LIST_MATCH) ==
274
1.46k
            NULL) {
275
0
        goto error;
276
0
    }
277
1.46k
    s->flags |= SIG_FLAG_REQUIRE_PACKET;
278
279
1.46k
    return 0;
280
281
112
error:
282
112
    if (rd != NULL)
283
0
        DetectRpcFree(de_ctx, rd);
284
112
    return -1;
285
286
1.46k
}
287
288
/**
289
 * \brief this function will free memory associated with DetectRpcData
290
 *
291
 * \param rd pointer to DetectRpcData
292
 */
293
void DetectRpcFree(DetectEngineCtx *de_ctx, void *ptr)
294
4.11k
{
295
4.11k
    SCEnter();
296
297
4.11k
    if (ptr == NULL) {
298
0
        SCReturn;
299
0
    }
300
301
4.11k
    DetectRpcData *rd = (DetectRpcData *)ptr;
302
4.11k
    SCFree(rd);
303
304
4.11k
    SCReturn;
305
4.11k
}
306
307
#ifdef UNITTESTS
308
#include "detect-engine-alert.h"
309
/**
310
 * \test DetectRpcTestParse01 is a test to make sure that we return "something"
311
 *  when given valid rpc opt
312
 */
313
static int DetectRpcTestParse01 (void)
314
{
315
    DetectRpcData *rd = DetectRpcParse(NULL, "123,444,555");
316
    FAIL_IF_NULL(rd);
317
318
    DetectRpcFree(NULL, rd);
319
    PASS;
320
}
321
322
/**
323
 * \test DetectRpcTestParse02 is a test for setting the established rpc opt
324
 */
325
static int DetectRpcTestParse02 (void)
326
{
327
    DetectRpcData *rd = NULL;
328
    rd = DetectRpcParse(NULL, "111,222,333");
329
    FAIL_IF_NULL(rd);
330
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROGRAM);
331
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_VERSION);
332
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROCEDURE);
333
    FAIL_IF_NOT(rd->program == 111);
334
    FAIL_IF_NOT(rd->program_version == 222);
335
    FAIL_IF_NOT(rd->procedure == 333);
336
337
    DetectRpcFree(NULL, rd);
338
339
    PASS;
340
}
341
342
/**
343
 * \test DetectRpcTestParse03 is a test for checking the wildcards
344
 * and not specified fields
345
 */
346
static int DetectRpcTestParse03 (void)
347
{
348
    DetectRpcData *rd = NULL;
349
350
    rd = DetectRpcParse(NULL, "111,*,333");
351
    FAIL_IF_NULL(rd);
352
353
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROGRAM);
354
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_VERSION);
355
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROCEDURE);
356
    FAIL_IF_NOT(rd->program == 111);
357
    FAIL_IF_NOT(rd->program_version == 0);
358
    FAIL_IF_NOT(rd->procedure == 333);
359
360
    DetectRpcFree(NULL, rd);
361
362
    rd = DetectRpcParse(NULL, "111,222,*");
363
    FAIL_IF_NULL(rd);
364
365
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROGRAM);
366
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_VERSION);
367
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_PROCEDURE);
368
    FAIL_IF_NOT(rd->program == 111);
369
    FAIL_IF_NOT(rd->program_version == 222);
370
    FAIL_IF_NOT(rd->procedure == 0);
371
372
    DetectRpcFree(NULL, rd);
373
374
    rd = DetectRpcParse(NULL, "111,*,*");
375
    FAIL_IF_NULL(rd);
376
377
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROGRAM);
378
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_VERSION);
379
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_PROCEDURE);
380
    FAIL_IF_NOT(rd->program == 111);
381
    FAIL_IF_NOT(rd->program_version == 0);
382
    FAIL_IF_NOT(rd->procedure == 0);
383
384
    DetectRpcFree(NULL, rd);
385
386
    rd = DetectRpcParse(NULL, "111,222");
387
    FAIL_IF_NULL(rd);
388
389
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROGRAM);
390
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_VERSION);
391
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_PROCEDURE);
392
    FAIL_IF_NOT(rd->program == 111);
393
    FAIL_IF_NOT(rd->program_version == 222);
394
    FAIL_IF_NOT(rd->procedure == 0);
395
396
    DetectRpcFree(NULL, rd);
397
398
    rd = DetectRpcParse(NULL, "111");
399
    FAIL_IF_NULL(rd);
400
401
    FAIL_IF_NOT(rd->flags & DETECT_RPC_CHECK_PROGRAM);
402
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_VERSION);
403
    FAIL_IF(rd->flags & DETECT_RPC_CHECK_PROCEDURE);
404
    FAIL_IF_NOT(rd->program == 111);
405
    FAIL_IF_NOT(rd->program_version == 0);
406
    FAIL_IF_NOT(rd->procedure == 0);
407
408
    DetectRpcFree(NULL, rd);
409
    PASS;
410
}
411
412
/**
413
 * \test DetectRpcTestParse04 is a test for check the discarding of empty options
414
 */
415
static int DetectRpcTestParse04 (void)
416
{
417
    DetectRpcData *rd = NULL;
418
    rd = DetectRpcParse(NULL, "");
419
420
    FAIL_IF_NOT_NULL(rd);
421
    DetectRpcFree(NULL, rd);
422
423
    PASS;
424
}
425
426
/**
427
 * \test DetectRpcTestParse05 is a test for check invalid values
428
 */
429
static int DetectRpcTestParse05 (void)
430
{
431
    DetectRpcData *rd = NULL;
432
    rd = DetectRpcParse(NULL, "111,aaa,*");
433
434
    FAIL_IF_NOT_NULL(rd);
435
    DetectRpcFree(NULL, rd);
436
437
    PASS;
438
}
439
440
/**
441
 * \test DetectRpcTestParse05 is a test to check the match function
442
 */
443
static int DetectRpcTestSig01(void)
444
{
445
    /* RPC Call */
446
    uint8_t buf[] = {
447
        /* XID */
448
        0x64,0xb2,0xb3,0x75,
449
        /* Message type: Call (0) */
450
        0x00,0x00,0x00,0x00,
451
        /* RPC Version (2) */
452
        0x00,0x00,0x00,0x02,
453
        /* Program portmap (100000) */
454
        0x00,0x01,0x86,0xa0,
455
        /* Program version (2) */
456
        0x00,0x00,0x00,0x02,
457
        /* Program procedure (3) = GETPORT */
458
        0x00,0x00,0x00,0x03,
459
        /* AUTH_NULL */
460
        0x00,0x00,0x00,0x00,
461
        /* Length 0 */
462
        0x00,0x00,0x00,0x00,
463
        /* VERIFIER NULL */
464
        0x00,0x00,0x00,0x00,
465
        /* Length 0 */
466
        0x00,0x00,0x00,0x00,
467
        /* Program portmap */
468
        0x00,0x01,0x86,0xa2,
469
        /* Version 2 */
470
        0x00,0x00,0x00,0x02,
471
        /* Proto UDP */
472
        0x00,0x00,0x00,0x11,
473
        /* Port 0 */
474
        0x00,0x00,0x00,0x00 };
475
    uint16_t buflen = sizeof(buf);
476
    Packet *p = NULL;
477
    Signature *s = NULL;
478
    ThreadVars th_v;
479
    DetectEngineThreadCtx *det_ctx;
480
481
    memset(&th_v, 0, sizeof(th_v));
482
    StatsThreadInit(&th_v.stats);
483
484
    p = UTHBuildPacket(buf, buflen, IPPROTO_UDP);
485
486
    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
487
    FAIL_IF_NULL(de_ctx);
488
489
    de_ctx->flags |= DE_QUIET;
490
491
    s = DetectEngineAppendSig(de_ctx,
492
            "alert udp any any -> any any (msg:\"RPC Get Port Call\"; rpc:100000, 2, 3; sid:1;)");
493
    FAIL_IF_NULL(s);
494
495
    s = DetectEngineAppendSig(de_ctx,
496
            "alert udp any any -> any any (msg:\"RPC Get Port Call\"; rpc:100000, 2, *; sid:2;)");
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, *, 3; sid:3;)");
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, *, *; sid:4;)");
505
    FAIL_IF_NULL(s);
506
507
    s = DetectEngineAppendSig(de_ctx, "alert udp any any -> any any (msg:\"RPC Get XXX Call.. no "
508
                                      "match\"; rpc:123456, *, 3; sid:5;)");
509
    FAIL_IF_NULL(s);
510
511
    SigGroupBuild(de_ctx);
512
    DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
513
514
    SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
515
    FAIL_IF(PacketAlertCheck(p, 1) == 0);
516
    FAIL_IF(PacketAlertCheck(p, 2) == 0);
517
    FAIL_IF(PacketAlertCheck(p, 3) == 0);
518
    FAIL_IF(PacketAlertCheck(p, 4) == 0);
519
    FAIL_IF(PacketAlertCheck(p, 5) > 0);
520
521
    UTHFreePackets(&p, 1);
522
523
    DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
524
    DetectEngineCtxFree(de_ctx);
525
    StatsThreadCleanup(&th_v.stats);
526
    PASS;
527
}
528
529
/**
530
 * \brief this function registers unit tests for DetectRpc
531
 */
532
static void DetectRpcRegisterTests(void)
533
{
534
    UtRegisterTest("DetectRpcTestParse01", DetectRpcTestParse01);
535
    UtRegisterTest("DetectRpcTestParse02", DetectRpcTestParse02);
536
    UtRegisterTest("DetectRpcTestParse03", DetectRpcTestParse03);
537
    UtRegisterTest("DetectRpcTestParse04", DetectRpcTestParse04);
538
    UtRegisterTest("DetectRpcTestParse05", DetectRpcTestParse05);
539
    UtRegisterTest("DetectRpcTestSig01", DetectRpcTestSig01);
540
}
541
#endif /* UNITTESTS */