Browse code

Adds --max-scantime clamscan option and MaxScanTime clamd config option.

--max-scantime replaces the --timelimit clamscan option that had been experimental.
Default max-scantime set to 2 minutes (120000 milliseconds).

Micah Snyder (micasnyd) authored on 2019/08/17 09:18:59
Showing 14 changed files
... ...
@@ -48,6 +48,21 @@ changes.
48 48
     release for offline viewing in the `docs/html` directory.
49 49
   - The new home for the documentation markdown is in our
50 50
     [ClamAV FAQ Github repository](https://github.com/Cisco-Talos/clamav-faq)
51
+- To remediate future denial of service conditions caused by excessive scan times,
52
+  we introduced a scan time limit.
53
+  The default value is 2 minutes (120000 milliseconds).
54
+
55
+  To customize the time limit:
56
+
57
+  - use the `clamscan` `--max-scantime` option
58
+  - use the `clamd` `MaxScanTime` config option
59
+
60
+  Libclamav users may customize the time limit using the `cl_engine_set_num`
61
+  function. For example:
62
+
63
+  ```c
64
+      cl_engine_set_num(engine, CL_ENGINE_MAX_SCANTIME, time_limit_milliseconds)
65
+  ```
51 66
 
52 67
 ### Other improvements
53 68
 
... ...
@@ -750,6 +750,19 @@ int recvloop_th(int *socketds, unsigned nsockets, struct cl_engine *engine, unsi
750 750
     memset(&options, 0, sizeof(struct cl_scan_options));
751 751
 
752 752
     /* set up limits */
753
+    if ((opt = optget(opts, "MaxScanTime"))->active) {
754
+        if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANTIME, opt->numarg))) {
755
+            logg("!cl_engine_set_num(CL_ENGINE_MAX_SCANTIME) failed: %s\n", cl_strerror(ret));
756
+            cl_engine_free(engine);
757
+            return 1;
758
+        }
759
+    }
760
+    val = cl_engine_get_num(engine, CL_ENGINE_MAX_SCANTIME, NULL);
761
+    if (val)
762
+        logg("Limits: Global time limit set to %llu milliseconds.\n", val);
763
+    else
764
+        logg("^Limits: Global time limit protection disabled.\n");
765
+
753 766
     if ((opt = optget(opts, "MaxScanSize"))->active) {
754 767
         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANSIZE, opt->numarg))) {
755 768
             logg("!cl_engine_set_num(CL_ENGINE_MAX_SCANSIZE) failed: %s\n", cl_strerror(ret));
... ...
@@ -273,6 +273,7 @@ void help(void)
273 273
     mprintf("    --nocerts                            Disable authenticode certificate chain verification in PE files\n");
274 274
     mprintf("    --dumpcerts                          Dump authenticode certificate chain in PE files\n");
275 275
     mprintf("\n");
276
+    mprintf("    --max-scantime=#n                    Scan time longer than this will be skipped and assumed clean\n");
276 277
     mprintf("    --max-filesize=#n                    Files larger than this will be skipped and assumed clean\n");
277 278
     mprintf("    --max-scansize=#n                    The maximum amount of data to scan for each container file (**)\n");
278 279
     mprintf("    --max-files=#n                       The maximum number of files to scan for each container file (**)\n");
... ...
@@ -867,6 +867,24 @@ int scanmanager(const struct optstruct *opts)
867 867
 
868 868
     /* set limits */
869 869
 
870
+    /* TODO: Remove deprecated option in a future feature release */
871
+    if ((opt = optget(opts, "timelimit"))->active) {
872
+        if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANTIME, opt->numarg))) {
873
+            logg("!cli_engine_set_num(CL_ENGINE_MAX_SCANTIME) failed: %s\n", cl_strerror(ret));
874
+
875
+            cl_engine_free(engine);
876
+            return 2;
877
+        }
878
+    }
879
+    if ((opt = optget(opts, "max-scantime"))->active) {
880
+        if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANTIME, opt->numarg))) {
881
+            logg("!cli_engine_set_num(CL_ENGINE_MAX_SCANTIME) failed: %s\n", cl_strerror(ret));
882
+
883
+            cl_engine_free(engine);
884
+            return 2;
885
+        }
886
+    }
887
+
870 888
     if ((opt = optget(opts, "max-scansize"))->active) {
871 889
         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANSIZE, opt->numarg))) {
872 890
             logg("!cli_engine_set_num(CL_ENGINE_MAX_SCANSIZE) failed: %s\n", cl_strerror(ret));
... ...
@@ -988,15 +1006,6 @@ int scanmanager(const struct optstruct *opts)
988 988
         }
989 989
     }
990 990
 
