1 /*
2  * This program is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License Version 2 as
4  * published by the Free Software Foundation.  You may not use, modify or
5  * distribute this program under any other version of the GNU General
6  * Public License.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16  *
17  * Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
18  * Copyright (C) 2011-2013 Sourcefire, Inc.
19  *
20  * Author: Ryan Jordan
21  *
22  * Rule options for the DNP3 preprocessor
23  *
24  */
25 
26 #include <string.h>
27 
28 #include "sf_types.h"
29 #include "sf_snort_plugin_api.h"
30 #include "sf_snort_packet.h"
31 #include "sf_dynamic_preprocessor.h"
32 #include "mempool.h"
33 
34 #include "spp_dnp3.h"
35 #include "dnp3_map.h"
36 #include "dnp3_roptions.h"
37 
38 /* Object decoding constants */
39 #define DNP3_OBJ_HDR_MIN_LEN 3 /* group, var, qualifier */
40 #define DNP3_OBJ_QUAL_PREFIX(x) ((x & 0x70) >> 4)
41 #define DNP3_OBJ_QUAL_RANGE(x) (x & 0x0F)
42 
43 /* Object header prefix codes */
44 #define DNP3_PREFIX_NO_PREFIX   0x00
45 #define DNP3_PREFIX_1OCT_INDEX  0x01
46 #define DNP3_PREFIX_2OCT_INDEX  0x02
47 #define DNP3_PREFIX_4OCT_INDEX  0x03
48 #define DNP3_PREFIX_1OCT_SIZE   0x04
49 #define DNP3_PREFIX_2OCT_SIZE   0x05
50 #define DNP3_PREFIX_4OCT_SIZE   0x06
51 #define DNP3_PREFIX_RESERVED    0x07
52 
53 /* Object header range specifiers -- 0x0A & 0x0C-0x0F are reserved */
54 #define DNP3_RANGE_1OCT_INDICES     0x00
55 #define DNP3_RANGE_2OCT_INDICES     0x01
56 #define DNP3_RANGE_4OCT_INDICES     0x02
57 #define DNP3_RANGE_1OCT_ADDRESSES   0x03
58 #define DNP3_RANGE_2OCT_ADDRESSES   0x04
59 #define DNP3_RANGE_4OCT_ADDRESSES   0x05
60 #define DNP3_RANGE_NO_RANGE         0x06
61 #define DNP3_RANGE_1OCT_COUNT       0x07
62 #define DNP3_RANGE_2OCT_COUNT       0x08
63 #define DNP3_RANGE_4OCT_COUNT       0x09
64 #define DNP3_RANGE_VARIABLE         0x0B
65 
66 typedef enum _dnp3_option_type_t
67 {
68     DNP3_FUNC = 0,
69     DNP3_OBJ,
70     DNP3_IND,
71     DNP3_DATA
72 } dnp3_option_type_t;
73 
74 typedef struct _dnp3_option_data_t
75 {
76     dnp3_option_type_t type;
77     uint16_t arg;
78 } dnp3_option_data_t;
79 
80 /* Parsing functions */
DNP3FuncInit(struct _SnortConfig * sc,char * name,char * params,void ** data)81 int DNP3FuncInit(struct _SnortConfig *sc, char *name, char *params, void **data)
82 {
83     char *endptr;
84     dnp3_option_data_t *dnp3_data;
85     long func_code;
86 
87     if (name == NULL || data == NULL)
88         return 0;
89 
90     if (params == NULL)
91     {
92         DynamicPreprocessorFatalMessage("%s(%d): dnp3_func requires a "
93             "number beween 0 and 255, or a valid function name.\n",
94             *_dpd.config_file, *_dpd.config_line);
95     }
96 
97     if (strcmp(name, DNP3_FUNC_NAME) != 0)
98         return 0;
99 
100     dnp3_data = (dnp3_option_data_t *)calloc(1, sizeof(dnp3_option_data_t));
101     if (dnp3_data == NULL)
102     {
103         DynamicPreprocessorFatalMessage("%s(%d) Failed to allocate memory for "
104                       "dnp3_func data structure.\n", __FILE__, __LINE__);
105     }
106 
107     /* Parsing time */
108     if (isdigit(params[0]))
109     {
110         /* Function code given as integer */
111         func_code = _dpd.SnortStrtol(params, &endptr, 10);
112         if ((func_code > 255) || (func_code < 0) || (*endptr != '\0'))
113         {
114             DynamicPreprocessorFatalMessage("%s(%d): dnp3_func requires a "
115                 "number beween 0 and 255, or a valid function name.\n",
116                 *_dpd.config_file, *_dpd.config_line);
117         }
118     }
119     else
120     {
121         func_code = DNP3FuncStrToCode(params);
122 
123         if (func_code == -1)
124         {
125             DynamicPreprocessorFatalMessage("%s(%d): dnp3_func requires a "
126                 "number beween 0 and 255, or a valid function name.\n",
127                 *_dpd.config_file, *_dpd.config_line);
128         }
129     }
130 
131     dnp3_data->type = DNP3_FUNC;
132     dnp3_data->arg = (uint16_t) func_code;
133 
134     *data = (void *)dnp3_data;
135 
136     return 1;
137 }
138 
DNP3ObjError(void)139 NORETURN static inline void DNP3ObjError(void)
140 {
141     DynamicPreprocessorFatalMessage("%s(%d) dnp3_obj requires two arguments,"
142         "where each argument is a number between 0 and 255.\n",
143         *_dpd.config_file, *_dpd.config_line);
144 }
145 
DNP3ObjInit(struct _SnortConfig * sc,char * name,char * params,void ** data)146 int DNP3ObjInit(struct _SnortConfig *sc, char *name, char *params, void **data)
147 {
148     char *endptr, *token, *saveptr;
149     dnp3_option_data_t *dnp3_data;
150     unsigned int obj_group, obj_var;
151 
152     if (name == NULL || data == NULL)
153         return 0;
154 
155     if (strcmp(name, DNP3_OBJ_NAME) != 0)
156         return 0;
157 
158     if (params == NULL)
159     {
160         DynamicPreprocessorFatalMessage("%s(%d): No argument given for dnp3_obj. "
161             "dnp3_obj requires two arguments, where each argument is a number "
162             "between 0 and 255.\n", *_dpd.config_file, *_dpd.config_line);
163     }
164 
165     dnp3_data = (dnp3_option_data_t *)calloc(1, sizeof(dnp3_option_data_t));
166     if (dnp3_data == NULL)
167     {
168         DynamicPreprocessorFatalMessage("%s(%d) Failed to allocate memory for "
169                       "dnp3_func data structure.\n", __FILE__, __LINE__);
170     }
171 
172     token = strtok_r(params, ",", &saveptr);
173     if (token == NULL)
174         DNP3ObjError();
175 
176     /* First token: object group */
177     obj_group = _dpd.SnortStrtoul(token, &endptr, 10);
178     if ((obj_group > 255) || (*endptr != '\0'))
179         DNP3ObjError();
180 
181     token = strtok_r(NULL, ",", &saveptr);
182     if (token == NULL)
183         DNP3ObjError();
184 
185     /* Second token: object var */
186     obj_var = _dpd.SnortStrtoul(token, &endptr, 10);
187     if ((obj_var > 255) || (*endptr != '\0'))
188         DNP3ObjError();
189 
190     /* pack the two arguments into one uint16_t */
191     dnp3_data->type = DNP3_OBJ;
192     dnp3_data->arg = ((obj_group << 8) | (obj_var));
193 
194     *data = dnp3_data;
195 
196     return 1;
197 }
198 
DNP3IndInit(struct _SnortConfig * sc,char * name,char * params,void ** data)199 int DNP3IndInit(struct _SnortConfig *sc, char *name, char *params, void **data)
200 {
201     dnp3_option_data_t *dnp3_data;
202     char *token, *saveptr;
203     uint16_t flags = 0;
204 
205     if (name == NULL || data == NULL)
206         return 0;
207 
208     if (params == NULL)
209     {
210         DynamicPreprocessorFatalMessage("%s(%d): dnp3_ind requires a "
211             "number beween 0 and 255, or a valid function name.\n",
212             *_dpd.config_file, *_dpd.config_line);
213     }
214 
215     dnp3_data = (dnp3_option_data_t *)calloc(1, sizeof(dnp3_option_data_t));
216     if (dnp3_data == NULL)
217     {
218         DynamicPreprocessorFatalMessage("%s(%d) Failed to allocate memory for "
219                       "dnp3_func data structure.\n", __FILE__, __LINE__);
220     }
221 
222     token = strtok_r(params, ",", &saveptr);
223 
224     while (token != NULL)
225     {
226         int flag = DNP3IndStrToCode(token);
227 
228         if (flag == -1)
229         {
230             DynamicPreprocessorFatalMessage("%s(%d): dnp3_ind requires a "
231                 "valid indication flag name. '%s' is invalid.\n",
232                 *_dpd.config_file, *_dpd.config_line, token);
233         }
234 
235         flags |= (uint16_t) flag;
236         token = strtok_r(NULL, ",", &saveptr);
237     }
238 
239     if (flags == 0)
240     {
241         DynamicPreprocessorFatalMessage("%s(%d): dnp3_ind requires a "
242             "valid indication flag name. No flags were given.\n",
243             *_dpd.config_file, *_dpd.config_line);
244     }
245 
246     dnp3_data->type = DNP3_IND;
247     dnp3_data->arg = flags;
248 
249     *data = (void *)dnp3_data;
250 
251     return 1;
252 }
253 
DNP3DataInit(struct _SnortConfig * sc,char * name,char * params,void ** data)254 int DNP3DataInit(struct _SnortConfig *sc, char *name, char *params, void **data)
255 {
256     dnp3_option_data_t *dnp3_data;
257 
258     if (name == NULL || data == NULL)
259         return 0;
260 
261     /* nothing to parse. */
262     if (params)
263     {
264         DynamicPreprocessorFatalMessage("%s(%d): dnp3_data does not take "
265             "any arguments.\n", *_dpd.config_file, *_dpd.config_line);
266     }
267 
268     dnp3_data = (dnp3_option_data_t *)calloc(1, sizeof(dnp3_option_data_t));
269     if (dnp3_data == NULL)
270     {
271         DynamicPreprocessorFatalMessage("%s(%d) Failed to allocate memory for "
272                       "dnp3_data data structure.\n", __FILE__, __LINE__);
273     }
274 
275     dnp3_data->type = DNP3_DATA;
276     dnp3_data->arg = 0;
277 
278     *data = (void *)dnp3_data;
279 
280     return 1;
281 }
282 
283 /* Evaluation functions */
DNP3FuncEval(void * raw_packet,const uint8_t ** cursor,void * data)284 int DNP3FuncEval(void *raw_packet, const uint8_t **cursor, void *data)
285 {
286     SFSnortPacket *packet = (SFSnortPacket *)raw_packet;
287     MemBucket *tmp_bucket;
288     dnp3_option_data_t *rule_data = (dnp3_option_data_t *)data;
289     dnp3_session_data_t *session_data;
290     dnp3_reassembly_data_t *rdata;
291 
292     /* The preprocessor only evaluates PAF-flushed PDUs. If the rule options
293        don't check for this, they'll fire on stale session data when the
294        original packet goes through before flushing. */
295     if (packet->tcp_header && !PacketHasFullPDU(packet))
296         return RULE_NOMATCH;
297 
298     /* For UDP packets, there is no PAF so we use the Alt Decode buffer. */
299     if (packet->udp_header && !_dpd.Is_DetectFlag(SF_FLAG_ALT_DECODE))
300         return RULE_NOMATCH;
301 
302     tmp_bucket = (MemBucket *)
303         _dpd.sessionAPI->get_application_data(packet->stream_session, PP_DNP3);
304 
305     if ((packet->payload_size == 0) || (tmp_bucket == NULL))
306     {
307         return RULE_NOMATCH;
308     }
309 
310     session_data = (dnp3_session_data_t *)tmp_bucket->data;
311 
312     if (session_data->direction == DNP3_CLIENT)
313         rdata = &(session_data->client_rdata);
314     else
315         rdata = &(session_data->server_rdata);
316 
317     /* Only evaluate rules against complete Application-layer fragments */
318     if (rdata->state != DNP3_REASSEMBLY_STATE__DONE)
319         return RULE_NOMATCH;
320 
321     if (session_data->func == rule_data->arg)
322         return RULE_MATCH;
323 
324     return RULE_NOMATCH;
325 }
326 
DNP3DecodeObject(uint8_t * buf,uint16_t buflen,uint8_t rule_group,uint8_t rule_var)327 static int DNP3DecodeObject(uint8_t *buf, uint16_t buflen, uint8_t rule_group, uint8_t rule_var)
328 {
329     uint8_t group, var;
330 
331     /* XXX: uncomment these when fixing the below TODO regarding multiple objects
332     uint8_t qualifier, prefix_size, prefix_code, range_specifier;
333     uint32_t begin, end, num_objects;
334     */
335 
336     if (buf == NULL || buflen < DNP3_OBJ_HDR_MIN_LEN)
337         return RULE_NOMATCH;
338 
339     /* Decode group */
340     group = *buf;
341     buf++;
342     buflen--;
343 
344     /* Decode var */
345     var = *buf;
346     buf++;
347     buflen--;
348 
349     /* Match the rule option here, quit decoding if we found the right header. */
350     if ((group == rule_group) && (var == rule_var))
351         return RULE_MATCH;
352 
353 /* TODO: Implement matching with multiple objects in a Request/Response. */
354 #if 0
355     /* Decode qualifier */
356     qualifier = *buf;
357     prefix_code = DNP3_OBJ_QUAL_PREFIX(qualifier);
358     range_specifier = DNP3_OBJ_QUAL_RANGE(qualifier);
359     buf++;
360     buflen--;
361 
362     /* The size of object prefixes depends on the prefix code */
363     switch (prefix_code)
364     {
365         case DNP3_PREFIX_NO_PREFIX:
366             prefix_size = 0;
367             break;
368 
369         case DNP3_PREFIX_1OCT_INDEX:
370         case DNP3_PREFIX_1OCT_SIZE:
371             prefix_size = 1;
372             break;
373 
374         case DNP3_PREFIX_2OCT_INDEX:
375         case DNP3_PREFIX_2OCT_SIZE:
376             prefix_size = 2;
377             break;
378 
379         case DNP3_PREFIX_4OCT_INDEX:
380         case DNP3_PREFIX_4OCT_SIZE:
381             prefix_size = 4;
382             break;
383 
384         default:
385             /* TODO: Preprocessor alert on reserved value */
386             return DNP3_FAIL;
387     }
388 
389     /* Decoding of the range field depends on the Range Specifier */
390     switch (range_specifier)
391     {
392         case DNP3_RANGE_1OCT_INDICES:
393             if (buflen < 2)
394                 return DNP3_FAIL;
395 
396             /* Decode 8-bit indices for object prefixes */
397             begin = *(uint8_t *)buf++;
398             end = *(uint8_t *)buf++;
399             buflen -= 2;
400 
401             /* Check that indices make sense */
402             if (begin > end)
403                 return DNP3_FAIL; /* TODO: Preprocessor alert */
404 
405             num_objects = end - begin + 1;
406             break;
407 
408         case DNP3_RANGE_2OCT_INDICES:
409             if (buflen < 2)
410                 return DNP3_FAIL;
411 
412             /* Decode 8-bit indices for object prefixes */
413             begin = *(uint16_t *)buf++;
414             end = *(uint16_t *)buf++;
415             buflen -= 2;
416 
417             /* Check that indices make sense */
418             if (begin > end)
419                 return DNP3_FAIL; /* TODO: Preprocessor alert */
420 
421             num_objects = end - begin + 1;
422             break;
423 
424         case DNP3_RANGE_4OCT_INDICES:
425         case DNP3_RANGE_1OCT_ADDRESSES:
426         case DNP3_RANGE_2OCT_ADDRESSES:
427         case DNP3_RANGE_4OCT_ADDRESSES:
428         case DNP3_RANGE_NO_RANGE:
429         case DNP3_RANGE_1OCT_COUNT:
430         case DNP3_RANGE_2OCT_COUNT:
431         case DNP3_RANGE_4OCT_COUNT:
432         case DNP3_RANGE_VARIABLE:
433         default:
434     }
435 #endif /* 0 */
436 
437     return RULE_NOMATCH;
438 }
439 
DNP3ObjEval(void * raw_packet,const uint8_t ** cursor,void * data)440 int DNP3ObjEval(void *raw_packet, const uint8_t **cursor, void *data)
441 {
442     SFSnortPacket *packet = (SFSnortPacket *)raw_packet;
443     MemBucket *tmp_bucket;
444     dnp3_option_data_t *rule_data = (dnp3_option_data_t *)data;
445     dnp3_session_data_t *session_data;
446     dnp3_reassembly_data_t *rdata;
447     uint8_t group, var;
448     uint8_t *obj_buffer;
449     uint16_t obj_buflen;
450     size_t header_size;
451     int rval = RULE_NOMATCH;
452 
453     /* The preprocessor only evaluates PAF-flushed PDUs. If the rule options
454        don't check for this, they'll fire on stale session data when the
455        original packet goes through before flushing. */
456     if (packet->tcp_header && !PacketHasFullPDU(packet))
457         return RULE_NOMATCH;
458 
459     /* For UDP packets, there is no PAF so we use the Alt Decode buffer. */
460     if (packet->udp_header && !_dpd.Is_DetectFlag(SF_FLAG_ALT_DECODE))
461         return RULE_NOMATCH;
462 
463     tmp_bucket = (MemBucket *)
464         _dpd.sessionAPI->get_application_data(packet->stream_session, PP_DNP3);
465 
466     if ((packet->payload_size == 0) || (tmp_bucket == NULL))
467     {
468         return RULE_NOMATCH;
469     }
470 
471     session_data = (dnp3_session_data_t *)tmp_bucket->data;
472 
473     if (session_data->direction == DNP3_CLIENT)
474     {
475         rdata = &(session_data->client_rdata);
476         header_size = sizeof(dnp3_app_request_header_t);
477     }
478     else
479     {
480         rdata = &(session_data->server_rdata);
481         header_size = sizeof(dnp3_app_response_header_t);
482     }
483 
484     /* Only evaluate rules against complete Application-layer fragments */
485     if (rdata->state != DNP3_REASSEMBLY_STATE__DONE)
486         return RULE_NOMATCH;
487 
488     /* Skip over the App request/response header.
489        They are different sizes, depending on whether it is a request or response! */
490     if (rdata->buflen < header_size)
491         return RULE_NOMATCH;
492 
493     obj_buffer = (uint8_t *)rdata->buffer + header_size;
494     obj_buflen = rdata->buflen - header_size;
495 
496     /* Rule parsing code combined our two arguments into a single uint16_t */
497     group = (rule_data->arg >> 8);
498     var = (rule_data->arg & 0x00FF);
499 
500     rval = DNP3DecodeObject(obj_buffer, obj_buflen, group, var);
501 
502     return rval;
503 }
504 
DNP3IndEval(void * raw_packet,const uint8_t ** cursor,void * data)505 int DNP3IndEval(void *raw_packet, const uint8_t **cursor, void *data)
506 {
507     SFSnortPacket *packet = (SFSnortPacket *)raw_packet;
508     MemBucket *tmp_bucket;
509     dnp3_option_data_t *rule_data = (dnp3_option_data_t *)data;
510     dnp3_session_data_t *session_data;
511     dnp3_reassembly_data_t *rdata;
512 
513     /* The preprocessor only evaluates PAF-flushed PDUs. If the rule options
514        don't check for this, they'll fire on stale session data when the
515        original packet goes through before flushing. */
516     if (packet->tcp_header && !PacketHasFullPDU(packet))
517         return RULE_NOMATCH;
518 
519     /* For UDP packets, there is no PAF so we use the Alt Decode buffer. */
520     if (packet->udp_header && !_dpd.Is_DetectFlag(SF_FLAG_ALT_DECODE))
521         return RULE_NOMATCH;
522 
523     tmp_bucket = (MemBucket *)
524         _dpd.sessionAPI->get_application_data(packet->stream_session, PP_DNP3);
525 
526     if ((packet->payload_size == 0) || (tmp_bucket == NULL))
527     {
528         return RULE_NOMATCH;
529     }
530 
531     session_data = (dnp3_session_data_t *)tmp_bucket->data;
532 
533     /* Internal Indications only apply to DNP3 responses, not requests. */
534     if (session_data->direction == DNP3_CLIENT)
535         return RULE_NOMATCH;
536 
537     rdata = &(session_data->server_rdata);
538 
539     /* Only evaluate rules against complete Application-layer fragments */
540     if (rdata->state != DNP3_REASSEMBLY_STATE__DONE)
541         return RULE_NOMATCH;
542 
543     if (session_data->indications & rule_data->arg)
544         return RULE_MATCH;
545 
546     return RULE_NOMATCH;
547 }
548 
DNP3DataEval(void * raw_packet,const uint8_t ** cursor,void * data)549 int DNP3DataEval(void *raw_packet, const uint8_t **cursor, void *data)
550 {
551     SFSnortPacket *packet = (SFSnortPacket *)raw_packet;
552     MemBucket *tmp_bucket;
553     dnp3_session_data_t *session_data;
554     dnp3_reassembly_data_t *rdata;
555 
556     /* The preprocessor only evaluates PAF-flushed PDUs. If the rule options
557        don't check for this, they'll fire on stale session data when the
558        original packet goes through before flushing. */
559     if (packet->tcp_header && !PacketHasFullPDU(packet))
560         return RULE_NOMATCH;
561 
562     /* For UDP packets, there is no PAF so we use the Alt Decode buffer. */
563     if (packet->udp_header && !_dpd.Is_DetectFlag(SF_FLAG_ALT_DECODE))
564         return RULE_NOMATCH;
565 
566     tmp_bucket = (MemBucket *)
567         _dpd.sessionAPI->get_application_data(packet->stream_session, PP_DNP3);
568 
569     if ((packet->payload_size == 0) || (tmp_bucket == NULL))
570     {
571         return RULE_NOMATCH;
572     }
573 
574     session_data = (dnp3_session_data_t *)tmp_bucket->data;
575 
576     if (session_data->direction == DNP3_CLIENT)
577         rdata = &(session_data->client_rdata);
578     else
579         rdata = &(session_data->server_rdata);
580 
581     /* Only evaluate rules against complete Application-layer fragments */
582     if (rdata->state != DNP3_REASSEMBLY_STATE__DONE)
583         return RULE_NOMATCH;
584 
585     /* Set the cursor to the reassembled Application-layer buffer */
586     *cursor = (uint8_t *)rdata->buffer;
587     _dpd.SetAltDetect((uint8_t *)rdata->buffer, rdata->buflen);
588 
589     return RULE_MATCH;
590 }
591