Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/detect-ssl-version.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   detect-ssl-version.c
20
 *
21
 * \author Gurvinder Singh <gurvindersinghdahiya@gmail.com>
22
 *
23
 * Implements the ssl_version keyword
24
 */
25
26
#include "suricata-common.h"
27
#include "threads.h"
28
#include "decode.h"
29
30
#include "detect.h"
31
#include "detect-parse.h"
32
33
#include "detect-engine.h"
34
#include "detect-engine-mpm.h"
35
#include "detect-engine-state.h"
36
37
#include "flow.h"
38
#include "flow-var.h"
39
#include "flow-util.h"
40
41
#include "util-debug.h"
42
#include "util-unittest.h"
43
#include "util-unittest-helper.h"
44
45
#include "app-layer.h"
46
#include "app-layer-parser.h"
47
48
#include "detect-ssl-version.h"
49
50
#include "stream-tcp.h"
51
#include "app-layer-ssl.h"
52
53
54
static int DetectSslVersionMatch(DetectEngineThreadCtx *,
55
        Flow *, uint8_t, void *, void *,
56
        const Signature *, const SigMatchCtx *);
57
static int DetectSslVersionSetup(DetectEngineCtx *, Signature *, const char *);
58
#ifdef UNITTESTS
59
static void DetectSslVersionRegisterTests(void);
60
#endif
61
static void DetectSslVersionFree(DetectEngineCtx *, void *);
62
static int g_tls_generic_list_id = 0;
63
64
/**
65
 * \brief Registration function for keyword: ssl_version
66
 */
67
void DetectSslVersionRegister(void)
68
73
{
69
73
    sigmatch_table[DETECT_AL_SSL_VERSION].name = "ssl_version";
70
73
    sigmatch_table[DETECT_AL_SSL_VERSION].desc = "match version of SSL/TLS record";
71
73
    sigmatch_table[DETECT_AL_SSL_VERSION].url = "/rules/tls-keywords.html#ssl-version";
72
73
    sigmatch_table[DETECT_AL_SSL_VERSION].AppLayerTxMatch = DetectSslVersionMatch;
73
73
    sigmatch_table[DETECT_AL_SSL_VERSION].Setup = DetectSslVersionSetup;
74
73
    sigmatch_table[DETECT_AL_SSL_VERSION].Free  = DetectSslVersionFree;
75
#ifdef UNITTESTS
76
    sigmatch_table[DETECT_AL_SSL_VERSION].RegisterTests = DetectSslVersionRegisterTests;
77
#endif
78
79
73
    g_tls_generic_list_id = DetectBufferTypeRegister("tls_generic");
80
73
}
81
82
/**
83
 * \brief match the specified version on a ssl session
84
 *
85
 * \param t pointer to thread vars
86
 * \param det_ctx pointer to the pattern matcher thread
87
 * \param p pointer to the current packet
88
 * \param m pointer to the sigmatch that we will cast into DetectSslVersionData
89
 *
90
 * \retval 0 no match
91
 * \retval 1 match
92
 */
93
static int DetectSslVersionMatch(DetectEngineThreadCtx *det_ctx,
94
        Flow *f, uint8_t flags, void *state, void *txv,
95
        const Signature *s, const SigMatchCtx *m)
