Browse code

Make .crb sigs load before .cat files in loaddbdir

.crb rules are needed to validate .cat files before they get loaded
in, but when running clamscan with '-d <dir>' there wasn't any logic
to ensure that .cat files got loaded after the .crb files. This
commit changes that, and refactors the code a bit to make it easier
to add new ordering requirements and to make error handling cleaner.

Andrew authored on 2019/03/04 01:38:01
Showing 1 changed files
... ...
@@ -4474,9 +4474,40 @@ int cli_load(const char *filename, struct cl_engine *engine, unsigned int *signo
4474 4474
     return ret;
4475 4475
 }
4476 4476
 
4477
+struct db_ll_entry {
4478
+    char *path;
4479
+    unsigned int load_priority;
4480
+    struct db_ll_entry *next;
4481
+};
4482
+
4483
+static void
4484
+cli_insertdbtoll(struct db_ll_entry **head, struct db_ll_entry *entry)
4485
+{
4486
+    struct db_ll_entry *iter, *prev;
4487
+    if (NULL == *head) {
4488
+        *head       = entry;
4489
+        entry->next = NULL;
4490
+        return;
4491
+    }
4492
+    for (prev = NULL, iter = *head; iter != NULL; prev = iter, iter = iter->next) {
4493
+        if (entry->load_priority < iter->load_priority) {
4494
+            if (NULL == prev) {
4495
+                *head = entry;
4496
+            } else {
4497
+                prev->next = entry;
4498
+            }
4499
+            entry->next = iter;
4500
+            return;
4501
+        }
4502
+    }
4503
+    prev->next  = entry;
4504
+    entry->next = NULL;
4505
+    return;
4506
+}
4507
+
4477 4508
 static int cli_loaddbdir(const char *dirname, struct cl_engine *engine, unsigned int *signo, unsigned int options)
