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