96
0
{
97
0
    SCEnter();
98
99
0
    int ret = 0;
100
0
    uint16_t ver = 0;
101
0
    uint8_t sig_ver = TLS_UNKNOWN;
102
103
0
    const DetectSslVersionData *ssl = (const DetectSslVersionData *)m;
104
0
    SSLState *app_state = (SSLState *)state;
105
0
    if (app_state == NULL) {
106
0
        SCLogDebug("no app state, no match");
107
0
        SCReturnInt(0);
108
0
    }
109
110
0
    if (flags & STREAM_TOCLIENT) {
111
0
        SCLogDebug("server (toclient) version is 0x%02X",
112
0
                   app_state->server_connp.version);
113
0
        ver = app_state->server_connp.version;
114
0
    } else if (flags & STREAM_TOSERVER) {
115
0
        SCLogDebug("client (toserver) version is 0x%02X",
116
0
                   app_state->client_connp.version);
117
0
        ver = app_state->client_connp.version;
118
0
    }
119
120
0
    switch (ver) {
121
0
        case SSL_VERSION_2:
122
0
            if (ver == ssl->data[SSLv2].ver)
123
0
                ret = 1;
124
0
            sig_ver = SSLv2;
125
0
            break;
126
0
        case SSL_VERSION_3:
127
0
            if (ver == ssl->data[SSLv3].ver)
128
0
                ret = 1;
129
0
            sig_ver = SSLv3;
130
0
            break;
131
0
        case TLS_VERSION_10:
132
0
            if (ver == ssl->data[TLS10].ver)
133
0
                ret = 1;
134
0
            sig_ver = TLS10;
135
0
            break;
136
0
        case TLS_VERSION_11:
137
0
            if (ver == ssl->data[TLS11].ver)
138
0
                ret = 1;
139
0
            sig_ver = TLS11;
140
0
            break;
141
0
        case TLS_VERSION_12:
142
0
            if (ver == ssl->data[TLS12].ver)
143
0
                ret = 1;
144
0
            sig_ver = TLS12;
145
0
            break;
146
0
        case TLS_VERSION_13_DRAFT28:
147
0
        case TLS_VERSION_13_DRAFT27:
148
0
        case TLS_VERSION_13_DRAFT26:
149
0
        case TLS_VERSION_13_DRAFT25:
150
0
        case TLS_VERSION_13_DRAFT24:
151
0
        case TLS_VERSION_13_DRAFT23:
152
0
        case TLS_VERSION_13_DRAFT22:
153
0
        case TLS_VERSION_13_DRAFT21:
154
0
        case TLS_VERSION_13_DRAFT20:
155
0
        case TLS_VERSION_13_DRAFT19:
156
0
        case TLS_VERSION_13_DRAFT18:
157
0
        case TLS_VERSION_13_DRAFT17:
158
0
        case TLS_VERSION_13_DRAFT16:
159
0
        case TLS_VERSION_13_PRE_DRAFT16:
160
0
            if (((ver >> 8) & 0xff) == 0x7f)
161
0
                ver = TLS_VERSION_13;
162
            /* fall through */
163
0
        case TLS_VERSION_13:
164
0
            if (ver == ssl->data[TLS13].ver)
165
0
                ret = 1;
166
0
            sig_ver = TLS13;
167
0
            break;
168
0
    }
169
170
0
    if (sig_ver == TLS_UNKNOWN)
171
0
        SCReturnInt(0);
172
173
0
    SCReturnInt(ret ^ ((ssl->data[sig_ver].flags & DETECT_SSL_VERSION_NEGATED) ? 1 : 0));
174
0
}
175
176
struct SSLVersionKeywords {
177
    const char *word;
178
    int index;
179
    uint16_t value;
180
};
181
182
struct SSLVersionKeywords ssl_version_keywords[TLS_SIZE] = {
183
    { "sslv2", SSLv2, SSL_VERSION_2 },
184
    { "sslv3", SSLv3, SSL_VERSION_3 },
185
    { "tls1.0", TLS10, TLS_VERSION_10 },
186
    { "tls1.1", TLS11, TLS_VERSION_11 },
187
    { "tls1.2", TLS12, TLS_VERSION_12 },
188
    { "tls1.3", TLS13, TLS_VERSION_13 },
189
};
190
191
/**
192
 * \brief This function is used to parse ssl_version data passed via
193
 *        keyword: "ssl_version"
194
 *
195
 * \param de_ctx Pointer to the detection engine context
196
 * \param str Pointer to the user provided options
197
 *
198
 * \retval ssl pointer to DetectSslVersionData on success
199
 * \retval NULL on failure
200
 */
