Coverage Report

Created: 2025-12-31 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/detect-engine-enip.c
Line
Count
Source
1
/* Copyright (C) 2015-2022 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
/** \file
19
 *
20
 *  \author Kevin Wong <kwong@solananetworks.com>
21
 *
22
 *  Based on detect-engine-modbus.c
23
 */
24
25
#include "suricata-common.h"
26
27
#include "app-layer.h"
28
#include "app-layer-enip-common.h"
29
30
#include "detect.h"
31
#include "detect-cipservice.h"
32
#include "detect-engine-enip.h"
33
34
#include "flow.h"
35
36
#include "util-debug.h"
37
38
#if 0
39
/**
40
 * \brief Print fields from ENIP Packet
41
 * @param enip_data
42
 */
43
void PrintENIPAL(ENIPTransaction *enip_data)
44
{
45
    SCLogDebug("============================================");
46
    SCLogDebug("ENCAP HEADER cmd 0x%x, length %d, session 0x%x, status 0x%x",
47
            enip_data->header.command, enip_data->header.length,
48
            enip_data->header.session, enip_data->header.status);
49
    //SCLogDebug("context 0x%x option 0x%x", enip_data->header.context, enip_data->header.option);
50
    SCLogDebug("ENCAP DATA HEADER handle 0x%x, timeout %d, count %d",
51
            enip_data->encap_data_header.interface_handle,
52
            enip_data->encap_data_header.timeout,
53
            enip_data->encap_data_header.item_count);
54
    SCLogDebug("ENCAP ADDR ITEM type 0x%x, length %d",
55
            enip_data->encap_addr_item.type, enip_data->encap_addr_item.length);
56
    SCLogDebug("ENCAP DATA ITEM type 0x%x, length %d sequence 0x%x",
57
            enip_data->encap_data_item.type, enip_data->encap_data_item.length,
58
            enip_data->encap_data_item.sequence_count);
59
60
    CIPServiceEntry *svc = NULL;
61
62
    int count = 0;
63
    TAILQ_FOREACH(svc, &enip_data->service_list, next)
64
    {
65
        //SCLogDebug("CIP Service #%d : 0x%x", count, svc->service);
66
        count++;
67
    }
68
}
69
#endif
70
71
/**
72
 * \brief Matches the rule to the CIP segment in ENIP Packet
73
 * @param svc - the CIP service entry
74
 * * @param cipserviced - the CIP service rule
75
 */
76
static int CIPPathMatch(CIPServiceEntry *svc, DetectCipServiceData *cipserviced)
77
0
{
78
0
    uint16_t class = 0;
79
0
    uint16_t attrib = 0;
80
0
    int found_class = 0;
81
82
0
    SegmentEntry *seg = NULL;
83
0
    TAILQ_FOREACH(seg, &svc->segment_list, next)
84
0
    {
85
0
        switch(seg->segment)
86
0
        {
87
0
            case PATH_CLASS_8BIT:
88
0
                class = seg->value;
89
0
                if (cipserviced->cipclass == class)
90
0
                {
91
0
                    if (cipserviced->tokens == 2)
92
0
                    {// if rule only has class
93
0
                        return 1;
94
0
                    } else
95
0
                    {
96
0
                        found_class = 1;
97
0
                    }
98
0
                }
99
0
                break;
100
0
            case PATH_INSTANCE_8BIT:
101
0
                break;
102
0
            case PATH_ATTR_8BIT: //single attribute
103
0
                attrib = seg->value;
104
0
                if ((cipserviced->tokens == 3) &&
105
0
                        (cipserviced->cipclass == class) &&
106
0
                        (cipserviced->cipattribute == attrib) &&
107
0
                        (cipserviced->matchattribute == 1))
108
0
                { // if rule has class & attribute, matched all here
109
0
                    return 1;
110
0
                }
111
0
                if ((cipserviced->tokens == 3) &&
112
0
                        (cipserviced->cipclass == class) &&
113
0
                        (cipserviced->matchattribute == 0))
114
0
                { // for negation rule on attribute
115
0
                    return 1;
116
0
                }
117
0
                break;
118
0
            case PATH_CLASS_16BIT:
119
0
                class = seg->value;
120
0
                if (cipserviced->cipclass == class)
121
0
                {
122
0
                    if (cipserviced->tokens == 2)
123
0
                    {// if rule only has class
124
0
                        return 1;
125
0
                    } else
126
0
                    {
127
0
                        found_class = 1;
128
0
                    }
129
0
                }
130
0
                break;
131
0
            case PATH_INSTANCE_16BIT:
132
0
                break;
133
0
            default:
134
0
                return 0;
135
0
        }
136
0
    }
137
138
0
    if (found_class == 0)
139
0
    { // if haven't matched class yet, no need to check attribute
140
0
        return 0;
141
0
    }
142
143
0
    if ((svc->service == CIP_SET_ATTR_LIST) ||
144
0
            (svc->service == CIP_GET_ATTR_LIST))
145
0
    {
146
0
        AttributeEntry *attr = NULL;
147
0
        TAILQ_FOREACH    (attr, &svc->attrib_list, next)
148
0
        {
149
0
            if (cipserviced->cipattribute == attr->attribute)
150
0
            {
151
0
                return 1;
152
0
            }
153
0
        }
154
0
    }
155
156
0
    return 0;
157
0
}
158
159
/**
160
 * \brief Matches the rule to the ENIP Transaction
161
 * @param enip_data - the ENIP transaction
162
 * * @param cipserviced - the CIP service rule
163
 */
