Coverage Report

Created: 2026-08-14 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/detect-cipservice.c
Line
Count
Source
1
/* Copyright (C) 2015 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 Kevin Wong <kwong@solananetworks.com>
22
 *
23
 * Set up ENIP Command and CIP Service rule parsing and entry point for matching
24
 */
25
26
#include "suricata-common.h"
27
#include "util-unittest.h"
28
#include "detect-parse.h"
29
#include "detect-engine.h"
30
#include "util-byte.h"
31
32
#include "app-layer-enip-common.h"
33
#include "detect-cipservice.h"
34
#include "detect-engine-enip.h"
35
36
/*
37
 * CIP SERVICE CODE
38
 */
39
40
/**
41
 * \brief CIP Service Detect Prototypes
42
 */
43
static int DetectCipServiceSetup(DetectEngineCtx *, Signature *, const char *);
44
static void DetectCipServiceFree(DetectEngineCtx *, void *);
45
#ifdef UNITTESTS
46
static void DetectCipServiceRegisterTests(void);
47
#endif
48
static int g_cip_buffer_id = 0;
49
50
/**
51
 * \brief Registration function for cip_service: keyword
52
 */
53
void DetectCipServiceRegister(void)
54
34
{
55
34
    SCEnter();
56
34
    sigmatch_table[DETECT_CIPSERVICE].name = "cip_service"; //rule keyword
57
34
    sigmatch_table[DETECT_CIPSERVICE].desc = "match on CIP Service";
58
34
    sigmatch_table[DETECT_CIPSERVICE].url = "/rules/enip-keyword.html#enip-cip-keywords";
59
34
    sigmatch_table[DETECT_CIPSERVICE].Match = NULL;
60
34
    sigmatch_table[DETECT_CIPSERVICE].Setup = DetectCipServiceSetup;
61
34
    sigmatch_table[DETECT_CIPSERVICE].Free = DetectCipServiceFree;
62
#ifdef UNITTESTS
63
    sigmatch_table[DETECT_CIPSERVICE].RegisterTests
64
            = DetectCipServiceRegisterTests;
65
#endif
66
34
    DetectAppLayerInspectEngineRegister2(
67
34
            "cip", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, DetectEngineInspectCIP, NULL);
68
34
    DetectAppLayerInspectEngineRegister2(
69
34
            "cip", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectCIP, NULL);
70
71
34
    g_cip_buffer_id = DetectBufferTypeGetByName("cip");
72
73
34
    SCReturn;
74
34
}
75
76
/**
77
 * \brief This function is used to parse cip_service options passed via cip_service: keyword
78
 *
79
 * \param rulestr Pointer to the user provided rulestr options
80
 * Takes comma seperated string with numeric tokens.  Only first 3 are used
81
 *
82
 * \retval cipserviced pointer to DetectCipServiceData on success
83
 * \retval NULL on failure
84
 */