201
static DetectSslVersionData *DetectSslVersionParse(DetectEngineCtx *de_ctx, const char *str)
202
1.45k
{
203
1.45k
    DetectSslVersionData *ssl = NULL;
204
1.45k
    const char *tmp_str = str;
205
1.45k
    size_t tmp_len = 0;
206
1.45k
    uint8_t found = 0;
207
208
    /* We have a correct ssl_version options */
209
1.45k
    ssl = SCCalloc(1, sizeof(DetectSslVersionData));
210
1.45k
    if (unlikely(ssl == NULL))
211
0
        goto error;
212
213
    // skip leading space
214
1.65k
    while (tmp_str[0] != 0 && isspace(tmp_str[0])) {
215
197
        tmp_str++;
216
197
    }
217
1.45k
    if (tmp_str[0] == 0) {
218
1
        SCLogError("Invalid empty value");
219
1
        goto error;
220
1
    }
221
    // iterate every version separated by comma
222
2.13k
    while (tmp_str[0] != 0) {
223
1.52k
        uint8_t neg = 0;
224
1.52k
        if (tmp_str[0] == '!') {
225
96
            neg = 1;
226
96
            tmp_str++;
227
96
        }
228
        // counts word length
229
1.52k
        tmp_len = 0;
230
35.1k
        while (tmp_str[tmp_len] != 0 && !isspace(tmp_str[tmp_len]) && tmp_str[tmp_len] != ',') {
231
33.6k
            tmp_len++;
232
33.6k
        }
233
234
1.52k
        bool is_keyword = false;
235
9.70k
        for (size_t i = 0; i < TLS_SIZE; i++) {
236
8.85k
            if (tmp_len == strlen(ssl_version_keywords[i].word) &&
237
2.56k
                    strncasecmp(ssl_version_keywords[i].word, tmp_str, tmp_len) == 0) {
238
674
                if (ssl->data[ssl_version_keywords[i].index].ver != 0) {
239
1
                    SCLogError("Invalid duplicate value");
240
1
                    goto error;
241
1
                }
242
673
                ssl->data[ssl_version_keywords[i].index].ver = ssl_version_keywords[i].value;
243
673
                if (neg == 1)
244
87
                    ssl->data[ssl_version_keywords[i].index].flags |= DETECT_SSL_VERSION_NEGATED;
245
673
                is_keyword = true;
246
673
                break;
247
674
            }
248
8.85k
        }
249
1.52k
        if (!is_keyword) {
250
847
            SCLogError("Invalid unknown value");
251
847
            goto error;
252
847
        }
253
254
        /* check consistency between negative and positive values :
255
         * if there is a negative value, it overrides positive values
256
         */
257
673
        if (found == 0) {
258
629
            found |= 1 << neg;
259
629
        } else if (found != 1 << neg) {
260
1
            SCLogError("Invalid value mixing negative and positive forms");
261
1
            goto error;
262
1
        }
263
264
672
        tmp_str += tmp_len;
265
3.31k
        while (isspace(tmp_str[0]) || tmp_str[0] == ',') {
266
2.64k
            tmp_str++;
267
2.64k
        }
268
672
    }
269
270
609
    return ssl;
271
272
850
error:
273
850
    if (ssl != NULL)
274
850
        DetectSslVersionFree(de_ctx, ssl);
275
850
    return NULL;
276
277
1.45k
}
278
279
/**
280
 * \brief this function is used to add the parsed "id" option
281
 * \brief into the current signature
282
 *
283
 * \param de_ctx pointer to the Detection Engine Context
284
 * \param s pointer to the Current Signature
285
 * \param idstr pointer to the user provided "id" option
286
 *
287
 * \retval 0 on Success
288
 * \retval -1 on Failure
289
 */
290
static int DetectSslVersionSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str)
291
282
{
292
282
    DetectSslVersionData *ssl = NULL;
293
282
    SigMatch *sm = NULL;
294
295
282
    if (DetectSignatureSetAppProto(s, ALPROTO_TLS) != 0)
296
7
        return -1;
297
298
275
    ssl = DetectSslVersionParse(de_ctx, str);
299
275
    if (ssl == NULL)
300
271
        goto error;
301
302
    /* Okay so far so good, lets get this into a SigMatch
303
     * and put it in the Signature. */
304
4
    sm = SigMatchAlloc();
305
4
    if (sm == NULL)
306
0
        goto error;
307
308
4
    sm->type = DETECT_AL_SSL_VERSION;
309
4
    sm->ctx = (void *)ssl;
310
311
4
    SigMatchAppendSMToList(s, sm, g_tls_generic_list_id);
312
4
    return 0;
313
314
271
error:
315
271
    if (ssl != NULL)
316
0
        DetectSslVersionFree(de_ctx, ssl);
317
271
    if (sm != NULL)
318
0
        SCFree(sm);
319
271
    return -1;
320
4
}
321
322
/**
323
 * \brief this function will free memory associated with DetectSslVersionData
324
 *
325
 * \param id_d pointer to DetectSslVersionData
326
 */
327
void DetectSslVersionFree(DetectEngineCtx *de_ctx, void *ptr)
328
1.45k
{
329
1.45k
    if (ptr != NULL)
330
1.45k
        SCFree(ptr);
331
1.45k
}
332
333
#ifdef UNITTESTS
334
#include "tests/detect-ssl-version.c"
335
#endif