164
165
static int CIPServiceMatch(ENIPTransaction *enip_data,
166
        DetectCipServiceData *cipserviced)
167
1.42k
{
168
#ifdef DEBUG
169
    int count = 1;
170
#endif
171
1.42k
    CIPServiceEntry *svc = NULL;
172
    //SCLogDebug("CIPServiceMatchAL");
173
1.42k
    TAILQ_FOREACH(svc, &enip_data->service_list, next)
174
1.66k
    {
175
1.66k
        SCLogDebug("CIPServiceMatchAL service #%d : 0x%x dir %d",
176
1.66k
                count, svc->service,  svc->direction);
177
178
1.66k
        if (cipserviced->cipservice == svc->service)
179
417
        { // compare service
180
            //SCLogDebug("Rule Match for cip service %d",cipserviced->cipservice );
181
182
417
            if (cipserviced->tokens > 1)
183
0
            { //if rule params have class and attribute
184
185
186
0
                if ((svc->service == CIP_SET_ATTR_LIST) || (svc->service
187
0
                                == CIP_SET_ATTR_SINGLE) || (svc->service
188
0
                                == CIP_GET_ATTR_LIST) || (svc->service
189
0
                                == CIP_GET_ATTR_SINGLE))
190
0
                { //decode path
191
0
                    if (CIPPathMatch(svc, cipserviced) == 1)
192
0
                    {
193
0
                        if (svc->direction == 1) return 0; //don't match responses
194
195
0
                        return 1;
196
0
                    }
197
0
                }
198
0
            } else
199
417
            {
200
417
                if (svc->direction == 1) return 0; //don't match responses
201
202
                // SCLogDebug("CIPServiceMatchAL found");
203
211
                return 1;
204
417
            }
205
417
        }
206
#ifdef DEBUG
207
        count++;
208
#endif
209
1.66k
    }
210
1.00k
    return 0;
211
1.42k
}
212
213
/** \brief Do the content inspection & validation for a signature
214
 *
215
 *  \param de_ctx   Detection engine context
216
 *  \param det_ctx  Detection engine thread context
217
 *  \param s        Signature to inspect ( and sm: SigMatch to inspect)
218
 *  \param f        Flow
219
 *  \param flags    App layer flags
220
 *  \param alstate  App layer state
221
 *  \param txv      Pointer to ENIP Transaction structure
222
 *
223
 *  \retval 0 no match or 1 match
224
 */
225
uint8_t DetectEngineInspectCIP(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
226
        const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f,
227
        uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
228
1.42k
{
229
1.42k
    SCEnter();
230
231
232
1.42k
    ENIPTransaction *tx = (ENIPTransaction *) txv;
233
1.42k
    DetectCipServiceData *cipserviced = (DetectCipServiceData *)engine->smd->ctx;
234
235
1.42k
    if (cipserviced == NULL)
236
0
    {
237
0
        SCLogDebug("no cipservice state, no match");
238
0
        SCReturnInt(0);
239
0
    }
240
    //SCLogDebug("DetectEngineInspectCIP %d", cipserviced->cipservice);
241
242
1.42k
    if (CIPServiceMatch(tx, cipserviced) == 1)
243
211
    {
244
        //SCLogDebug("DetectCIPServiceMatchAL found");
245
211
        SCReturnInt(1);
246
211
    }
247
248
1.42k
    SCReturnInt(0);
249
1.42k
}
250
251
/** \brief Do the content inspection & validation for a signature
252
 *
253
 *  \param de_ctx   Detection engine context
254
 *  \param det_ctx  Detection engine thread context
255
 *  \param s        Signature to inspect ( and sm: SigMatch to inspect)
256
 *  \param f        Flow
257
 *  \param flags    App layer flags
258
 *  \param alstate  App layer state
259
 *  \param txv      Pointer to ENIP Transaction structure
260
 *
261
 *  \retval 0 no match or 1 match
262
 */
263
264
uint8_t DetectEngineInspectENIP(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
265
        const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f,
266
        uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
267
1.90k
{
268
1.90k
    SCEnter();
269
270
1.90k
    ENIPTransaction *tx = (ENIPTransaction *) txv;
271
1.90k
    DetectEnipCommandData *enipcmdd = (DetectEnipCommandData *)engine->smd->ctx;
272
273
1.90k
    if (enipcmdd == NULL)
274
0
    {
275
0
        SCLogDebug("no enipcommand state, no match");
276
0
        SCReturnInt(0);
277
0
    }
278
279
    //SCLogDebug("DetectEngineInspectENIP %d, %d", enipcmdd->enipcommand, tx->header.command);
280
281
1.90k
    if (enipcmdd->enipcommand == tx->header.command)
282
927
    {
283
        // SCLogDebug("DetectENIPCommandMatchAL found!");
284
927
        SCReturnInt(1);
285
927
    }
286
287
978
    SCReturnInt(0);
288
1.90k
}