85
static DetectCipServiceData *DetectCipServiceParse(const char *rulestrc)
86
7.08k
{
87
7.08k
    const char delims[] = ",";
88
7.08k
    DetectCipServiceData *cipserviced = NULL;
89
90
    //SCLogDebug("DetectCipServiceParse - rule string  %s", rulestr);
91
92
    /* strtok_r modifies the string so work with a copy */
93
7.08k
    char *rulestr = SCStrdup(rulestrc);
94
7.08k
    if (unlikely(rulestr == NULL))
95
0
        goto error;
96
97
7.08k
    cipserviced = SCMalloc(sizeof(DetectCipServiceData));
98
7.08k
    if (unlikely(cipserviced == NULL))
99
0
        goto error;
100
101
7.08k
    cipserviced->cipservice = 0;
102
7.08k
    cipserviced->cipclass = 0;
103
7.08k
    cipserviced->matchattribute = 1;
104
7.08k
    cipserviced->cipattribute = 0;
105
106
7.08k
    char* token;
107
7.08k
    char *save;
108
7.08k
    uint8_t var;
109
7.08k
    uint8_t input[3] = { 0, 0, 0 };
110
7.08k
    uint8_t i = 0;
111
112
7.08k
    token = strtok_r(rulestr, delims, &save);
113
13.5k
    while (token != NULL)
114
7.22k
    {
115
7.22k
        if (i > 2) //for now only need 3 parameters
116
0
        {
117
0
            SCLogError("too many parameters");
118
0
            goto error;
119
0
        }
120
121
7.22k
        if (i < 2) //if on service or class
122
7.15k
        {
123
7.15k
            if (!isdigit((int) *token))
124
597
            {
125
597
                SCLogError("parameter error %s", token);
126
597
                goto error;
127
597
            }
128
7.15k
        } else //if on attribute
129
67
        {
130
131
67
            if (token[0] == '!')
132
62
            {
133
62
                cipserviced->matchattribute = 0;
134
62
                token++;
135
62
            }
136
137
67
            if (!isdigit((int) *token))
138
62
            {
139
62
                SCLogError("attribute error  %s", token);
140
62
                goto error;
141
62
            }
142
143
67
        }
144
145
6.56k
        unsigned long num = atol(token);
146
6.56k
        if ((num > MAX_CIP_SERVICE) && (i == 0))//if service greater than 7 bit
147
128
        {
148
128
            SCLogError("invalid CIP service %lu", num);
149
128
            goto error;
150
6.43k
        } else if ((num > MAX_CIP_CLASS) && (i == 1))//if service greater than 16 bit
151
0
        {
152
0
            SCLogError("invalid CIP class %lu", num);
153
0
            goto error;
154
6.43k
        } else if ((num > MAX_CIP_ATTRIBUTE) && (i == 2))//if service greater than 16 bit
155
0
        {
156
0
            SCLogError("invalid CIP attribute %lu", num);
157
0
            goto error;
158
0
        }
159
160
6.43k
        sscanf(token, "%2" SCNu8, &var);
161
6.43k
        input[i++] = var;
162
163
6.43k
        token = strtok_r(NULL, delims, &save);
164
6.43k
    }
165
166
6.29k
    if (i == 0) {
167
0
        SCLogError("no tokens found");
168
0
        goto error;
169
0
    }
170
171
6.29k
    cipserviced->cipservice = input[0];
172
6.29k
    cipserviced->cipclass = input[1];
173
6.29k
    cipserviced->cipattribute = input[2];
174
6.29k
    cipserviced->tokens = i;
175
176
6.29k
    SCLogDebug("DetectCipServiceParse - tokens %d", cipserviced->tokens);
177
6.29k
    SCLogDebug("DetectCipServiceParse - service %d", cipserviced->cipservice);
178
6.29k
    SCLogDebug("DetectCipServiceParse - class %d", cipserviced->cipclass);
179
6.29k
    SCLogDebug("DetectCipServiceParse - match attribute %d",
180
6.29k
            cipserviced->matchattribute);
181
6.29k
    SCLogDebug("DetectCipServiceParse - attribute %d",
182
6.29k
            cipserviced->cipattribute);
183
184
6.29k
    SCFree(rulestr);
185
6.29k
    SCReturnPtr(cipserviced, "DetectENIPFunction");
186
187
787
error:
188
787
    if (cipserviced)
189
787
        SCFree(cipserviced);
190
787
    if (rulestr)
191
787
        SCFree(rulestr);
192
787
    SCReturnPtr(NULL, "DetectENIP");
193
6.29k
}
194
195
/**
196
 * \brief this function is used to a cipserviced the parsed cip_service data into the current signature
197
 *
198
 * \param de_ctx pointer to the Detection Engine Context
199
 * \param s pointer to the Current Signature
200
 * \param rulestr pointer to the user provided cip_service options
201
 *
202
 * \retval 0 on Success
203
 * \retval -1 on Failure
204
 */
205
static int DetectCipServiceSetup(DetectEngineCtx *de_ctx, Signature *s,
206
        const char *rulestr)
207
7.20k
{
208
7.20k
    SCEnter();
209
210
7.20k
    DetectCipServiceData *cipserviced = NULL;
211
7.20k
    SigMatch *sm = NULL;
212
213
7.20k
    if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0)
214
120
        return -1;
215
216
7.08k
    cipserviced = DetectCipServiceParse(rulestr);
217
7.08k
    if (cipserviced == NULL)
218
787
        goto error;
219
220
6.29k
    sm = SigMatchAlloc();
221
6.29k
    if (sm == NULL)
222
0
        goto error;
223
224
6.29k
    sm->type = DETECT_CIPSERVICE;
225
6.29k
    sm->ctx = (void *) cipserviced;
226
227
6.29k
    SigMatchAppendSMToList(s, sm, g_cip_buffer_id);
228
6.29k
    SCReturnInt(0);
229
230
787
error:
231
787
    if (cipserviced != NULL)
