Browse code

Removing problematic call to convert file descriptors to filepaths. Added filename and tempfile names to scandesc calls in clamd. Added a general scan option to treat the scan engine as unprivileged, meaning that the scan engine will not have read access to the file. Added check to drop a temp file for RAR's where the we don't have read access to the filepath provided (i.e. unprivileged is set, or access() check fails).

Micah Snyder authored on 2019/02/08 05:03:43
Showing 6 changed files
... ...
@@ -394,7 +394,7 @@ int scanfd(
394 394
 	context.filename = fdstr;
395 395
 	context.virsize = 0;
396 396
 	context.scandata = NULL;
397
-	ret = cl_scandesc_callback(fd, NULL, &virname, scanned, engine, options, &context);
397
+	ret = cl_scandesc_callback(fd, conn->filename, &virname, scanned, engine, options, &context);
398 398
 	thrmgr_setactivetask(NULL, NULL);
399 399
 
400 400
 	if (thrmgr_group_need_terminate(conn->group)) {
... ...
@@ -568,7 +568,7 @@ int scanstream(
568 568
 		context.filename = peer_addr;
569 569
 		context.virsize = 0;
570 570
 		context.scandata = NULL;
571
-		ret = cl_scandesc_callback(tmpd, NULL, &virname, scanned, engine, options, &context);
571
+		ret = cl_scandesc_callback(tmpd, tmpname, &virname, scanned, engine, options, &context);
572 572
 		thrmgr_setactivetask(NULL, NULL);
573 573
     } else {
574 574
     	ret = -1;
... ...
@@ -157,10 +157,11 @@ struct cl_scan_options {
157 157
 };
158 158
 
159 159
 /* general */
160
-#define CL_SCAN_GENERAL_ALLMATCHES                  0x1 /* scan in all-match mode */
161
-#define CL_SCAN_GENERAL_COLLECT_METADATA            0x2 /* collect metadata (--gen-json) */
162
-#define CL_SCAN_GENERAL_HEURISTICS                  0x4 /* option to enable heuristic alerts */
163
-#define CL_SCAN_GENERAL_HEURISTIC_PRECEDENCE        0x8 /* allow heuristic match to take precedence. */
160
+#define CL_SCAN_GENERAL_ALLMATCHES                  0x1  /* scan in all-match mode */
161
+#define CL_SCAN_GENERAL_COLLECT_METADATA            0x2  /* collect metadata (--gen-json) */
162
+#define CL_SCAN_GENERAL_HEURISTICS                  0x4  /* option to enable heuristic alerts */
163
+#define CL_SCAN_GENERAL_HEURISTIC_PRECEDENCE        0x8  /* allow heuristic match to take precedence. */
164
+#define CL_SCAN_GENERAL_UNPRIVILEGED                0x10 /* scanner will not have read access to files. */
164 165
 
165 166
 /* parsing capabilities options */
166 167
 #define CL_SCAN_PARSE_ARCHIVE                       0x1
... ...
@@ -496,6 +496,7 @@ extern int have_rar;
496 496
 #define SCAN_COLLECT_METADATA                   (ctx->options->general & CL_SCAN_GENERAL_COLLECT_METADATA)
497 497
 #define SCAN_HEURISTICS                         (ctx->options->general & CL_SCAN_GENERAL_HEURISTICS)
498 498
 #define SCAN_HEURISTIC_PRECEDENCE               (ctx->options->general & CL_SCAN_GENERAL_HEURISTIC_PRECEDENCE)
499
+#define SCAN_UNPRIVILEGED                       (ctx->options->general & CL_SCAN_GENERAL_UNPRIVILEGED)
499 500
 
500 501
 #define SCAN_PARSE_ARCHIVE                      (ctx->options->parse & CL_SCAN_PARSE_ARCHIVE)
501 502
 #define SCAN_PARSE_ELF                          (ctx->options->parse & CL_SCAN_PARSE_ELF)
... ...
@@ -538,7 +539,7 @@ extern int have_rar;
538 538
 		     (((v) & 0x00ff000000000000ULL) >> 40) | \
539 539
 		     (((v) & 0xff00000000000000ULL) >> 56))
540 540
 
541
-#ifndef HAVE_ATTRIB_PACKED 
541
+#ifndef HAVE_ATTRIB_PACKED
542 542
 #define __attribute__(x)
543 543
 #endif
544 544
 #ifdef HAVE_PRAGMA_PACK
... ...
@@ -751,18 +752,18 @@ const char *cli_gettmpdir(void);
751 751
 
752 752
 /**
753 753
  * @brief Generate tempfile filename (no path) with a random MD5 hash.
754
- * 
754
+ *
755 755
  * Caller is responsible for freeing the filename.
756
- * 
756
+ *
757 757
  * @return char* filename or NULL.
758 758
  */
759 759
 char *cli_genfname(const char *prefix);
760 760
 
761 761
 /**
762 762
  * @brief Generate a full tempfile filepath with a random MD5 hash and prefix the name, if provided.
763
- * 
763
+ *
764 764
  * Caller is responsible for freeing the filename.
765
- * 
765
+ *
766 766
  * @param dir 	 Alternative temp directory. (optional)
767 767
  * @return char* filename or NULL.
768 768
  */
... ...
@@ -770,9 +771,9 @@ char* cli_gentemp_with_prefix(const char* dir, const char* prefix);
770 770
 
771 771
 /**
772 772
  * @brief Generate a full tempfile filepath with a random MD5 hash.
773
- * 
773
+ *
774 774
  * Caller is responsible for freeing the filename.
775
- * 
775
+ *
776 776
  * @param dir 	 Alternative temp directory. (optional)
777 777
  * @return char* filename or NULL.
778 778
  */
... ...
@@ -784,18 +785,18 @@ char *cli_gentemp(const char *dir);
784 784
  * @param dir        Alternative temp directory (optional).
785 785
  * @param[out] name  Allocated filepath, must be freed by caller.
786 786
  * @param[out] fd    File descriptor of open temp file.
787
- * @return cl_error_t CL_SUCCESS, CL_ECREAT, or CL_EMEM. 
787
+ * @return cl_error_t CL_SUCCESS, CL_ECREAT, or CL_EMEM.
788 788
  */
789 789
 cl_error_t cli_gentempfd(const char *dir, char **name, int *fd);
790 790
 
791 791
 /**
792 792
  * @brief Create a temp filename, create the file, open it, and pass back the filepath and open file descriptor.
793
- * 
793
+ *
794 794
  * @param dir        Alternative temp directory (optional).
795 795
  * @param prefix  	 (Optional) Prefix for new file tempfile.
796 796
  * @param[out] name  Allocated filepath, must be freed by caller.
797 797
  * @param[out] fd    File descriptor of open temp file.
798
- * @return cl_error_t CL_SUCCESS, CL_ECREAT, or CL_EMEM. 
798
+ * @return cl_error_t CL_SUCCESS, CL_ECREAT, or CL_EMEM.
799 799
  */
800 800
 cl_error_t cli_gentempfd_with_prefix(const char* dir, char* prefix, char** name, int* fd);
801 801
 
... ...
@@ -844,11 +845,11 @@ struct cli_ftw_cbdata {
844 844
     void *data;
845 845
 };
846 846
 
847
-/* 
847
+/*
848 848
  * return CL_BREAK to break out without an error, CL_SUCCESS to continue,
849 849
  * or any CL_E* to break out due to error.
850 850
  * The callback is responsible for freeing filename when it is done using it.
851
- * Note that callback decides if directory traversal should continue 
851
+ * Note that callback decides if directory traversal should continue
852 852
  * after an error, we call the callback with reason == error,
853 853
  * and if it returns CL_BREAK we break.
854 854
  */
... ...
@@ -861,7 +862,7 @@ typedef int (*cli_ftw_cb)(STATBUF *stat_buf, char *filename, const char *path, e
861 861
 typedef int (*cli_ftw_pathchk)(const char *path, struct cli_ftw_cbdata *data);
862 862
 
863 863
 /*
864
- * returns 
864
+ * returns
865 865
  *  CL_SUCCESS if it traversed all files and subdirs
866 866
  *  CL_BREAK if traversal has stopped at some point
867 867
  *  CL_E* if error encountered during traversal and we had to break out
... ...
@@ -880,10 +881,10 @@ const char *cli_strerror(int errnum, char* buf, size_t len);
880 880
 
881 881
 /**
882 882
  * @brief   Attempt to get a filename from an open file descriptor.
883
- * 
883
+ *
884 884
  * Caller is responsible for free'ing the filename.
885 885
  * Should work on Linux, macOS, Windows.
886
- * 
886
+ *
887 887
  * @param desc           File descriptor
888 888
  * @param[out] filepath  Will be set to file path if found, or NULL.
889 889
  * @return cl_error_t    CL_SUCCESS if found, else an error code.
... ...
@@ -223,16 +223,15 @@ static int cli_scandir(const char *dirname, cli_ctx *ctx)
223 223
 
224 224
 /**
225 225
  * @brief  Scan the metadata using cli_matchmeta()
226
- * 
226
+ *
227 227
  * @param metadata  unrar metadata structure
228 228
  * @param ctx       scanning context structure
229
- * @param files     
229
+ * @param files
230 230
  * @return cl_error_t  Returns CL_CLEAN if nothing found, CL_VIRUS if something found, CL_EUNPACK if encrypted.
231 231
  */
232 232
 static cl_error_t cli_unrar_scanmetadata(unrar_metadata_t* metadata, cli_ctx* ctx, unsigned int files)
233 233
 {
234 234
     cl_error_t status = CL_CLEAN;
235
-    int virus_found = 0;
236 235
 
237 236
     cli_dbgmsg("RAR: %s, crc32: 0x%x, encrypted: %u, compressed: %u, normal: %u, method: %u, ratio: %u\n",
238 237
         metadata->filename, metadata->crc, metadata->encrypted, (unsigned int)metadata->pack_size,
... ...
@@ -246,8 +245,6 @@ static cl_error_t cli_unrar_scanmetadata(unrar_metadata_t* metadata, cli_ctx* ct
246 246
         status = CL_EUNPACK;
247 247
     }
248 248
 
249
-done:
250
-
251 249
     return status;
252 250
 }
253 251
 
... ...
@@ -255,9 +252,8 @@ static cl_error_t cli_scanrar(const char *filepath, int desc, cli_ctx* ctx)
255 255
 {
256 256
     cl_error_t status = CL_EPARSE;
257 257
     cl_unrar_error_t unrar_ret = UNRAR_ERR;
258
-    
258
+
259 259
     char* extract_dir = NULL; /* temp dir to write extracted files to */
260
-    char* comment_dir = NULL; /* temp dir to write file comments to */
261 260
     unsigned int file_count = 0;
262 261
     unsigned int viruses_found = 0;
263 262
 
... ...
@@ -283,7 +279,7 @@ static cl_error_t cli_scanrar(const char *filepath, int desc, cli_ctx* ctx)
283 283
 
284 284
     /* Zero out the metadata struct before we read the header */
285 285
     memset(&metadata, 0, sizeof(unrar_metadata_t));
286
-    
286
+
287 287
     /* Determine file basename */
288 288
     if (CL_SUCCESS != cli_basename(filepath, strlen(filepath), &filename_base)) {
289 289
         status = CL_EARG;
... ...
@@ -313,6 +309,9 @@ static cl_error_t cli_scanrar(const char *filepath, int desc, cli_ctx* ctx)
313 313
         if (unrar_ret == UNRAR_EMEM) {
314 314
             status = CL_EMEM;
315 315
             goto done;
316
+        } else if (unrar_ret == UNRAR_EOPEN) {
317
+            status = CL_EOPEN;
318
+            goto done;
316 319
         } else {
317 320
             status = CL_EFORMAT;
318 321
             goto done;
... ...
@@ -360,7 +359,7 @@ static cl_error_t cli_scanrar(const char *filepath, int desc, cli_ctx* ctx)
360 360
     /*
361 361
      * Read & scan each file header.
362 362
      * Extract & scan each file.
363
-     * 
363
+     *
364 364
      * Skip files if they will exceed max filesize or max scansize.
365 365
      * Count the number of encrypted file headers and encrypted files.
366 366
      *  - Alert if there are encrypted files,
... ...
@@ -416,7 +415,7 @@ static cl_error_t cli_scanrar(const char *filepath, int desc, cli_ctx* ctx)
416 416
             /* Check if we've already exceeded the scan limit */
417 417
             if (cli_checklimits("RAR", ctx, 0, 0, 0))
418 418
                 break;
419
-            
419
+
420 420
             if (metadata.is_dir) {
421 421
                 /* Entry is a directory. Skip. */
422 422
                 cli_dbgmsg("RAR: Found directory. Skipping to next file.\n");
... ...
@@ -427,7 +426,7 @@ static cl_error_t cli_scanrar(const char *filepath, int desc, cli_ctx* ctx)
427 427
                     break;
428 428
                 }
429 429
             } else if (cli_checklimits("RAR", ctx, metadata.unpack_size, 0, 0)) {
430
-                /* File size exceeds maxfilesize, must skip extraction. 
430
+                /* File size exceeds maxfilesize, must skip extraction.
431 431
                 * Although we may be able to scan the metadata */
432 432
                 nTooLargeFilesFound += 1;
433 433
 
... ...
@@ -462,12 +461,12 @@ static cl_error_t cli_scanrar(const char *filepath, int desc, cli_ctx* ctx)
462 462
 
463 463
                 unrar_ret = cli_unrar_extract_file(hArchive, extract_fullpath, NULL);
464 464
                 if (unrar_ret != UNRAR_OK) {
465
-                    /* 
465
+                    /*
466 466
                      * Some other error extracting the file
467 467
                      */
468 468
                     cli_dbgmsg("RAR: Error extracting file: %s\n", metadata.filename);
469 469
 
470
-                    /* TODO: 
470
+                    /* TODO:
471 471
                      *   may need to manually skip the file depending on what, specifically, cli_unrar_extract_file() returned.
472 472
                      */
473 473
                 } else {
... ...
@@ -2580,7 +2579,6 @@ static int cli_scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_file_
2580 2580
 {
2581 2581
     int ret = CL_CLEAN, nret = CL_CLEAN;
2582 2582
     struct cli_matched_type *ftoffset = NULL, *fpt;
2583
-    uint32_t lastrar;
2584 2583
     struct cli_exe_info peinfo;
2585 2584
     unsigned int acmode = AC_SCAN_VIR, break_loop = 0;
2586 2585
     fmap_t *map = *ctx->fmap;
... ...
@@ -2660,7 +2658,11 @@ static int cli_scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_file_
2660 2660
                         cli_set_container(ctx, CL_TYPE_RAR, csize);
2661 2661
                         cli_dbgmsg("RAR/RAR-SFX signature found at %u\n", (unsigned int)fpt->offset);
2662 2662
 
2663
-                        if ((ctx->sub_filepath == NULL) || (fpt->offset != 0)) {
2663
+#ifdef _WIN32
2664
+                        if ((fpt->offset != 0) || (SCAN_UNPRIVILEGED)|| (NULL == ctx->sub_filepath) || (0 != _access_s(ctx->sub_filepath, R_OK))) {
2665
+#else
2666
+                        if ((fpt->offset != 0) || (SCAN_UNPRIVILEGED) || (NULL == ctx->sub_filepath) || (0 != access(ctx->sub_filepath, R_OK))) {
2667
+#endif
2664 2668
                             /*
2665 2669
                              * If map is not file-backed, or offset is not at the start of the file...
2666 2670
                              * ...have to dump to file for scanrar.
... ...
@@ -2683,6 +2685,25 @@ static int cli_scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_file_
2683 2683
                         /* scan file */
2684 2684
                         nret = cli_scanrar(filepath, fd, ctx);
2685 2685
 
2686
+                        if ((NULL == tmpname) && (CL_EOPEN == nret)) {
2687
+                            /*
2688
+                             * Failed to open the file using the original filename.
2689
+                             * Try writing the file descriptor to a temp file and try again.
2690
+                             */
2691
+                            nret = fmap_dump_to_file(map, ctx->sub_filepath, ctx->engine->tmpdir, &tmpname, &tmpfd, fpt->offset, fpt->offset + csize);
2692
+                            if (nret != CL_SUCCESS) {
2693
+                                cli_dbgmsg("cli_scanraw: failed to generate temporary file.\n");
2694
+                                ret = nret;
2695
+                                break_loop = 1;
2696
+                                break;
2697
+                            }
2698
+                            filepath = tmpname;
2699
+                            fd = tmpfd;
2700
+
2701
+                            /* try to scan again */
2702
+                            nret = cli_scanrar(filepath, fd, ctx);
2703
+                        }
2704
+
2686 2705
                         if (tmpfd != -1)
2687 2706
                         {
2688 2707
                             /* If dumped tempfile, need to cleanup */
... ...
@@ -3348,7 +3369,11 @@ static int magic_scandesc(cli_ctx *ctx, cli_file_t type)
3348 3348
             char *tmpname = NULL;
3349 3349
             int tmpfd = -1;
3350 3350
 
3351
-            if (ctx->sub_filepath == NULL) {
3351
+#ifdef _WIN32
3352
+            if ((SCAN_UNPRIVILEGED) || (NULL == ctx->sub_filepath) || (0 != _access_s(ctx->sub_filepath, R_OK))) {
3353
+#else
3354
+            if ((SCAN_UNPRIVILEGED) || (NULL == ctx->sub_filepath) || (0 != access(ctx->sub_filepath, R_OK))) {
3355
+#endif
3352 3356
                 /* If map is not file-backed have to dump to file for scanrar. */
3353 3357
                 ret = fmap_dump_to_file(*ctx->fmap, ctx->sub_filepath, ctx->engine->tmpdir, &tmpname, &tmpfd, 0, SIZE_MAX);
3354 3358
                 if (ret != CL_SUCCESS) {
... ...
@@ -3366,6 +3391,23 @@ static int magic_scandesc(cli_ctx *ctx, cli_file_t type)
3366 3366
             /* scan file */
3367 3367
             ret = cli_scanrar(filepath, fd, ctx);
3368 3368
 
3369
+            if ((NULL == tmpname) && (CL_EOPEN == ret)) {
3370
+                /*
3371
+                 * Failed to open the file using the original filename.
3372
+                 * Try writing the file descriptor to a temp file and try again.
3373
+                 */
3374
+                ret = fmap_dump_to_file(*ctx->fmap, ctx->sub_filepath, ctx->engine->tmpdir, &tmpname, &tmpfd, 0, SIZE_MAX);
3375
+                if (ret != CL_SUCCESS) {
3376
+                    cli_dbgmsg("cli_scanraw: failed to generate temporary file.\n");
3377
+                    break;
3378
+                }
3379
+                filepath = tmpname;
3380
+                fd = tmpfd;
3381
+
3382
+                /* try to scan again */
3383
+                ret = cli_scanrar(filepath, fd, ctx);
3384
+            }
3385
+
3369 3386
             if (tmpfd != -1) {
3370 3387
                 /* If dumped tempfile, need to cleanup */
3371 3388
                 close(tmpfd);
... ...
@@ -4015,8 +4057,8 @@ int cli_mem_scandesc(const void *buffer, size_t length, cli_ctx *ctx)
4015 4015
 }
4016 4016
 
4017 4017
 /**
4018
- * @brief   The main function to initiate a scan, that may be invoked with a file descriptor or a file map. 
4019
- * 
4018
+ * @brief   The main function to initiate a scan, that may be invoked with a file descriptor or a file map.
4019
+ *
4020 4020
  * @param desc              File descriptor of an open file. The caller must provide this or the map.
4021 4021
  * @param map               File map. The caller must provide this or the desc.
4022 4022
  * @param filepath          (optional, recommended) filepath of the open file descriptor or file map.
... ...
@@ -4092,23 +4134,13 @@ static cl_error_t scan_common(int desc, cl_fmap_t *map, const char * filepath, c
4092 4092
         }
4093 4093
     }
4094 4094
 
4095
-    /* Best effort to determine the filename if not provided. 
4096
-     * May still be NULL if filename could not be determined. */
4097
-    if (filepath == NULL)
4095
+    if (filepath != NULL)
4098 4096
     {
4099
-        char *fpath = NULL;
4100
-
4101
-        if (desc >= 0) {
4102
-            (void) cli_get_filepath_from_filedesc(desc, &fpath);
4103
-        }
4104
-
4105
-        ctx.target_filepath = fpath;
4106
-    } else {
4107 4097
         ctx.target_filepath = strdup(filepath);
4108 4098
     }
4109 4099
 
4110 4100
     cli_logg_setup(&ctx);
4111
-    rc = map ? cli_map_scandesc(map, 0, map->len, &ctx, CL_TYPE_ANY) 
4101
+    rc = map ? cli_map_scandesc(map, 0, map->len, &ctx, CL_TYPE_ANY)
4112 4102
              : cli_magic_scandesc(desc, ctx.target_filepath, &ctx);
4113 4103
 
4114 4104
 #if HAVE_JSON
... ...
@@ -4237,8 +4269,8 @@ static cl_error_t scan_common(int desc, cl_fmap_t *map, const char * filepath, c
4237 4237
     if (rc == CL_CLEAN)
4238 4238
     {
4239 4239
         if ((ctx.found_possibly_unwanted) ||
4240
-            ((ctx.num_viruses != 0) && 
4241
-               ((ctx.options->general & CL_SCAN_GENERAL_ALLMATCHES) || 
4240
+            ((ctx.num_viruses != 0) &&
4241
+               ((ctx.options->general & CL_SCAN_GENERAL_ALLMATCHES) ||
4242 4242
                 (ctx.options->heuristic & CL_SCAN_HEURISTIC_EXCEEDS_MAX))
4243 4243
             ))
4244 4244
             rc = CL_VIRUS;
... ...
@@ -4279,8 +4311,8 @@ int cli_found_possibly_unwanted(cli_ctx *ctx)
4279 4279
             return CL_VIRUS;
4280 4280
         }
4281 4281
         /* heuristic scan isn't taking precedence, keep scanning.
4282
-     * If this is part of an archive, and 
4283
-     * we find a real malware we report that instead of the 
4282
+     * If this is part of an archive, and
4283
+     * we find a real malware we report that instead of the
4284 4284
      * heuristic match */
4285 4285
         ctx->found_possibly_unwanted = 1;
4286 4286
     }
... ...
@@ -89,7 +89,7 @@ uint8_t unrar_debug = 0;
89 89
 
90 90
 /**
91 91
  * @brief  Translate an ERAR_<code> to the appropriate UNRAR_<code>
92
- * 
92
+ *
93 93
  * @param errorCode ERAR_<code>
94 94
  * @return cl_unrar_error_t UNRAR_OK, UNRAR_ENCRYPTED, or UNRAR_ERR.
95 95
  */
... ...
@@ -133,6 +133,7 @@ static cl_unrar_error_t unrar_retcode(int retcode)
133 133
     }
134 134
     case ERAR_EOPEN: {
135 135
         unrar_dbgmsg("unrar_retcode: Volume open error.\n");
136
+        status = UNRAR_EOPEN;
136 137
         break;
137 138
     }
138 139
     case ERAR_ECREATE: {
... ...
@@ -289,7 +290,7 @@ done:
289 289
 
290 290
 /**
291 291
  * @brief  Get file metadata from the next file header.
292
- * 
292
+ *
293 293
  * @param hArchive              Handle to the archive we're extracting.
294 294
  * @param[in/out] file_metadata Pointer to a pre-allocated metadata structure.
295 295
  * @return cl_unrar_error_t     UNRAR_OK if metadata retrieved, UNRAR_BREAK if no more files, UNRAR_ENCRYPTED if header was encrypted, else maybe UNRAR_EMEM or UNRAR_ERR.
... ...
@@ -317,7 +318,7 @@ cl_unrar_error_t unrar_peek_file_header(void* hArchive, unrar_metadata_t* file_m
317 317
      */
318 318
     headerData.CmtBuf = NULL;
319 319
     headerData.CmtBufSize = 0;
320
-    
320
+
321 321
     headerData.RedirNameSize = 1024 * sizeof(wchar_t);
322 322
     headerData.RedirName = (wchar_t*)&RedirName;
323 323
     memset(headerData.RedirName, 0, headerData.RedirNameSize);
... ...
@@ -48,7 +48,8 @@ typedef enum cl_unrar_error_tag {
48 48
     UNRAR_BREAK,
49 49
     UNRAR_ENCRYPTED,
50 50
     UNRAR_EMEM,
51
-    UNRAR_ERR
51
+    UNRAR_ERR,
52
+    UNRAR_EOPEN
52 53
 } cl_unrar_error_t;
53 54
 
54 55
 typedef struct unrar_metadata_tag