991
-    if ((opt = optget(opts, "timelimit"))->active) {
992
-        if ((ret = cl_engine_set_num(engine, CL_ENGINE_TIME_LIMIT, opt->numarg))) {
993
-            logg("!cli_engine_set_num(CL_ENGINE_TIME_LIMIT) failed: %s\n", cl_strerror(ret));
994
-
995
-            cl_engine_free(engine);
996
-            return 2;
997
-        }
998
-    }
999
-
1000 991
     if ((opt = optget(opts, "pcre-max-filesize"))->active) {
1001 992
         if ((ret = cl_engine_set_num(engine, CL_ENGINE_PCRE_MAX_FILESIZE, opt->numarg))) {
1002 993
             logg("!cli_engine_set_num(CL_ENGINE_PCRE_MAX_FILESIZE) failed: %s\n", cl_strerror(ret));
... ...
@@ -85,7 +85,7 @@ Example
85 85
 # Default: no
86 86
 #OfficialDatabaseOnly no
87 87
 
88
-# The daemon can work in local mode, network mode or both. 
88
+# The daemon can work in local mode, network mode or both.
89 89
 # Due to security reasons we recommend the local mode.
90 90
 
91 91
 # Path to a local socket file the daemon will listen on.
... ...
@@ -231,7 +231,7 @@ Example
231 231
 #DetectPUA yes
232 232
 
233 233
 # Exclude a specific PUA category. This directive can be used multiple times.
234
-# See https://github.com/vrtadmin/clamav-faq/blob/master/faq/faq-pua.md for 
234
+# See https://github.com/vrtadmin/clamav-faq/blob/master/faq/faq-pua.md for
235 235
 # the complete list of PUA categories.
236 236
 # Default: Load all categories (if DetectPUA is activated)
237 237
 #ExcludePUA NetTool
... ...
@@ -271,9 +271,9 @@ Example
271 271
 # the end of a scan. If an archive contains both a heuristically detected
272 272
 # virus/phish, and a real malware, the real malware will be reported
273 273
 #
274
-# Keep this disabled if you intend to handle "*.Heuristics.*" viruses 
274
+# Keep this disabled if you intend to handle "*.Heuristics.*" viruses
275 275
 # differently from "real" malware.
276
-# If a non-heuristically-detected virus (signature-based) is found first, 
276
+# If a non-heuristically-detected virus (signature-based) is found first,
277 277
 # the scan is interrupted immediately, regardless of this config option.
278 278
 #
279 279
 # Default: no
... ...
@@ -475,6 +475,16 @@ Example
475 475
 # The options below protect your system against Denial of Service attacks
476 476
 # using archive bombs.
477 477
 
478
+# This option sets the maximum amount of time to a scan may take.
479
+# In this version, this field only affects the scan time of ZIP archives.
480
+# Value of 0 disables the limit
481
+# Note: disabling this limit or setting it too high may result allow scanning
482
+# of certain files to lock up the scanning process/threads resulting in a Denial
483
+# of Service.
484
+# Time is in milliseconds.
485
+# Default: 120000
486
+#MaxScanTime 300000
487
+
478 488
 # This option sets the maximum amount of data to be scanned for each input
479 489
 # file.
480 490
 # Archives and other containers are recursively extracted and scanned up to
... ...
@@ -619,13 +629,13 @@ Example
619 619
 #OnAccessMaxFileSize 10M
620 620
 
621 621
 # Max number of scanning threads to allocate to the OnAccess thread pool at startup.
622
-# These threads are the ones responsible for creating a connection with the daemon 
623
-# and kicking off scanning after an event has been processed. To prevent clamonacc 
622
+# These threads are the ones responsible for creating a connection with the daemon
623
+# and kicking off scanning after an event has been processed. To prevent clamonacc
624 624
 # from consuming all clamd's resources keep this lower than clamd's max threads.
625 625
 # Default: 5
626 626
 #OnAccessMaxThreads 10
627 627
 
628
-# Max amount of time (in milliseconds) that the OnAccess client should spend for every 
628
+# Max amount of time (in milliseconds) that the OnAccess client should spend for every
629 629
 # connect, send, and recieve attempt when communicating with clamd via curl.
630 630
 # Default: 5000 (5 seconds)
631 631
 # OnAccessCurlTimeout 10000
... ...
@@ -652,9 +662,9 @@ Example
652 652
 # Default: no
653 653
 #OnAccessPrevention yes
654 654
 
655
-# When using prevention, if this option is turned on, any errors that occur during 
656
-# scanning will result in the event attempt being denied. This could potentially 
657
-# lead to unwanted system behaviour with certain configurations, so the client defaults 
655
+# When using prevention, if this option is turned on, any errors that occur during
656
+# scanning will result in the event attempt being denied. This could potentially
657
+# lead to unwanted system behaviour with certain configurations, so the client defaults
658 658
 # this to off and prefers allowing access events in case of scan or connection error.
659 659
 # Default: no
660 660
 #OnAccessDenyOnError yes
... ...
@@ -667,9 +677,9 @@ Example
667 667
 
668 668
 # Set the  mount point to be scanned. The mount point specified, or the mount
669 669
 # point containing the specified directory will be watched. If any directories
670
-# are specified, this option will preempt (disable and ignore all options related to) 
670
+# are specified, this option will preempt (disable and ignore all options related to)
671 671
 # the DDD system. This option will result in verdicts only.
672
-# Note that prevention is explicitly disallowed to prevent common, fatal misconfigurations. (e.g. 
672
+# Note that prevention is explicitly disallowed to prevent common, fatal misconfigurations. (e.g.
673 673
 # watching "/" with prevention on and no exclusions made on vital system directories)
674 674
 # It can be used multiple times.
675 675
 # Default: disabled
... ...
@@ -702,14 +712,14 @@ Example
702 702
 # Default: disabled
703 703
 #OnAccessExcludeUID -1
704 704
 
705
-# This option allows exclusions via user names when using the on-access 
705
+# This option allows exclusions via user names when using the on-access
706 706
 # scanning client. It can be used multiple times.
707 707
 # It has the same potential race condition limitations of the OnAccessExcludeUID option.
708 708
 # Default: disabled
709 709
 #OnAccessExcludeUname clamav
710 710
 
711
-# Number of times the OnAccess client will retry a failed scan due to connection problems 
712
-# (or other issues). 
711
+# Number of times the OnAccess client will retry a failed scan due to connection problems
712
+# (or other issues).
713 713
 # Default: 0
714 714
 #OnAccessRetryAttempts 3
715 715
 
... ...
@@ -717,7 +727,7 @@ Example
717 717
 ## Bytecode
718 718
 ##
719 719
 
720
-# With this option enabled ClamAV will load bytecode from the database. 
720
+# With this option enabled ClamAV will load bytecode from the database.
721 721
 # It is highly recommended you keep this option on, otherwise you'll miss
722 722
 # detections for many new viruses.
723 723
 # Default: yes
... ...
@@ -741,7 +751,7 @@ Example
741 741
 #BytecodeSecurity TrustSigned
742 742
 
743 743
 # Set bytecode timeout in milliseconds.
744
-# 
744
+#
745 745
 # Default: 5000
746 746
 # BytecodeTimeout 1000
747 747
 
... ...
@@ -300,12 +300,12 @@ enum cl_engine_field {
300 300
     CL_ENGINE_MAX_PARTITIONS,      /* uint32_t */
301 301
     CL_ENGINE_MAX_ICONSPE,         /* uint32_t */
302 302
     CL_ENGINE_MAX_RECHWP3,         /* uint32_t */
303
-    CL_ENGINE_TIME_LIMIT,          /* uint32_t */
303
+    CL_ENGINE_MAX_SCANTIME,        /* uint32_t */
304 304
     CL_ENGINE_PCRE_MATCH_LIMIT,    /* uint64_t */
305 305
     CL_ENGINE_PCRE_RECMATCH_LIMIT, /* uint64_t */
306 306
     CL_ENGINE_PCRE_MAX_FILESIZE,   /* uint64_t */
307 307
     CL_ENGINE_DISABLE_PE_CERTS,    /* uint32_t */
308
-    CL_ENGINE_PE_DUMPCERTS         /* uint32_t */
308
+    CL_ENGINE_PE_DUMPCERTS,        /* uint32_t */
309 309
 };
310 310
 
311 311
 enum bytecode_security {
... ...
@@ -33,6 +33,7 @@
33 33
 
34 34
 #define CLI_DEFAULT_BM_OFFMODE_FSIZE 262144
35 35
 
36
+#define CLI_DEFAULT_TIMELIMIT     120000
36 37
 #define CLI_DEFAULT_MAXSCANSIZE   104857600
37 38
 #define CLI_DEFAULT_MAXFILESIZE   26214400
38 39
 #define CLI_DEFAULT_MAXRECLEVEL   16
... ...
@@ -679,6 +679,12 @@ cl_error_t cli_pcre_scanbuf(const unsigned char *buffer, uint32_t length, const
679 679
 
680 680
         /* if the global flag is set, loop through the scanning */
681 681
         do {
682
+            if (cli_checktimelimit(ctx) != CL_SUCCESS) {
683
+                cli_dbgmsg("cli_unzip: Time limit reached (max: %u)\n", ctx->engine->maxscantime);
684
+                ret = CL_ETIMEOUT;
685
+                break;
686
+            }
687
+
682 688
             /* reset the match results */
683 689
             if ((ret = cli_pcre_results_reset(&p_res, pd)) != CL_SUCCESS)
684 690
                 break;
... ...
@@ -257,7 +257,7 @@ const char *cl_strerror(int clerror)
257 257
         case CL_EMEM:
258 258
             return "Can't allocate memory";
259 259
         case CL_ETIMEOUT:
260
-            return "Time limit reached";
260
+            return "CL_ETIMEOUT: Time limit reached";
261 261
         /* internal (needed for debug messages) */
262 262
         case CL_EMAXREC:
263 263
             return "CL_EMAXREC";
... ...
@@ -321,6 +321,7 @@ struct cl_engine *cl_engine_new(void)
321 321
     }
322 322
 
323 323
     /* Setup default limits */
324
+    new->maxscantime   = CLI_DEFAULT_TIMELIMIT;
324 325
     new->maxscansize   = CLI_DEFAULT_MAXSCANSIZE;
325 326
     new->maxfilesize   = CLI_DEFAULT_MAXFILESIZE;
326 327
     new->maxreclevel   = CLI_DEFAULT_MAXRECLEVEL;
... ...
@@ -613,8 +614,8 @@ int cl_engine_set_num(struct cl_engine *engine, enum cl_engine_field field, long
613 613
         case CL_ENGINE_MAX_RECHWP3:
614 614
             engine->maxrechwp3 = (uint32_t)num;
615 615
             break;
616
-        case CL_ENGINE_TIME_LIMIT:
617
-            engine->time_limit = (uint32_t)num;
616
+        case CL_ENGINE_MAX_SCANTIME:
617
+            engine->maxscantime = (uint32_t)num;
618 618
             break;
619 619
         case CL_ENGINE_PCRE_MATCH_LIMIT:
620 620
             engine->pcre_match_limit = (uint64_t)num;
... ...
@@ -714,8 +715,8 @@ long long cl_engine_get_num(const struct cl_engine *engine, enum cl_engine_field
714 714
             return engine->maxiconspe;
715 715
         case CL_ENGINE_MAX_RECHWP3:
716 716
             return engine->maxrechwp3;
717
-        case CL_ENGINE_TIME_LIMIT:
718
-            return engine->time_limit;
717
+        case CL_ENGINE_MAX_SCANTIME:
718
+            return engine->maxscantime;
719 719
         case CL_ENGINE_PCRE_MATCH_LIMIT:
720 720
             return engine->pcre_match_limit;
721 721
         case CL_ENGINE_PCRE_RECMATCH_LIMIT:
... ...
@@ -795,6 +796,7 @@ struct cl_settings *cl_engine_settings_copy(const struct cl_engine *engine)
795 795
     settings->ac_maxdepth        = engine->ac_maxdepth;
796 796
     settings->tmpdir             = engine->tmpdir ? strdup(engine->tmpdir) : NULL;
797 797
     settings->keeptmp            = engine->keeptmp;
798
+    settings->maxscantime        = engine->maxscantime;
798 799
     settings->maxscansize        = engine->maxscansize;
799 800
     settings->maxfilesize        = engine->maxfilesize;
800 801
     settings->maxreclevel        = engine->maxreclevel;
... ...
@@ -849,6 +851,7 @@ int cl_engine_settings_apply(struct cl_engine *engine, const struct cl_settings
849 849
     engine->ac_mindepth        = settings->ac_mindepth;
850 850
     engine->ac_maxdepth        = settings->ac_maxdepth;
851 851
     engine->keeptmp            = settings->keeptmp;
852
+    engine->maxscantime        = settings->maxscantime;
852 853
     engine->maxscansize        = settings->maxscansize;
853 854
     engine->maxfilesize        = settings->maxfilesize;
854 855
     engine->maxreclevel        = settings->maxreclevel;
... ...
@@ -937,9 +940,9 @@ void cli_check_blockmax(cli_ctx *ctx, int rc)
937 937
     }
938 938
 }
939 939
 
940
-int cli_checklimits(const char *who, cli_ctx *ctx, unsigned long need1, unsigned long need2, unsigned long need3)
940
+cl_error_t cli_checklimits(const char *who, cli_ctx *ctx, unsigned long need1, unsigned long need2, unsigned long need3)
941 941
 {
942
-    int ret = CL_SUCCESS;
942
+    cl_error_t ret = CL_SUCCESS;
943 943
     unsigned long needed;
944 944
 
945 945
     /* if called without limits, go on, unpack, scan */
... ...
@@ -948,6 +951,9 @@ int cli_checklimits(const char *who, cli_ctx *ctx, unsigned long need1, unsigned
948 948
     needed = (need1 > need2) ? need1 : need2;
949 949
     needed = (needed > need3) ? needed : need3;
950 950
 
951
+    /* Enforce timelimit */
952
+    ret = cli_checktimelimit(ctx);
953
+
951 954
     /* if we have global scan limits */
952 955
     if (needed && ctx->engine->maxscansize) {
953 956
         /* if the remaining scansize is too small... */
... ...
@@ -976,9 +982,9 @@ int cli_checklimits(const char *who, cli_ctx *ctx, unsigned long need1, unsigned
976 976
     return ret;
977 977
 }
978 978
 
979
-int cli_updatelimits(cli_ctx *ctx, unsigned long needed)
979
+cl_error_t cli_updatelimits(cli_ctx *ctx, unsigned long needed)
980 980
 {
981
-    int ret = cli_checklimits("cli_updatelimits", ctx, needed, 0, 0);
981
+    cl_error_t ret = cli_checklimits("cli_updatelimits", ctx, needed, 0, 0);
982 982
 
983 983
     if (ret != CL_CLEAN) return ret;
984 984
     ctx->scannedfiles++;
... ...
@@ -988,18 +994,33 @@ int cli_updatelimits(cli_ctx *ctx, unsigned long needed)
988 988
     return CL_CLEAN;
989 989
 }
990 990
 
991
-int cli_checktimelimit(cli_ctx *ctx)
991
+/**
992
+ * @brief Check if we've exceeded the time limit.
993
+ * If ctx is NULL, there can be no timelimit so just return success.
994
+ *
995
+ * @param ctx         The scanning context.
996
+ * @return cl_error_t CL_SUCCESS if has not exceeded, CL_ETIMEOUT if has exceeded.
997
+ */
998
+cl_error_t cli_checktimelimit(cli_ctx *ctx)
992 999
 {
1000
+    cl_error_t ret = CL_SUCCESS;
1001
+
1002
+    if (NULL == ctx) {
1003
+        goto done;
1004
+    }
1005
+
993 1006
     if (ctx->time_limit.tv_sec != 0) {
994 1007
         struct timeval now;
995 1008
         if (gettimeofday(&now, NULL) == 0) {
996
-            if (now.tv_sec < ctx->time_limit.tv_sec)
997
-                return CL_SUCCESS;
998
-            if (now.tv_sec > ctx->time_limit.tv_sec || now.tv_usec > ctx->time_limit.tv_usec)
999
-                return CL_ETIMEOUT;
1009
+            if (now.tv_sec > ctx->time_limit.tv_sec)
1010
+                ret = CL_ETIMEOUT;
1011
+            else if (now.tv_sec == ctx->time_limit.tv_sec && now.tv_usec > ctx->time_limit.tv_usec)
1012
+                ret = CL_ETIMEOUT;
1000 1013
         }
1001 1014
     }
1002
-    return CL_SUCCESS;
1015
+
1016
+done:
1017
+    return ret;
1003 1018
 }
1004 1019
 
1005 1020
 /*
... ...
@@ -1103,7 +1124,7 @@ void cli_virus_found_cb(cli_ctx *ctx)
1103 1103
         ctx->engine->cb_virus_found(fmap_fd(*ctx->fmap), (const char *)*ctx->virname, ctx->cb_ctx);
1104 1104
 }
1105 1105
 
1106
-int cli_append_possibly_unwanted(cli_ctx *ctx, const char *virname)
1106
+cl_error_t cli_append_possibly_unwanted(cli_ctx *ctx, const char *virname)
1107 1107
 {
1108 1108
     if (SCAN_ALLMATCHES)
1109 1109
         return cli_append_virus(ctx, virname);
... ...
@@ -285,16 +285,17 @@ struct cl_engine {
285 285
     uint64_t engine_options;
286 286
 
287 287
     /* Limits */
288
+    uint32_t maxscantime; /* Time limit (in milliseconds) */
288 289
     uint64_t maxscansize; /* during the scanning of archives this size
289
-				     * will never be exceeded
290
-				     */
290
+				           * will never be exceeded
291
+				           */
291 292
     uint64_t maxfilesize; /* compressed files will only be decompressed
292
-				     * and scanned up to this size
293
-				     */
293
+				           * and scanned up to this size
294
+				           */
294 295
     uint32_t maxreclevel; /* maximum recursion level for archives */
295 296
     uint32_t maxfiles;    /* maximum number of files to be scanned
296
-				     * within a single archive
297
-				     */
297
+				           * within a single archive
298
+				           */
298 299
     /* This is for structured data detection.  You can set the minimum
299 300
      * number of occurrences of an CC# or SSN before the system will
300 301
      * generate a notification.
... ...
@@ -403,9 +404,6 @@ struct cl_engine {
403 403
     uint32_t maxiconspe; /* max number of icons to scan for PE */
404 404
     uint32_t maxrechwp3; /* max recursive calls for HWP3 parsing */
405 405
 
406
-    /* millisecond time limit for preclassification scanning */
407
-    uint32_t time_limit;
408
-
409 406
     /* PCRE matching limitations */
410 407
     uint64_t pcre_match_limit;
411 408
     uint64_t pcre_recmatch_limit;
... ...
@@ -427,6 +425,7 @@ struct cl_settings {
427 427
     uint32_t ac_maxdepth;
428 428
     char *tmpdir;
429 429
     uint32_t keeptmp;
430
+    uint32_t maxscantime;
430 431
     uint64_t maxscansize;
431 432
     uint64_t maxfilesize;
432 433
     uint32_t maxreclevel;
... ...
@@ -815,21 +814,20 @@ cl_error_t cli_gentempfd_with_prefix(const char *dir, char *prefix, char **name,
815 815
 
816 816
 unsigned int cli_rndnum(unsigned int max);
817 817
 int cli_filecopy(const char *src, const char *dest);
818
-int cli_mapscan(fmap_t *map, off_t offset, size_t size, cli_ctx *ctx, cli_file_t type);
819 818
 bitset_t *cli_bitset_init(void);
820 819
 void cli_bitset_free(bitset_t *bs);
821 820
 int cli_bitset_set(bitset_t *bs, unsigned long bit_offset);
822 821
 int cli_bitset_test(bitset_t *bs, unsigned long bit_offset);
823 822
 const char *cli_ctime(const time_t *timep, char *buf, const size_t bufsize);
824 823
 void cli_check_blockmax(cli_ctx *, int);
825
-int cli_checklimits(const char *, cli_ctx *, unsigned long, unsigned long, unsigned long);
826
-int cli_updatelimits(cli_ctx *, unsigned long);
824
+cl_error_t cli_checklimits(const char *, cli_ctx *, unsigned long, unsigned long, unsigned long);
825
+cl_error_t cli_updatelimits(cli_ctx *, unsigned long);
827 826
 unsigned long cli_getsizelimit(cli_ctx *, unsigned long);
828 827
 int cli_matchregex(const char *str, const char *regex);
829 828
 void cli_qsort(void *a, size_t n, size_t es, int (*cmp)(const void *, const void *));
830 829
 void cli_qsort_r(void *a, size_t n, size_t es, int (*cmp)(const void *, const void *, const void *), void *arg);
831
-int cli_checktimelimit(cli_ctx *ctx);
832
-int cli_append_possibly_unwanted(cli_ctx *ctx, const char *virname);
830
+cl_error_t cli_checktimelimit(cli_ctx *ctx);
831
+cl_error_t cli_append_possibly_unwanted(cli_ctx *ctx, const char *virname);
833 832
 
834 833
 /* symlink behaviour */
835 834
 #define CLI_FTW_FOLLOW_FILE_SYMLINK 0x01
... ...
@@ -601,7 +601,7 @@ done:
601 601
 
602 602
 static cl_error_t cli_scanegg(cli_ctx *ctx, size_t sfx_offset)
603 603
 {
604
-    cl_error_t status      = CL_EPARSE;
604
+    cl_error_t status  = CL_EPARSE;
605 605
     cl_error_t egg_ret = CL_EPARSE;
606 606
 
607 607
     char *buffer      = NULL;
... ...
@@ -616,7 +616,7 @@ static cl_error_t cli_scanegg(cli_ctx *ctx, size_t sfx_offset)
616 616
 
617 617
     void *hArchive = NULL;
618 618
 
619
-    char **comments     = NULL;
619
+    char **comments    = NULL;
620 620
     uint32_t nComments = 0;
621 621
 
622 622
     cl_egg_metadata metadata;
... ...
@@ -680,9 +680,9 @@ static cl_error_t cli_scanegg(cli_ctx *ctx, size_t sfx_offset)
680 680
             * Drop the comment to a temp file, if requested
681 681
             */
682 682
             if (ctx->engine->keeptmp) {
683
-                int comment_fd = -1;
683
+                int comment_fd   = -1;
684 684
                 size_t prefixLen = strlen("comments_") + 5;
685
-                char * prefix = (char*)malloc(prefixLen + 1);
685
+                char *prefix     = (char *)malloc(prefixLen + 1);
686 686
 
687 687
                 snprintf(prefix, prefixLen, "comments_%u", i);
688 688
                 prefix[prefixLen] = '\0';
... ...
@@ -2448,7 +2448,7 @@ static int cli_scanmail(cli_ctx *ctx)
2448 2448
 static int cli_scan_structured(cli_ctx *ctx)
2449 2449
 {
2450 2450
     char buf[8192];
2451
-    size_t result             = 0;
2451
+    size_t result          = 0;
2452 2452
     unsigned int cc_count  = 0;
2453 2453
     unsigned int ssn_count = 0;
2454 2454
     int done               = 0;
... ...
@@ -3244,7 +3244,7 @@ static int dispatch_prescan(clcb_pre_scan cb, cli_ctx *ctx, const char *filetype
3244 3244
 
3245 3245
 static int magic_scandesc(cli_ctx *ctx, cli_file_t type)
3246 3246
 {
3247
-    int ret            = CL_CLEAN;
3247
+    cl_error_t ret     = CL_CLEAN;
3248 3248
     cli_file_t dettype = 0;
3249 3249
     uint8_t typercg    = 1;
3250 3250
     size_t hashed_size;
... ...
@@ -3567,11 +3567,11 @@ static int magic_scandesc(cli_ctx *ctx, cli_file_t type)
3567 3567
                         break;
3568 3568
                     } else if (ret != CL_SUCCESS) {
3569 3569
                         /*
3570
-             * non-critical return => allow for the CL_TYPE_ZIP scan to occur
3571
-             * cli_process_ooxml other possible returns:
3572
-             *   CL_ETIMEOUT, CL_EMAXSIZE, CL_EMAXFILES, CL_EPARSE,
3573
-             *   CL_EFORMAT, CL_BREAK, CL_ESTAT
3574
-             */
3570
+                         * non-critical return => allow for the CL_TYPE_ZIP scan to occur
3571
+                         * cli_process_ooxml other possible returns:
3572
+                         *   CL_ETIMEOUT, CL_EMAXSIZE, CL_EMAXFILES, CL_EPARSE,
3573
+                         *   CL_EFORMAT, CL_BREAK, CL_ESTAT
3574
+                         */
3575 3575
                         ret = CL_SUCCESS;
3576 3576
                     }
3577 3577
                 }
... ...
@@ -3804,8 +3804,8 @@ static int magic_scandesc(cli_ctx *ctx, cli_file_t type)
3804 3804
         case CL_TYPE_TEXT_ASCII:
3805 3805
             if (SCAN_HEURISTIC_STRUCTURED && (DCONF_OTHER & OTHER_CONF_DLP))
3806 3806
                 /* TODO: consider calling this from cli_scanscript() for
3807
-         * a normalised text
3808
-         */
3807
+                 * a normalised text
3808
+                 */
3809 3809
 
3810 3810
                 ret = cli_scan_structured(ctx);
3811 3811
             break;
... ...
@@ -3845,7 +3845,6 @@ static int magic_scandesc(cli_ctx *ctx, cli_file_t type)
3845 3845
                 case CL_ETMPFILE:
3846 3846
                 case CL_ETMPDIR:
3847 3847
                 case CL_EMEM:
3848
-                case CL_ETIMEOUT:
3849 3848
                     cli_dbgmsg("Descriptor[%d]: cli_scanraw error %s\n", fmap_fd(*ctx->fmap), cl_strerror(res));
3850 3849
                     cli_bitset_free(ctx->hook_lsig_matches);
3851 3850
                     ctx->hook_lsig_matches = old_hook_lsig_matches;
... ...
@@ -3866,7 +3865,15 @@ static int magic_scandesc(cli_ctx *ctx, cli_file_t type)
3866 3866
                     cli_bitset_free(ctx->hook_lsig_matches);
3867 3867
                     ctx->hook_lsig_matches = old_hook_lsig_matches;
3868 3868
                     return magic_scandesc_cleanup(ctx, type, hash, hashed_size, cache_clean, ret, parent_property);
3869
-                /* "MAX" conditions should still fully scan the current file */
3869
+                /* The CL_ETIMEOUT "MAX" condition should set exceeds max flag and exit out quietly. */
3870
+                case CL_ETIMEOUT:
3871
+                    cli_check_blockmax(ctx, ret);
3872
+                    cli_bitset_free(ctx->hook_lsig_matches);
3873
+                    ctx->hook_lsig_matches = old_hook_lsig_matches;
3874
+                    cli_dbgmsg("Descriptor[%d]: Stopping after cli_scanraw reached %s\n",
3875
+                               fmap_fd(*ctx->fmap), cl_strerror(res));
3876
+                    return magic_scandesc_cleanup(ctx, type, hash, hashed_size, cache_clean, CL_CLEAN, parent_property);
3877
+                /* All other "MAX" conditions should still fully scan the current file */
3870 3878
                 case CL_EMAXREC:
3871 3879
                 case CL_EMAXSIZE:
3872 3880
                 case CL_EMAXFILES:
... ...
@@ -3875,9 +3882,9 @@ static int magic_scandesc(cli_ctx *ctx, cli_file_t type)
3875 3875
                                fmap_fd(*ctx->fmap), cl_strerror(res));
3876 3876
                     break;
3877 3877
                 /* Other errors must not block further scans below
3878
-         * This specifically includes CL_EFORMAT & CL_EREAD & CL_EUNPACK
3879
-         * Malformed/truncated files could report as any of these three.
3880
-         */
3878
+                 * This specifically includes CL_EFORMAT & CL_EREAD & CL_EUNPACK
3879
+                 * Malformed/truncated files could report as any of these three.
3880
+                 */
3881 3881
                 default:
3882 3882
                     ret = res;
3883 3883
                     cli_dbgmsg("Descriptor[%d]: Continuing after cli_scanraw error %s\n",
... ...
@@ -3889,7 +3896,7 @@ static int magic_scandesc(cli_ctx *ctx, cli_file_t type)
3889 3889
     ctx->recursion++;
3890 3890
     switch (type) {
3891 3891
         /* bytecode hooks triggered by a lsig must be a hook
3892
-     * called from one of the functions here */
3892
+         * called from one of the functions here */
3893 3893
         case CL_TYPE_TEXT_ASCII:
3894 3894
         case CL_TYPE_TEXT_UTF16BE:
3895 3895
         case CL_TYPE_TEXT_UTF16LE:
... ...
@@ -3903,8 +3910,8 @@ static int magic_scandesc(cli_ctx *ctx, cli_file_t type)
3903 3903
             perf_nested_stop(ctx, PERFT_SCRIPT, PERFT_SCAN);
3904 3904
             break;
3905 3905
         /* Due to performance reasons all executables were first scanned
3906
-     * in raw mode. Now we will try to unpack them
3907
-     */
3906
+         * in raw mode. Now we will try to unpack them
3907
+         */
3908 3908
         case CL_TYPE_MSEXE:
3909 3909
             perf_nested_start(ctx, PERFT_PE, PERFT_SCAN);
3910 3910
             if (SCAN_PARSE_PE && ctx->dconf->pe) {
... ...
@@ -3937,14 +3944,16 @@ static int magic_scandesc(cli_ctx *ctx, cli_file_t type)
3937 3937
     ctx->hook_lsig_matches = old_hook_lsig_matches;
3938 3938
 
3939 3939
     switch (ret) {
3940
-        /* Malformed file cases */
3941
-        case CL_EFORMAT:
3942
-        case CL_EREAD:
3943
-        case CL_EUNPACK:
3944 3940
         /* Limits exceeded */
3941
+        case CL_ETIMEOUT:
3945 3942
         case CL_EMAXREC:
3946 3943
         case CL_EMAXSIZE:
3947 3944
         case CL_EMAXFILES:
3945
+            cli_check_blockmax(ctx, ret);
3946
+        /* Malformed file cases */
3947
+        case CL_EFORMAT:
3948
+        case CL_EREAD:
3949
+        case CL_EUNPACK:
3948 3950
             cli_dbgmsg("Descriptor[%d]: %s\n", fmap_fd(*ctx->fmap), cl_strerror(ret));
3949 3951
 #if HAVE_JSON
3950 3952
             ctx->wrkproperty = parent_property;
... ...
@@ -4233,10 +4242,10 @@ static cl_error_t scan_common(int desc, cl_fmap_t *map, const char *filepath, co
4233 4233
     }
4234 4234
     perf_init(&ctx);
4235 4235
 
4236
-    if (ctx.options->general & CL_SCAN_GENERAL_COLLECT_METADATA && ctx.engine->time_limit != 0) {
4236
+    if (ctx.engine->maxscantime != 0) {
4237 4237
         if (gettimeofday(&ctx.time_limit, NULL) == 0) {
4238
-            uint32_t secs  = ctx.engine->time_limit / 1000;
4239
-            uint32_t usecs = (ctx.engine->time_limit % 1000) * 1000;
4238
+            uint32_t secs  = ctx.engine->maxscantime / 1000;
4239
+            uint32_t usecs = (ctx.engine->maxscantime % 1000) * 1000;
4240 4240
             ctx.time_limit.tv_sec += secs;
4241 4241
             ctx.time_limit.tv_usec += usecs;
4242 4242
             if (ctx.time_limit.tv_usec >= 1000000) {
... ...
@@ -4245,7 +4254,7 @@ static cl_error_t scan_common(int desc, cl_fmap_t *map, const char *filepath, co
4245 4245
             }
4246 4246
         } else {
4247 4247
             char buf[64];
4248
-            cli_dbgmsg("scan_common; gettimeofday error: %s\n", cli_strerror(errno, buf, 64));
4248
+            cli_dbgmsg("scan_common: gettimeofday error: %s\n", cli_strerror(errno, buf, 64));
4249 4249
         }
4250 4250
     }
4251 4251
 
... ...
@@ -4403,14 +4412,14 @@ int cli_found_possibly_unwanted(cli_ctx *ctx)
4403 4403
         cli_dbgmsg("found Possibly Unwanted: %s\n", cli_get_last_virus(ctx));
4404 4404
         if (SCAN_HEURISTIC_PRECEDENCE) {
4405 4405
             /* we found a heuristic match, don't scan further,
4406
-         * but consider it a virus. */
4406
+             * but consider it a virus. */
4407 4407
             cli_dbgmsg("cli_found_possibly_unwanted: CL_VIRUS\n");
4408 4408
             return CL_VIRUS;
4409 4409
         }
4410 4410
         /* heuristic scan isn't taking precedence, keep scanning.
4411
-     * If this is part of an archive, and
4412
-     * we find a real malware we report that instead of the
4413
-     * heuristic match */
4411
+         * If this is part of an archive, and
4412
+         * we find a real malware we report that instead of the
4413
+         * heuristic match */
4414 4414
         ctx->found_possibly_unwanted = 1;
4415 4415
     } else {
4416 4416
         cli_warnmsg("cli_found_possibly_unwanted called, but virname is not set\n");
... ...
@@ -930,6 +930,11 @@ cl_error_t cli_unzip(cli_ctx *ctx)
930 930
                 ret = CL_EMAXFILES;
931 931
             }
932 932
 
933
+            if (cli_checktimelimit(ctx) != CL_SUCCESS) {
934
+                cli_dbgmsg("cli_unzip: Time limit reached (max: %u)\n", ctx->engine->maxscantime);
935
+                ret = CL_ETIMEOUT;
936
+            }
937
+
933 938
             /*
934 939
              * Detect overlapping files and zip bombs.
935 940
              */
... ...
@@ -366,6 +366,8 @@ const struct clam_option __clam_options[] = {
366 366
 
367 367
     {"ForceToDisk", "force-to-disk", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option causes memory or nested map scans to dump the content to disk.\nIf you turn on this option, more data is written to disk and is available\nwhen the leave-temps option is enabled at the cost of more disk writes.", "no"},
368 368
 
369
+    {"MaxScanTime", "max-scantime", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, 0, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option sets the maximum amount of time a scan may take to complete.\nIn this version, this field only affects the scan time of ZIP archives.\nThe value of 0 disables the limit.\nWARNING: disabling this limit or setting it too high may result allow scanning\nof certain files to lock up the scanning process/threads resulting in a Denial of Service.\nThe value is in milliseconds.", "120000"},
370
+
369 371
     {"MaxScanSize", "max-scansize", 0, CLOPT_TYPE_SIZE, MATCH_SIZE, CLI_DEFAULT_MAXSCANSIZE, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option sets the maximum amount of data to be scanned for each input file.\nArchives and other containers are recursively extracted and scanned up to this\nvalue.\nThe value of 0 disables the limit.\nWARNING: disabling this limit or setting it too high may result in severe\ndamage.", "100M"},
370 372
 
371 373
     {"MaxFileSize", "max-filesize", 0, CLOPT_TYPE_SIZE, MATCH_SIZE, CLI_DEFAULT_MAXFILESIZE, NULL, 0, OPT_CLAMD | OPT_MILTER | OPT_CLAMSCAN, "Files/messages larger than this limit won't be scanned. Affects the input\nfile itself as well as files contained inside it (when the input file is\nan archive, a document or some other kind of container).\nThe value of 0 disables the limit.\nWARNING: disabling this limit or setting it too high may result in severe\ndamage to the system.", "25M"},
... ...
@@ -391,8 +393,6 @@ const struct clam_option __clam_options[] = {
391 391
 
392 392
     {"MaxRecHWP3", "max-rechwp3", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, CLI_DEFAULT_MAXRECHWP3, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option sets the maximum recursive calls to HWP3 parsing function.\nHWP3 files using more than this limit will be terminated and alert the user.\nScans will be unable to scan any HWP3 attachments if the recursive limit is reached.\nNegative values are not allowed.\nWARNING: setting this limit too high may result in severe damage or impact performance.", "16"},
393 393
 
394
-    {"TimeLimit", "timelimit", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, 0, NULL, 0, OPT_CLAMSCAN, "This clamscan option is currently for testing only. It sets the engine parameter CL_ENGINE_TIME_LIMIT. The value is in milliseconds.", "0"},
395
-
396 394
     {"PCREMatchLimit", "pcre-match-limit", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, CLI_DEFAULT_PCRE_MATCH_LIMIT, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option sets the maximum calls to the PCRE match function during an instance of regex matching.\nInstances using more than this limit will be terminated and alert the user but the scan will continue.\nFor more information on match_limit, see the PCRE documentation.\nNegative values are not allowed.\nWARNING: setting this limit too high may severely impact performance.", "100000"},
397 395
 
398 396
     {"PCRERecMatchLimit", "pcre-recmatch-limit", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, CLI_DEFAULT_PCRE_RECMATCH_LIMIT, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option sets the maximum recursive calls to the PCRE match function during an instance of regex matching.\nInstances using more than this limit will be terminated and alert the user but the scan will continue.\nFor more information on match_limit_recursion, see the PCRE documentation.\nNegative values are not allowed and values > PCREMatchLimit are superfluous.\nWARNING: setting this limit too high may severely impact performance.", "5000"},
... ...
@@ -506,6 +506,7 @@ const struct clam_option __clam_options[] = {
506 506
 
507 507
     /* Deprecated options */
508 508
 
509
+    {"TimeLimit", "timelimit", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, 0, NULL, 0, OPT_CLAMSCAN | OPT_DEPRECATED, "Deprecated option to set the max-scantime.\nThe value is in milliseconds.", "120000"},
509 510
     {"DetectBrokenExecutables", "detect-broken", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN | OPT_DEPRECATED, "Deprecated option to alert on broken PE and ELF executable files.", "no"},
510 511
     {"AlgorithmicDetection", "algorithmic-detection", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "Deprecated option to enable heuristic alerts (e.g. \"Heuristics.<sig name>\")", "no"},
511 512
     {"BlockMax", "block-max", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "", ""},
... ...
@@ -76,8 +76,8 @@ Example
76 76
 # Default: no
77 77
 #OfficialDatabaseOnly no
78 78
 
79
-# The daemon on Windows only supports unsecured TCP sockets. 
80
-# Due to security reasons make sure that your IP & port is not 
79
+# The daemon on Windows only supports unsecured TCP sockets.
80
+# Due to security reasons make sure that your IP & port is not
81 81
 # exposed to the open internet.
82 82
 
83 83
 # TCP port address.
... ...
@@ -203,7 +203,7 @@ TCPAddr 127.0.0.1
203 203
 #DetectPUA yes
204 204
 
205 205
 # Exclude a specific PUA category. This directive can be used multiple times.
206
-# See https://github.com/vrtadmin/clamav-faq/blob/master/faq/faq-pua.md for 
206
+# See https://github.com/vrtadmin/clamav-faq/blob/master/faq/faq-pua.md for
207 207
 # the complete list of PUA categories.
208 208
 # Default: Load all categories (if DetectPUA is activated)
209 209
 #ExcludePUA NetTool
... ...
@@ -243,9 +243,9 @@ TCPAddr 127.0.0.1
243 243
 # the end of a scan. If an archive contains both a heuristically detected
244 244
 # virus/phish, and a real malware, the real malware will be reported
245 245
 #
246
-# Keep this disabled if you intend to handle "*.Heuristics.*" viruses 
246
+# Keep this disabled if you intend to handle "*.Heuristics.*" viruses
247 247
 # differently from "real" malware.
248
-# If a non-heuristically-detected virus (signature-based) is found first, 
248
+# If a non-heuristically-detected virus (signature-based) is found first,
249 249
 # the scan is interrupted immediately, regardless of this config option.
250 250
 #
251 251
 # Default: no
... ...
@@ -446,6 +446,16 @@ TCPAddr 127.0.0.1
446 446
 # The options below protect your system against Denial of Service attacks
447 447
 # using archive bombs.
448 448
 
449
+# This option sets the maximum amount of time to a scan may take.
450
+# In this version, this field only affects the scan time of ZIP archives.
451
+# Value of 0 disables the limit
452
+# Note: disabling this limit or setting it too high may result allow scanning
453
+# of certain files to lock up the scanning process/threads resulting in a Denial
454
+# of Service.
455
+# Time is in milliseconds.
456
+# Default: 120000
457
+#MaxScanTime 300000
458
+
449 459
 # This option sets the maximum amount of data to be scanned for each input file.
450 460
 # Archives and other containers are recursively extracted and scanned up to this
451 461
 # value.
... ...
@@ -584,7 +594,7 @@ TCPAddr 127.0.0.1
584 584
 ## Bytecode
585 585
 ##
586 586
 
587
-# With this option enabled ClamAV will load bytecode from the database. 
587
+# With this option enabled ClamAV will load bytecode from the database.
588 588
 # It is highly recommended you keep this option on, otherwise you'll miss
589 589
 # detections for many new viruses.
590 590
 # Default: yes
... ...
@@ -608,7 +618,7 @@ TCPAddr 127.0.0.1
608 608
 #BytecodeSecurity TrustSigned
609 609
 
610 610
 # Set bytecode timeout in milliseconds.
611
-# 
611
+#
612 612
 # Default: 5000
613 613
 # BytecodeTimeout 1000
614 614