232
0
        DetectCipServiceFree(de_ctx, cipserviced);
233
787
    if (sm != NULL)
234
0
        SCFree(sm);
235
787
    SCReturnInt(-1);
236
6.29k
}
237
238
/**
239
 * \brief this function will free memory associated with DetectCipServiceData
240
 *
241
 * \param ptr pointer to DetectCipServiceData
242
 */
243
static void DetectCipServiceFree(DetectEngineCtx *de_ctx, void *ptr)
244
6.29k
{
245
6.29k
    DetectCipServiceData *cipserviced = (DetectCipServiceData *) ptr;
246
6.29k
    SCFree(cipserviced);
247
6.29k
}
248
249
#ifdef UNITTESTS
250
251
/**
252
 * \test Test CIP Command parameter parsing
253
 */
254
static int DetectCipServiceParseTest01 (void)
255
{
256
    DetectCipServiceData *cipserviced = NULL;
257
    cipserviced = DetectCipServiceParse("7");
258
    FAIL_IF_NULL(cipserviced);
259
    FAIL_IF(cipserviced->cipservice != 7);
260
    DetectCipServiceFree(NULL, cipserviced);
261
    PASS;
262
}
263
264
/**
265
 * \test Test CIP Service signature
266
 */
267
static int DetectCipServiceSignatureTest01 (void)
268
{
269
    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
270
    FAIL_IF_NULL(de_ctx);
271
    Signature *sig = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any (cip_service:1; sid:1; rev:1;)");
272
    FAIL_IF_NULL(sig);
273
    DetectEngineCtxFree(de_ctx);
274
    PASS;
275
}
276
277
/**
278
 * \brief this function registers unit tests for DetectCipService
279
 */
280
static void DetectCipServiceRegisterTests(void)
281
{
282
    UtRegisterTest("DetectCipServiceParseTest01",
283
            DetectCipServiceParseTest01);
284
    UtRegisterTest("DetectCipServiceSignatureTest01",
285
            DetectCipServiceSignatureTest01);
286
}
287
#endif /* UNITTESTS */
288
289
/*
290
 * ENIP COMMAND CODE
291
 */
292
293
/**
294
 * \brief ENIP Command Detect Prototypes
295
 */
296
static int DetectEnipCommandSetup(DetectEngineCtx *, Signature *, const char *);
297
static void DetectEnipCommandFree(DetectEngineCtx *, void *);
298
#ifdef UNITTESTS
299
static void DetectEnipCommandRegisterTests(void);
300
#endif
301
static int g_enip_buffer_id = 0;
302
303
/**
304
 * \brief Registration function for enip_command: keyword
305
 */
306
void DetectEnipCommandRegister(void)
307
34
{
308
34
    sigmatch_table[DETECT_ENIPCOMMAND].name = "enip_command"; //rule keyword
309
34
    sigmatch_table[DETECT_ENIPCOMMAND].desc
310
34
            = "rules for detecting EtherNet/IP command";
311
34
    sigmatch_table[DETECT_ENIPCOMMAND].url = "/rules/enip-keyword.html#enip-cip-keywords";
312
34
    sigmatch_table[DETECT_ENIPCOMMAND].Match = NULL;
313
34
    sigmatch_table[DETECT_ENIPCOMMAND].Setup = DetectEnipCommandSetup;
314
34
    sigmatch_table[DETECT_ENIPCOMMAND].Free = DetectEnipCommandFree;
315
#ifdef UNITTESTS
316
    sigmatch_table[DETECT_ENIPCOMMAND].RegisterTests
317
            = DetectEnipCommandRegisterTests;
318
#endif
319
34
    DetectAppLayerInspectEngineRegister2(
320
34
            "enip", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, DetectEngineInspectENIP, NULL);
321
34
    DetectAppLayerInspectEngineRegister2(
322
34
            "enip", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectENIP, NULL);
323
324
34
    g_enip_buffer_id = DetectBufferTypeGetByName("enip");
325
34
}
326
327
/**
328
 * \brief This function is used to parse cip_service options passed via enip_command: keyword
329
 *
330
 * \param rulestr Pointer to the user provided rulestr options
331
 * Takes single numeric value
332
 *
333
 * \retval enipcmdd pointer to DetectCipServiceData on success
334
 * \retval NULL on failure
335
 */
