1 /* $Id$ */
2 /*
3 ** file_decomp.c
4 **
5 ** Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
6 **
7 ** This program is free software; you can redistribute it and/or modify
8 ** it under the terms of the GNU General Public License Version 2 as
9 ** published by the Free Software Foundation. You may not use, modify or
10 ** distribute this program under any other version of the GNU General
11 ** Public License.
12 **
13 ** This program is distributed in the hope that it will be useful,
14 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ** GNU General Public License for more details.
17 **
18 ** You should have received a copy of the GNU General Public License
19 ** along with this program; if not, write to the Free Software
20 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 //#include <zlib.h>
28 //#ifdef LZMA
29 //#include <lzma.h>
30 //#endif
31
32 #include "util.h"
33 #include "mempool.h"
34 #include "file_decomp.h"
35 #include "sf_types.h"
36 #include "detection_util.h"
37 #include "memory_stats.h"
38
39 #ifdef FILE_DECOMP_PDF
40 #include "file_decomp_PDF.h"
41 #endif
42 #ifdef FILE_DECOMP_SWF
43 #include "file_decomp_SWF.h"
44 #endif
45
46 #ifdef FILE_DECOMP_PDF
47 static const char PDF_Sig[5] = { '%', 'P', 'D', 'F', '-' };
48 #endif
49 #ifdef FILE_DECOMP_SWF
50 static const char SWF_ZLIB_Sig[3] = { 'C', 'W', 'S' };
51 #ifdef LZMA
52 static const char SWF_LZMA_Sig[3] = { 'Z', 'W', 'S' };
53 #endif
54 static const char SWF_Uncomp_Sig[3] = { 'F', 'W', 'S' };
55 #endif
56
57 /* Please assure that the following value correlates with the set of sig's */
58 #define MAX_SIG_LENGTH (5)
59
60 static struct sig_map_s
61 {
62 const char *Sig;
63 size_t Sig_Length;
64 bool Enabled;
65 file_type_t File_Type;
66 file_compression_type_t File_Compression_Type;
67 } Signature_Map[] =
68 {
69
70 #ifdef FILE_DECOMP_PDF
71 { PDF_Sig, sizeof(PDF_Sig), false, FILE_TYPE_PDF, FILE_COMPRESSION_TYPE_NONE }, // Compression type is embedded in PDF dictionaries
72 #endif /* FILE_DECOMP_PDF */
73 #ifdef FILE_DECOMP_SWF
74 { SWF_ZLIB_Sig, sizeof(SWF_ZLIB_Sig), false, FILE_TYPE_SWF, FILE_COMPRESSION_TYPE_ZLIB },
75 #ifdef LZMA
76 { SWF_LZMA_Sig, sizeof(SWF_LZMA_Sig), false, FILE_TYPE_SWF, FILE_COMPRESSION_TYPE_LZMA },
77 #endif /* LZMA */
78 #endif /* FILE_DECOMP_SWF */
79 { NULL, 0, false, FILE_TYPE_NONE, FILE_COMPRESSION_TYPE_NONE }
80 };
81
82
83 static fd_config_p_t g_Config = NULL;
84
85 /* Define the elements of the Sig_State value (packed for storage efficiency */
86 #define SIG_MATCH_ACTIVE (0x80)
87 #define SIG_SIG_INDEX_MASK (0x70)
88 #define SIG_SIG_INDEX_SHIFT (4)
89 #define SIG_CHR_INDEX_MASK (0x07)
90 #define SIG_CHR_INDEX_SHIFT (0)
91
92 static uint8_t File_Decomp_Buffer[DECODE_BLEN];
93
94 /* Look for possible sig at the current payload location.
95 Do NOT beyond the current location (initial Next_In). */
Locate_Sig_Here(fd_session_p_t SessionPtr)96 static fd_status_t Locate_Sig_Here( fd_session_p_t SessionPtr )
97 {
98 unsigned int Sig_Index, Char_Index;
99
100 /* If there's no new input, we don't change state */
101 if( (SessionPtr->Avail_In == 0) ||
102 (SessionPtr->Next_In == NULL) || (SessionPtr->Next_Out == NULL) )
103 return( File_Decomp_Error );
104
105 if( SessionPtr->Avail_Out < MAX_SIG_LENGTH )
106 return( File_Decomp_BlockOut );
107
108 /* Given that we are here, there is at least one input byte to process.
109 And at least enough room in the output stream for the signature. */
110
111 /* Have we started down a sig string? */
112 if( (SessionPtr->Sig_State & SIG_MATCH_ACTIVE) != 0 )
113 {
114 /* Get the current index into the sig map table (indicating which sig) and
115 the index into the sig itself. */
116 Sig_Index = (SessionPtr->Sig_State & SIG_SIG_INDEX_MASK) >> SIG_SIG_INDEX_SHIFT;
117 /* Char_Index indicates the sig char that we are looking for now. */
118 Char_Index = (SessionPtr->Sig_State & SIG_CHR_INDEX_MASK) >> SIG_CHR_INDEX_SHIFT;
119 }
120 else
121 {
122 Sig_Index = 0;
123 Char_Index = 0;
124 }
125
126 /* There must be more in the input stream for us to look at, else
127 we indicate that we didn't find the sig yet. */
128 if( SessionPtr->Avail_In <= Char_Index )
129 return( File_Decomp_BlockIn );
130
131 /* NOTE: The following code block makes the assumption that there are
132 at least MAX_SIG_LENGTH bytes in the output buffer. This assumption
133 is valid for the current implementation where the signature only
134 occurs at the beginning of the file. For the generic case of the sig
135 begin embedded with the file, the seach will need to modified.*/
136 while( 1 )
137 {
138 /* if we get to the end of the sig table (or the table is empty),
139 indicate that we didn't match a sig */
140 if( Signature_Map[Sig_Index].Sig == NULL )
141 return( File_Decomp_NoSig );
142
143 /* Get next char and see if it matches next char in sig */
144 if( (Signature_Map[Sig_Index].Enabled) &&
145 (*(SessionPtr->Next_In+Char_Index) == *(Signature_Map[Sig_Index].Sig+Char_Index)) )
146 {
147 /* Check to see if we are at the end of the sig string. */
148 if( Char_Index == (Signature_Map[Sig_Index].Sig_Length-1) )
149 {
150 uint8_t *Sig = (uint8_t *)Signature_Map[Sig_Index].Sig;
151 uint16_t Len = (uint16_t)Signature_Map[Sig_Index].Sig_Length;
152
153 SessionPtr->File_Type = Signature_Map[Sig_Index].File_Type;
154 SessionPtr->Decomp_Type = Signature_Map[Sig_Index].File_Compression_Type;
155
156 if( (SessionPtr->File_Type == FILE_TYPE_SWF) && ((SessionPtr->Modes & FILE_REVERT_BIT) != 0) )
157 {
158 Sig = (uint8_t *)SWF_Uncomp_Sig;
159 Len = (uint16_t)sizeof( SWF_Uncomp_Sig );
160 }
161 /* The following is safe as we can only be here is there are
162 are least MAX_SIG_LENGTH bytes in the output buffer */
163 (void)Put_N(SessionPtr, Sig, Len);
164 /* Skip the Sig bytes in the input stream */
165 SessionPtr->Next_In += Len;
166 SessionPtr->Avail_In -= Len;
167 SessionPtr->Total_In += Len;
168 return( File_Decomp_OK );
169 }
170
171 /* check for more available input bytes */
172 if( Char_Index < SessionPtr->Avail_In )
173 {
174 /* Set to the next char and keep checking this matching sig */
175 Char_Index += 1;
176 continue; /* goto top of while() loop */
177 }
178 else
179 {
180 /* Indicate that we are actively finding a sig, save the char index
181 and save the sig index. We'll pickup where we left off when more
182 input is available. */
183 SessionPtr->Sig_State = SIG_MATCH_ACTIVE |
184 ((Sig_Index & SIG_SIG_INDEX_MASK) << SIG_SIG_INDEX_SHIFT) |
185 ((Char_Index & SIG_CHR_INDEX_MASK) << SIG_CHR_INDEX_SHIFT);
186 return( File_Decomp_BlockIn );
187 }
188 }
189 else
190 {
191 /* Failed somewhere matching this sig, goto next sig and reset the
192 Char_Index to the beginning */
193 Sig_Index += 1;
194 Char_Index = 0;
195 }
196 }
197 }
198
Initialize_Decompression(fd_session_p_t SessionPtr)199 static fd_status_t Initialize_Decompression( fd_session_p_t SessionPtr )
200 {
201 fd_status_t Ret_Code = File_Decomp_OK;
202
203 switch( SessionPtr->File_Type )
204 {
205 #ifdef FILE_DECOMP_SWF
206 case( FILE_TYPE_SWF ):
207 {
208 Ret_Code = File_Decomp_Init_SWF( SessionPtr );
209 break;
210 }
211 #endif
212 #ifdef FILE_DECOMP_PDF
213 case( FILE_TYPE_PDF ):
214 {
215 Ret_Code = File_Decomp_Init_PDF( SessionPtr );
216 break;
217 }
218 #endif
219 default:
220 return( File_Decomp_Error );
221 }
222
223 if( Ret_Code == File_Decomp_OK )
224 SessionPtr->State = STATE_ACTIVE;
225
226 return( Ret_Code );
227 }
228
Process_Decompression(fd_session_p_t SessionPtr)229 static fd_status_t Process_Decompression( fd_session_p_t SessionPtr )
230 {
231 fd_status_t Ret_Code = File_Decomp_OK;
232
233 switch( SessionPtr->File_Type )
234 {
235 #ifdef FILE_DECOMP_SWF
236 case( FILE_TYPE_SWF ):
237 {
238 Ret_Code = File_Decomp_SWF( SessionPtr );
239 break;
240 }
241 #endif
242 #ifdef FILE_DECOMP_PDF
243 case( FILE_TYPE_PDF ):
244 {
245 Ret_Code = File_Decomp_PDF( SessionPtr );
246 break;
247 }
248 #endif
249 default:
250 return( File_Decomp_Error );
251 }
252
253 if( Ret_Code == File_Decomp_Complete )
254 SessionPtr->State = STATE_COMPLETE;
255
256 return( Ret_Code );
257 }
258
259 /* File_Decomp_OneTimeInit() is called at the preprocessor init to clear internal state */
File_Decomp_OneTimeInit()260 fd_status_t File_Decomp_OneTimeInit()
261 {
262 g_Config = NULL;
263
264 return( File_Decomp_OK );
265 }
266
267 /* File_Decomp_CleanExit() is called at the preprocessor 'clean exit' time to release resources */
File_Decomp_CleanExit()268 fd_status_t File_Decomp_CleanExit()
269 {
270 if( (g_Config != NULL) && (g_Config->fd_MemPool != NULL) && (mempool_destroy(g_Config->fd_MemPool) == 0))
271 {
272 SnortPreprocFree(g_Config->fd_MemPool, sizeof(MemPool), PP_HTTPINSPECT,
273 PP_MEM_CATEGORY_MEMPOOL);
274 g_Config->fd_MemPool = NULL;
275 g_Config = NULL;
276 return( File_Decomp_OK );
277 }
278 else
279 {
280 return( File_Decomp_Error );
281 }
282 }
283
284 /* The caller provides the memory size in the config struct. */
File_Decomp_Config(fd_config_p_t ConfigPtr)285 fd_status_t File_Decomp_Config( fd_config_p_t ConfigPtr )
286 {
287 unsigned Max_Sessions;
288
289 if( ConfigPtr == NULL )
290 return( File_Decomp_Error );
291
292 /* Our soul global, pointer to the config block */
293 g_Config = ConfigPtr;
294
295 Max_Sessions = ConfigPtr->Max_Memory / sizeof( fd_session_t );
296
297 g_Config->fd_MemPool = (MemPool *)SnortPreprocAlloc(1, sizeof(MemPool),
298 PP_HTTPINSPECT, PP_MEM_CATEGORY_MEMPOOL);
299 if( g_Config->fd_MemPool == NULL )
300 return( File_Decomp_Error );
301
302 if( (mempool_init(g_Config->fd_MemPool, Max_Sessions, sizeof( fd_session_t ))) != 0 )
303 return( File_Decomp_Error );
304
305 return( File_Decomp_OK );
306 }
307
308 /* The caller provides Compr_Depth, Decompr_Depth and Modes in the session object.
309 Based on the requested Modes, gear=up to initialize the potential decompressors. */
File_Decomp_Init(fd_session_p_t SessionPtr)310 fd_status_t File_Decomp_Init( fd_session_p_t SessionPtr )
311 {
312 int Sig;
313
314 if( SessionPtr == NULL )
315 return( File_Decomp_Error );
316
317 SessionPtr->State = STATE_READY;
318 SessionPtr->File_Type = FILE_TYPE_NONE;
319 SessionPtr->Decomp_Type = FILE_COMPRESSION_TYPE_NONE;
320
321 for( Sig=0; Signature_Map[Sig].Sig != NULL; Sig++ )
322 {
323 #ifdef FILE_DECOMP_PDF
324 if( (Signature_Map[Sig].File_Type == FILE_TYPE_PDF ) &&
325 ((SessionPtr->Modes & FILE_PDF_ANY) != 0) )
326 Signature_Map[Sig].Enabled = true;
327 #endif
328
329 #ifdef FILE_DECOMP_SWF
330 if( (Signature_Map[Sig].File_Type == FILE_TYPE_SWF ) &&
331 (Signature_Map[Sig].File_Compression_Type == FILE_COMPRESSION_TYPE_ZLIB) &&
332 ((SessionPtr->Modes & FILE_SWF_ZLIB_BIT) != 0) )
333 Signature_Map[Sig].Enabled = true;
334
335 #ifdef LZMA
336 if( (Signature_Map[Sig].File_Type == FILE_TYPE_SWF ) &&
337 (Signature_Map[Sig].File_Compression_Type == FILE_COMPRESSION_TYPE_LZMA) &&
338 ((SessionPtr->Modes & FILE_SWF_LZMA_BIT) != 0) )
339 Signature_Map[Sig].Enabled = true;
340 #endif
341 #endif
342
343 }
344
345 return( File_Decomp_OK );
346 }
347
348 /* Setup session to use internal decompression buffer. Set compr/decompr limits */
File_Decomp_SetBuf(fd_session_p_t SessionPtr)349 fd_status_t File_Decomp_SetBuf( fd_session_p_t SessionPtr )
350 {
351 if( SessionPtr == NULL )
352 return( File_Decomp_Error );
353
354 SessionPtr->Buffer = File_Decomp_Buffer;
355 SessionPtr->Buffer_Len = sizeof(File_Decomp_Buffer);
356
357 SessionPtr->Next_Out = File_Decomp_Buffer;
358 SessionPtr->Avail_Out = sizeof(File_Decomp_Buffer);
359
360 /* If Compr/Decompr limits are set, then enforce then. */
361 if( SessionPtr->Decompr_Depth > 0 )
362 {
363 uint32_t remainder;
364
365 if( SessionPtr->Total_Out > SessionPtr->Decompr_Depth )
366 return( File_Decomp_Error );
367
368 /* Calc whats left in allowance */
369 remainder = (SessionPtr->Total_Out - SessionPtr->Decompr_Depth);
370
371 /* Use smaller of remainder or value provided */
372 SessionPtr->Avail_Out = (remainder < SessionPtr->Avail_Out) ?
373 remainder : SessionPtr->Avail_Out;
374 }
375
376 if( SessionPtr->Compr_Depth > 0 )
377 {
378 uint32_t remainder;
379
380 if( SessionPtr->Total_In > SessionPtr->Compr_Depth )
381 return( File_Decomp_Error );
382
383 remainder = (SessionPtr->Total_In - SessionPtr->Compr_Depth);
384
385 SessionPtr->Avail_In = (remainder < SessionPtr->Avail_In) ?
386 remainder : SessionPtr->Avail_In;
387 }
388
389 /* SessionPtr->Next_In is set by the caller to File_Decomp() */
390
391 return( File_Decomp_OK );
392 }
393
394 /* Returns a new session object from the MemPool */
File_Decomp_New(void * scbPtr)395 fd_session_p_t File_Decomp_New(void* scbPtr)
396 {
397 fd_session_p_t New_Session;
398 MemBucket *bkt;
399
400 if( (g_Config == NULL) || (g_Config->fd_MemPool) == NULL )
401 return( NULL );
402
403 bkt = mempool_alloc(g_Config->fd_MemPool);
404
405 if( bkt == NULL )
406 {
407 mempool_free( g_Config->fd_MemPool, bkt );
408
409 return( (fd_session_p_t)NULL );
410 }
411 else
412 {
413 bkt->scbPtr = scbPtr;
414 New_Session = bkt->data;
415 New_Session->bkt = bkt;
416 New_Session->State = STATE_NEW;
417 New_Session->Sig_State = 0;
418 New_Session->Total_In = 0;
419 New_Session->Total_Out = 0;
420 New_Session->Avail_In = 0;
421 New_Session->Next_In = NULL;
422 New_Session->Avail_Out = 0;
423 New_Session->Next_Out = NULL;
424
425 return( New_Session );
426 }
427 }
428
429 /* Process Decompression. The session Next_In, Avail_In, Next_Out, Avail_Out MUST have been
430 set by caller.
431 */
File_Decomp(fd_session_p_t SessionPtr)432 fd_status_t File_Decomp( fd_session_p_t SessionPtr )
433 {
434 fd_status_t Return_Code;
435
436 if( (g_Config == NULL) || (g_Config->fd_MemPool == NULL) || (SessionPtr->State == STATE_NEW) ||
437 (SessionPtr->Next_In == NULL) || (SessionPtr->Next_Out == NULL) )
438 return( File_Decomp_Error );
439
440 /* STATE_NEW: Look for one of the configured file signatures. */
441 if( SessionPtr->State == STATE_READY )
442 {
443 /* Look for the signature at the beginning of the payload stream. */
444 if( (Return_Code = Locate_Sig_Here( SessionPtr )) == File_Decomp_OK )
445 {
446 /* We now know the file type and decompression type. Setup appropriate state. */
447 if( (Return_Code = Initialize_Decompression( SessionPtr )) == File_Decomp_OK )
448 {
449 return( Process_Decompression( SessionPtr ) );
450 }
451 else
452 return( Return_Code );
453 }
454 else
455 /* Locate_Sig_Here() might return BlockIn, BlockOut, Error, or NoSig */
456 return( Return_Code );
457 }
458 else if( SessionPtr->State == STATE_ACTIVE )
459 {
460 return( Process_Decompression( SessionPtr ) );
461 }
462 else
463 return( File_Decomp_Error );
464 }
465
466
File_Decomp_End(fd_session_p_t SessionPtr)467 fd_status_t File_Decomp_End( fd_session_p_t SessionPtr )
468 {
469 if( SessionPtr == NULL )
470 return( File_Decomp_Error );
471
472 switch( SessionPtr->File_Type )
473 {
474 #ifdef FILE_DECOMP_SWF
475 case( FILE_TYPE_SWF ):
476 {
477 return( File_Decomp_End_SWF( SessionPtr ) );
478 }
479 #endif
480 #ifdef FILE_DECOMP_PDF
481 case( FILE_TYPE_PDF ):
482 {
483 return( File_Decomp_End_PDF( SessionPtr ) );
484 }
485 #endif
486 default:
487 return( File_Decomp_Error );
488 }
489
490 return( File_Decomp_OK );
491 }
492
File_Decomp_Reset(fd_session_p_t SessionPtr)493 fd_status_t File_Decomp_Reset( fd_session_p_t SessionPtr )
494 {
495 fd_status_t Ret_Code;
496
497 if( SessionPtr == NULL )
498 return( File_Decomp_Error );
499
500 Ret_Code = File_Decomp_End( SessionPtr );
501
502 SessionPtr->State = STATE_READY;
503
504 return( Ret_Code );
505 }
506
File_Decomp_StopFree(fd_session_p_t SessionPtr)507 fd_status_t File_Decomp_StopFree( fd_session_p_t SessionPtr )
508 {
509 if( SessionPtr == NULL )
510 return( File_Decomp_Error );
511
512 File_Decomp_End( SessionPtr );
513 File_Decomp_Free( SessionPtr );
514
515 return( File_Decomp_OK );
516 }
517
518
File_Decomp_Free(fd_session_p_t SessionPtr)519 void File_Decomp_Free( fd_session_p_t SessionPtr )
520 {
521 mempool_free(g_Config->fd_MemPool, SessionPtr->bkt );
522 }
523
File_Decomp_Alert(fd_session_p_t SessionPtr,int Event)524 void File_Decomp_Alert( fd_session_p_t SessionPtr, int Event )
525 {
526 if( (SessionPtr != NULL) && (SessionPtr->Alert_Callback != NULL) && (SessionPtr->Alert_Context) )
527 (SessionPtr->Alert_Callback)(SessionPtr->Alert_Context, Event);
528 }
529