4478 4509
 {
4479
-    DIR *dd;
4510
+    DIR *dd = NULL;
4480 4511
     struct dirent *dent;
4481 4512
 #if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2)
4482 4513
     union {
... ...
@@ -4484,16 +4515,21 @@ static int cli_loaddbdir(const char *dirname, struct cl_engine *engine, unsigned
4484 4484
         char b[offsetof(struct dirent, d_name) + NAME_MAX + 1];
4485 4485
     } result;
4486 4486
 #endif
4487
-    char *dbfile;
4488
-    int ret = CL_EOPEN, have_cld, ends_with_sep = 0;
4487
+    char *dbfile = NULL;
4488
+    int ret = CL_EOPEN, have_daily_cld = 0, have_daily_cvd = 0, ends_with_sep = 0;
4489 4489
     size_t dirname_len;
4490
-    struct cl_cvd *daily_cld, *daily_cvd;
4490
+    struct cl_cvd *daily_cld = NULL;
4491
+    struct cl_cvd *daily_cvd = NULL;
4492
+    struct db_ll_entry *head = NULL;
4493
+    struct db_ll_entry *iter;
4494
+    struct db_ll_entry *next;
4491 4495
 
4492 4496
     cli_dbgmsg("Loading databases from %s\n", dirname);
4493 4497
 
4494 4498
     if ((dd = opendir(dirname)) == NULL) {
4495 4499
         cli_errmsg("cli_loaddbdir(): Can't open directory %s\n", dirname);
4496
-        return CL_EOPEN;
4500
+        ret = CL_EOPEN;
4501
+        goto cleanup;
4497 4502
     }
4498 4503
 
4499 4504
     dirname_len = strlen(dirname);
... ...
@@ -4504,7 +4540,6 @@ static int cli_loaddbdir(const char *dirname, struct cl_engine *engine, unsigned
4504 4504
         }
4505 4505
     }
4506 4506
 
4507
-    /* first round - load .ign and .ign2 files */
4508 4507
 #ifdef HAVE_READDIR_R_3
4509 4508
     while (!readdir_r(dd, &result.d, &dent) && dent) {
4510 4509
 #elif defined(HAVE_READDIR_R_2)
... ...
@@ -4512,159 +4547,156 @@ static int cli_loaddbdir(const char *dirname, struct cl_engine *engine, unsigned
4512 4512
 #else
4513 4513
     while ((dent = readdir(dd))) {
4514 4514
 #endif
4515
-        if (dent->d_ino) {
4516
-            if (cli_strbcasestr(dent->d_name, ".ign") || cli_strbcasestr(dent->d_name, ".ign2")) {
4517
-                dbfile = (char *)cli_malloc(strlen(dent->d_name) + dirname_len + 2);
4518
-                if (!dbfile) {
4519
-                    cli_errmsg("cli_loaddbdir(): dbfile == NULL\n");
4520
-                    closedir(dd);
4521
-                    return CL_EMEM;
4515
+        struct db_ll_entry *entry;
4516
+        unsigned int load_priority;
4517
+
4518
+        if (!dent->d_ino) {
4519
+            continue;
4520
+        }
4521
+        if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) {
4522
+            continue;
4523
+        }
4524
+        if (!CLI_DBEXT(dent->d_name)) {
4525
+            continue;
4526
+        }
4527
+
4528
+        dbfile = (char *)cli_malloc(strlen(dent->d_name) + dirname_len + 2);
4529
+        if (!dbfile) {
4530
+            cli_errmsg("cli_loaddbdir(): dbfile == NULL\n");
4531
+            ret = CL_EMEM;
4532
+            goto cleanup;
4533
+        }
4534
+        if (ends_with_sep)
4535
+            sprintf(dbfile, "%s%s", dirname, dent->d_name);
4536
+        else
4537
+            sprintf(dbfile, "%s" PATHSEP "%s", dirname, dent->d_name);
4538
+
4539
+#define DB_LOAD_PRIORITY_IGN 1
4540
+#define DB_LOAD_PRIORITY_DAILY_CLD 2
4541
+#define DB_LOAD_PRIORITY_DAILY_CVD 3
4542
+#define DB_LOAD_PRIORITY_LOCAL_GDB 4
4543
+#define DB_LOAD_PRIORITY_DAILY_CFG 5
4544
+#define DB_LOAD_PRIORITY_CRB 6
4545
+#define DB_LOAD_PRIORITY_NORMAL 7
4546
+
4547
+        if (cli_strbcasestr(dent->d_name, ".ign") || cli_strbcasestr(dent->d_name, ".ign2")) {
4548
+            /* load .ign and .ign2 files first */
4549
+            load_priority = DB_LOAD_PRIORITY_IGN;
4550
+
4551
+        } else if (!strcmp(dent->d_name, "daily.cld")) {
4552
+            /* the daily db must be loaded before main */
4553
+            load_priority = DB_LOAD_PRIORITY_DAILY_CLD;
4554
+
4555
+            have_daily_cld = !access(dbfile, R_OK);
4556
+            if (have_daily_cld) {
4557
+                daily_cld = cl_cvdhead(dbfile);
4558
+                if (!daily_cld) {
4559
+                    cli_errmsg("cli_loaddbdir(): error parsing header of %s\n", dbfile);
4560
+                    ret = CL_EMALFDB;
4561
+                    goto cleanup;
4522 4562
                 }
4523
-                if (ends_with_sep)
4524
-                    sprintf(dbfile, "%s%s", dirname, dent->d_name);
4525
-                else
4526
-                    sprintf(dbfile, "%s" PATHSEP "%s", dirname, dent->d_name);
4527
-                ret = cli_load(dbfile, engine, signo, options, NULL);
4528
-                if (ret) {
4529
-                    cli_errmsg("cli_loaddbdir(): error loading database %s\n", dbfile);
4530
-                    free(dbfile);
4531
-                    closedir(dd);
4532
-                    return ret;
4563
+            }
4564
+
4565
+        } else if (!strcmp(dent->d_name, "daily.cvd")) {
4566
+            load_priority = DB_LOAD_PRIORITY_DAILY_CVD;
4567
+
4568
+            have_daily_cvd = !access(dbfile, R_OK);
4569
+            if (have_daily_cvd) {
4570
+                daily_cvd = cl_cvdhead(dbfile);
4571
+                if (!daily_cvd) {
4572
+                    cli_errmsg("cli_loaddbdir(): error parsing header of %s\n", dbfile);
4573
+                    ret = CL_EMALFDB;
4574
+                    goto cleanup;
4533 4575
                 }
4534
-                free(dbfile);
4535 4576
             }
4536
-        }
4537
-    }
4538 4577
 
4539
-    /* the daily db must be loaded before main */
4540
-    dbfile = (char *)cli_malloc(dirname_len + 20);
4541
-    if (!dbfile) {
4542
-        closedir(dd);
4543
-        cli_errmsg("cli_loaddbdir: Can't allocate memory for dbfile\n");
4544
-        return CL_EMEM;
4545
-    }
4578
+        } else if (!strcmp(dent->d_name, "local.gdb")) {
4579
+            load_priority = DB_LOAD_PRIORITY_LOCAL_GDB;
4546 4580
 
4547
-    if (ends_with_sep)
4548
-        sprintf(dbfile, "%sdaily.cld", dirname);
4549
-    else
4550
-        sprintf(dbfile, "%s" PATHSEP "daily.cld", dirname);
4551
-    have_cld = !access(dbfile, R_OK);
4552
-    if (have_cld) {
4553
-        daily_cld = cl_cvdhead(dbfile);
4554
-        if (!daily_cld) {
4555
-            cli_errmsg("cli_loaddbdir(): error parsing header of %s\n", dbfile);
4581
+        } else if (!strcmp(dent->d_name, "daily.cfg")) {
4582
+            load_priority = DB_LOAD_PRIORITY_DAILY_CFG;
4583
+
4584
+        } else if ((options & CL_DB_OFFICIAL_ONLY) && !strstr(dirname, "clamav-") && !cli_strbcasestr(dent->d_name, ".cld") && !cli_strbcasestr(dent->d_name, ".cvd")) {
4585
+            // TODO Should this be higher up in the list? Should we
4586
+            // ignore .ign/.ign2 files and the local.gdb file when this
4587
+            // flag is set?
4588
+            cli_dbgmsg("Skipping unofficial database %s\n", dent->d_name);
4556 4589
             free(dbfile);
4557
-            closedir(dd);
4558
-            return CL_EMALFDB;
4590
+            dbfile = NULL;
4591
+            continue;
4592
+
4593
+        } else if (cli_strbcasestr(dent->d_name, ".crb")) {
4594
+            /* .cat files cannot be loaded successfully unless there are .crb
4595
+         * rules that whitelist the certs used to sign the catalog files.
4596
+         * Therefore, we need to ensure the .crb rules are loaded prior */
4597
+            load_priority = DB_LOAD_PRIORITY_CRB;
4598
+
4599
+        } else {
4600
+            load_priority = DB_LOAD_PRIORITY_NORMAL;
4601
+        }
4602
+
4603
+        entry = malloc(sizeof(*entry));
4604
+        if (NULL == entry) {
4605
+            cli_errmsg("cli_loaddbdir(): entry == NULL\n");
4606
+            ret = CL_EMEM;
4607
+            goto cleanup;
4559 4608
         }
4609
+
4610
+        entry->path          = dbfile;
4611
+        dbfile               = NULL;
4612
+        entry->load_priority = load_priority;
4613
+        cli_insertdbtoll(&head, entry);
4560 4614
     }
4561
-    if (ends_with_sep)
4562
-        sprintf(dbfile, "%sdaily.cvd", dirname);
4563
-    else
4564
-        sprintf(dbfile, "%s" PATHSEP "daily.cvd", dirname);
4565
-    if (!access(dbfile, R_OK)) {
4566
-        if (have_cld) {
4567
-            daily_cvd = cl_cvdhead(dbfile);
4568
-            if (!daily_cvd) {
4569
-                cli_errmsg("cli_loaddbdir(): error parsing header of %s\n", dbfile);
4570
-                free(dbfile);
4571
-                cl_cvdfree(daily_cld);
4572
-                closedir(dd);
4573
-                return CL_EMALFDB;
4615
+
4616
+    /* The list entries are stored in priority order, so now just loop through
4617
+     * and load everything.
4618
+     * NOTE: If there's a daily.cld and a daily.cvd, we'll only load whichever
4619
+     * has the highest version number. */
4620
+
4621
+    // TODO Should we treat all cld/cvd pairs like we do the daily ones?
4622
+    for (iter = head; iter != NULL; iter = iter->next) {
4623
+
4624
+        if (DB_LOAD_PRIORITY_DAILY_CLD == iter->load_priority && have_daily_cvd) {
4625
+            if (daily_cld->version <= daily_cvd->version) {
4626
+                continue;
4574 4627
             }
4628
+
4629
+        } else if (DB_LOAD_PRIORITY_DAILY_CVD == iter->load_priority && have_daily_cld) {
4575 4630
             if (daily_cld->version > daily_cvd->version) {
4576
-                if (ends_with_sep)
4577
-                    sprintf(dbfile, "%sdaily.cld", dirname);
4578
-                else
4579
-                    sprintf(dbfile, "%s" PATHSEP "daily.cld", dirname);
4631
+                continue;
4580 4632
             }
4581
-            cl_cvdfree(daily_cvd);
4582 4633
         }
4583
-    } else {
4584
-        if (ends_with_sep)
4585
-            sprintf(dbfile, "%sdaily.cld", dirname);
4586
-        else
4587
-            sprintf(dbfile, "%s" PATHSEP "daily.cld", dirname);
4634
+
4635
+        ret = cli_load(iter->path, engine, signo, options, NULL);
4636
+        if (ret) {
4637
+            cli_errmsg("cli_loaddbdir(): error loading database %s\n", iter->path);
4638
+            goto cleanup;
4639
+        }
4588 4640
     }
4589
-    if (have_cld)
4590
-        cl_cvdfree(daily_cld);
4591 4641
 
4592
-    if (!access(dbfile, R_OK) && (ret = cli_load(dbfile, engine, signo, options, NULL))) {
4593
-        free(dbfile);
4594
-        closedir(dd);
4595
-        return ret;
4642
+cleanup:
4643
+    for (iter = head; iter != NULL; iter = next) {
4644
+        next = iter->next;
4645
+        free(iter->path);
4646
+        free(iter);
4596 4647
     }
4597 4648
 
4598
-    /* try to load local.gdb next */
4599
-    if (ends_with_sep)
4600
-        sprintf(dbfile, "%slocal.gdb", dirname);
4601
-    else
4602
-        sprintf(dbfile, "%s" PATHSEP "local.gdb", dirname);
4603
-    if (!access(dbfile, R_OK) && (ret = cli_load(dbfile, engine, signo, options, NULL))) {
4649
+    if (NULL != dbfile) {
4604 4650
         free(dbfile);
4605
-        closedir(dd);
4606
-        return ret;
4607 4651
     }
4608 4652
 
4609
-    /* check for and load daily.cfg */
4610
-    if (ends_with_sep)
4611
-        sprintf(dbfile, "%sdaily.cfg", dirname);
4612
-    else
4613
-        sprintf(dbfile, "%s" PATHSEP "daily.cfg", dirname);
4614
-    if (!access(dbfile, R_OK) && (ret = cli_load(dbfile, engine, signo, options, NULL))) {
4615
-        free(dbfile);
4653
+    if (NULL != dd) {
4616 4654
         closedir(dd);
4617
-        return ret;
4618 4655
     }
4619
-    free(dbfile);
4620
-
4621
-    /* second round - load everything else */
4622
-    rewinddir(dd);
4623
-#ifdef HAVE_READDIR_R_3
4624
-    while (!readdir_r(dd, &result.d, &dent) && dent) {
4625
-#elif defined(HAVE_READDIR_R_2)
4626
-    while ((dent = (struct dirent *)readdir_r(dd, &result.d))) {
4627
-#else
4628
-    while ((dent = readdir(dd))) {
4629
-#endif
4630
-        if (dent->d_ino) {
4631
-            if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) {
4632
-                continue;
4633
-            }
4634
-
4635
-            /* Skip everything that's already been loaded in or ignored */
4636
-            if (cli_strbcasestr(dent->d_name, ".ign") || cli_strbcasestr(dent->d_name, ".ign2") || !strcmp(dent->d_name, "daily.cvd") || !strcmp(dent->d_name, "daily.cld") || !strcmp(dent->d_name, "local.gdb") || !strcmp(dent->d_name, "daily.cfg")) {
4637
-                continue;
4638
-            }
4639 4656
 
4640
-            if (CLI_DBEXT(dent->d_name)) {
4641
-                if ((options & CL_DB_OFFICIAL_ONLY) && !strstr(dirname, "clamav-") && !cli_strbcasestr(dent->d_name, ".cld") && !cli_strbcasestr(dent->d_name, ".cvd")) {
4642
-                    cli_dbgmsg("Skipping unofficial database %s\n", dent->d_name);
4643
-                    continue;
4644
-                }
4657
+    if (NULL != daily_cld) {
4658
+        cl_cvdfree(daily_cld);
4659
+    }
4645 4660
 
4646
-                dbfile = (char *)cli_malloc(strlen(dent->d_name) + dirname_len + 2);
4647
-                if (!dbfile) {
4648
-                    cli_errmsg("cli_loaddbdir(): dbfile == NULL\n");
4649
-                    closedir(dd);
4650
-                    return CL_EMEM;
4651
-                }
4652
-                if (ends_with_sep)
4653
-                    sprintf(dbfile, "%s%s", dirname, dent->d_name);
4654
-                else
4655
-                    sprintf(dbfile, "%s" PATHSEP "%s", dirname, dent->d_name);
4656
-                ret = cli_load(dbfile, engine, signo, options, NULL);
4657
-                if (ret) {
4658
-                    cli_errmsg("cli_loaddbdir(): error loading database %s\n", dbfile);
4659
-                    free(dbfile);
4660
-                    closedir(dd);
4661
-                    return ret;
4662
-                }
4663
-                free(dbfile);
4664
-            }
4665
-        }
4661
+    if (NULL != daily_cvd) {
4662
+        cl_cvdfree(daily_cvd);
4666 4663
     }
4667
-    closedir(dd);
4664
+
4668 4665
     if (ret == CL_EOPEN)
4669 4666
         cli_errmsg("cli_loaddbdir(): No supported database files found in %s\n", dirname);
4670 4667