336
static DetectEnipCommandData *DetectEnipCommandParse(const char *rulestr)
337
1.27k
{
338
1.27k
    DetectEnipCommandData *enipcmdd = NULL;
339
340
1.27k
    enipcmdd = SCMalloc(sizeof(DetectEnipCommandData));
341
1.27k
    if (unlikely(enipcmdd == NULL))
342
0
        goto error;
343
344
1.27k
    if (!(isdigit((int) *rulestr))) {
345
228
        SCLogError("invalid ENIP command %s", rulestr);
346
228
        goto error;
347
228
    }
348
349
1.04k
    uint16_t cmd;
350
1.04k
    if (StringParseUint16(&cmd, 10, 0, rulestr) <= 0) {
351
394
        SCLogError("invalid ENIP command"
352
394
                   ": \"%s\"",
353
394
                rulestr);
354
394
        goto error;
355
394
    }
356
357
653
    enipcmdd->enipcommand = cmd;
358
359
653
    return enipcmdd;
360
361
622
error:
362
622
    if (enipcmdd)
363
622
        SCFree(enipcmdd);
364
622
    return NULL;
365
1.04k
}
366
367
/**
368
 * \brief this function is used by enipcmdd to parse enip_command data into the current signature
369
 *
370
 * \param de_ctx pointer to the Detection Engine Context
371
 * \param s pointer to the Current Signature
372
 * \param rulestr pointer to the user provided enip command options
373
 *
374
 * \retval 0 on Success
375
 * \retval -1 on Failure
376
 */
377
static int DetectEnipCommandSetup(DetectEngineCtx *de_ctx, Signature *s,
378
        const char *rulestr)
379
1.33k
{
380
1.33k
    DetectEnipCommandData *enipcmdd = NULL;
381
1.33k
    SigMatch *sm = NULL;
382
383
1.33k
    if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0)
384
64
        return -1;
385
386
1.27k
    enipcmdd = DetectEnipCommandParse(rulestr);
387
1.27k
    if (enipcmdd == NULL)
388
622
        goto error;
389
390
653
    sm = SigMatchAlloc();
391
653
    if (sm == NULL)
392
0
        goto error;
393
394
653
    sm->type = DETECT_ENIPCOMMAND;
395
653
    sm->ctx = (void *) enipcmdd;
396
397
653
    SigMatchAppendSMToList(s, sm, g_enip_buffer_id);
398
653
    SCReturnInt(0);
399
400
622
error:
401
622
    if (enipcmdd != NULL)
402
0
        DetectEnipCommandFree(de_ctx, enipcmdd);
403
622
    if (sm != NULL)
404
0
        SCFree(sm);
405
622
    SCReturnInt(-1);
406
653
}
407
408
/**
409
 * \brief this function will free memory associated with DetectEnipCommandData
410
 *
411
 * \param ptr pointer to DetectEnipCommandData
412
 */
413
static void DetectEnipCommandFree(DetectEngineCtx *de_ctx, void *ptr)
414
653
{
415
653
    DetectEnipCommandData *enipcmdd = (DetectEnipCommandData *) ptr;
416
653
    SCFree(enipcmdd);
417
653
}
418
419
#ifdef UNITTESTS
420
421
/**
422
 * \test ENIP parameter test
423
 */
424
425
static int DetectEnipCommandParseTest01 (void)
426
{
427
    DetectEnipCommandData *enipcmdd = NULL;
428
429
    enipcmdd = DetectEnipCommandParse("1");
430
    FAIL_IF_NULL(enipcmdd);
431
    FAIL_IF_NOT(enipcmdd->enipcommand == 1);
432
433
    DetectEnipCommandFree(NULL, enipcmdd);
434
    PASS;
435
}
436
437
/**
438
 * \test ENIP Command signature test
439
 */
440
static int DetectEnipCommandSignatureTest01 (void)
441
{
442
    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
443
    FAIL_IF_NULL(de_ctx);
444
445
    Signature *sig = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any (enip_command:1; sid:1; rev:1;)");
446
    FAIL_IF_NULL(sig);
447
448
    DetectEngineCtxFree(de_ctx);
449
    PASS;
450
}
451
452
/**
453
 * \brief this function registers unit tests for DetectEnipCommand
454
 */
455
static void DetectEnipCommandRegisterTests(void)
456
{
457
    UtRegisterTest("DetectEnipCommandParseTest01",
458
            DetectEnipCommandParseTest01);
459
    UtRegisterTest("DetectEnipCommandSignatureTest01",
460
            DetectEnipCommandSignatureTest01);
461
}
462
#endif /* UNITTESTS */