CVE-2021-3119: NULL Pointer Dereference (CWE-476)
value: Zetetic SQLCipher 4.x before 4.4.3 has a NULL pointer dereferencing issue related to sqlcipher_export in crypto.c and sqlite3StrICmp in sqlite3.c. This may allow an attacker to perform a remote denial of service attack. For example, an SQL injection can be used to execute the crafted SQL command sequence, which causes a segmentation fault.
CWE description: The product dereferences a pointer that it expects to be valid but is NULL.
CVSS v3 base score: 7.5
Published: 2021-03-25T23:15Z
Repository: https://github.com/sqlcipher/sqlcipher
Language: Other
Files changed: src/crypto.c, test/sqlcipher-compatibility.test
Commit date: 2021-01-13 17:24:33 -0500
Commit message: fix sqlcipher_export handling of NULL parameters

Diff: diff --git a/src/crypto.c b/src/crypto.c
index 382718a..5489f9c 100644
--- a/src/crypto.c
+++ b/src/crypto.c
@@ -1073,17 +1073,33 @@ void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **ar
     rc = SQLITE_ERROR;
     pzErrMsg = sqlite3_mprintf("invalid number of arguments (%d) passed to sqlcipher_export", argc);
     goto end_of_export;
   }
 
-  targetDb = (const char*) sqlite3_value_text(argv[0]);
-  sourceDb = (argc == 2) ? (char *) sqlite3_value_text(argv[1]) : "main";
+  if(sqlite3_value_type(argv[0]) == SQLITE_NULL) {
+    rc = SQLITE_ERROR;
+    pzErrMsg = sqlite3_mprintf("target database can't be NULL");
+    goto end_of_export;
+  }
+
+  targetDb = (const char*) sqlite3_value_text(argv[0]); 
+  sourceDb = "main";
+
+  if(argc == 2) {
+    if(sqlite3_value_type(argv[1]) == SQLITE_NULL) {
+      rc = SQLITE_ERROR;
+      pzErrMsg = sqlite3_mprintf("target database can't be NULL");
+      goto end_of_export;
+    }
+    sourceDb = (char *) sqlite3_value_text(argv[1]);
+  }
+
 
   /* if the name of the target is not main, but the index returned is zero 
      there is a mismatch and we should not proceed */
   targetDb_idx =  sqlcipher_find_db_index(db, targetDb);
-  if(targetDb_idx == 0 && sqlite3StrICmp("main", targetDb) != 0) {
+  if(targetDb_idx == 0 && targetDb != NULL && sqlite3StrICmp("main", targetDb) != 0) {
     rc = SQLITE_ERROR;
     pzErrMsg = sqlite3_mprintf("unknown database %s", targetDb);
     goto end_of_export;
   }
   db->init.iDb = targetDb_idx;
diff --git a/test/sqlcipher-compatibility.test b/test/sqlcipher-compatibility.test
index 090e05e..7802b06 100644
--- a/test/sqlcipher-compatibility.test
+++ b/test/sqlcipher-compatibility.test
@@ -442,10 +442,36 @@ do_test export-error {
   } 
 } {1 {unknown database nodb}}
 db close
 file delete -force test.db
 
+# verify sqlcipher_export with NULL parameters
+do_test export-nulls {
+  sqlite_orig db test.db
+
+  catchsql {
+    SELECT sqlcipher_export(NULL);
+  }
+
+} {1 {target database can't be NULL}}
+db close
+file delete -force test.db
+
+do_test export-nulls {
+  sqlite_orig db test.db
+
+  catchsql {
+    SELECT sqlcipher_export('main', NULL);
+  }
+
+} {1 {target database can't be NULL}}
+db close
+file delete -force test.db
+
+
+# use the sqlcipher_export function
+
 # use the sqlcipher_export function
 # to copy a complicated database. 
 # tests autoincrement fields,
 # indexes, views, and triggers,
 # tables and virtual tables

Vulnerable code:   targetDb = (const char*) sqlite3_value_text(argv[0]);
  sourceDb = (argc == 2) ? (char *) sqlite3_value_text(argv[1]) : "main";
  if(targetDb_idx == 0 && sqlite3StrICmp("main", targetDb) != 0) {
Fixed code:   if(sqlite3_value_type(argv[0]) == SQLITE_NULL) {
    rc = SQLITE_ERROR;
    pzErrMsg = sqlite3_mprintf("target database can't be NULL");
    goto end_of_export;
  }

  targetDb = (const char*) sqlite3_value_text(argv[0]); 
  sourceDb = "main";

  if(argc == 2) {
    if(sqlite3_value_type(argv[1]) == SQLITE_NULL) {
      rc = SQLITE_ERROR;
      pzErrMsg = sqlite3_mprintf("target database can't be NULL");
      goto end_of_export;
    }
    sourceDb = (char *) sqlite3_value_text(argv[1]);
  }

  if(targetDb_idx == 0 && targetDb != NULL && sqlite3StrICmp("main", targetDb) != 0) {
# verify sqlcipher_export with NULL parameters
do_test export-nulls {
  sqlite_orig db test.db

  catchsql {
    SELECT sqlcipher_export(NULL);
  }

} {1 {target database can't be NULL}}
db close
file delete -force test.db

do_test export-nulls {
  sqlite_orig db test.db

  catchsql {
    SELECT sqlcipher_export('main', NULL);
  }

} {1 {target database can't be NULL}}
db close
file delete -force test.db


# use the sqlcipher_export function