Description
We are now able to reproduce crashes with lib sqlcipher if macOS >= 13.0-beta.2. Here are the minimal test sample steps.
Compile Sqlchiper
git clone https://github.com/sqlcipher/sqlcipher
cd sqlcipher
mkdir out
./configure --enable-tempstore=yes CFLAGS="-DSQLITE_HAS_CODEC" LDFLAGS="-framework CoreFoundation -framework Security" --with-crypto-lib=commoncrypto --prefix=$PWD/out
make install
Reproduce Crash
// main.c
#include "sqlite3.h"
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void ensure_ok(sqlite3 *handle, int code)
{
if (code == SQLITE_OK)
{
return;
}
printf("ERROR %d %s\n", code, sqlite3_errmsg(handle));
exit(1);
}
void run(sqlite3 *handle, const char *sql, void (*callback)(sqlite3_stmt *))
{
sqlite3_stmt *stmt = NULL;
char *unused = NULL;
int sql_len = strlen(sql);
int code = sqlite3_prepare_v2(handle, sql, sql_len, &stmt, &unused);
ensure_ok(handle, code);
code = SQLITE_ROW;
while (code != SQLITE_DONE)
{
code = sqlite3_step(stmt);
if (code != SQLITE_ROW && code != SQLITE_DONE)
{
printf("ERROR %d %s\n", code, sqlite3_errmsg(handle));
exit(1);
}
if (callback != NULL)
{
callback(stmt);
}
}
code = sqlite3_finalize(stmt);
ensure_ok(handle, code);
}
void init_db(sqlite3 *handle)
{
run(handle, "PRAGMA key = '114514'", NULL);
run(handle, "PRAGMA cipher_compatibility = '3'", NULL);
run(handle, "PRAGMA busy_timeout = '5000'", NULL);
run(handle, "PRAGMA journal_mode = 'WAL'", NULL);
}
sqlite3 *open_db()
{
char path[256] = {0};
pid_t pid = getpid();
sprintf(&path, "/tmp/%d", pid);
mkdir(&path, 0777);
sprintf(&path, "/tmp/%d/a.db", pid);
sqlite3 *ret = NULL;
int code = sqlite3_open(&path, &ret);
ensure_ok(ret, code);
init_db(ret);
return ret;
}
void close_db(sqlite3 *handle)
{
int code = sqlite3_close(handle);
ensure_ok(handle, code);
}
int positive_rand(int start, int end)
{
unsigned int ret = (unsigned int)rand();
return (ret % (end - start)) + start;
}
void alloc_test()
{
int count = positive_rand(128, 1024);
for (int i = 0; i < count; i++)
{
int count2 = positive_rand(512, 1024);
int **ll = malloc(sizeof(int *) * count2);
for (int j = 0; j < count2; j++)
{
int count3 = positive_rand(0, 1024);
int *l = malloc(sizeof(int) * count3);
memset(l, i ^ j, count3);
ll[j] = l;
}
for (int j = 0; j < count2; j++)
{
free(ll[j]);
}
free(ll);
}
}
void run_thread(void *data)
{
alloc_test();
sqlite3 *db = open_db();
sqlite3_close(db);
}
int main(void)
{
sqlite3 *db = open_db();
run(db, "CREATE TABLE student(id INTEGER, name STRING)", NULL);
close_db(db);
pthread_t threads[64];
for (int i = 0; i < 64; i++)
{
pthread_create(&threads[i], NULL, run_thread, NULL);
}
for (int i = 0; i < 64; i++)
{
pthread_join(threads[i], NULL);
}
printf("done\n");
return 0;
}
Put the above content to main.c file, then:
clang main.c -o demo -L ./sqlcipher/out/lib/ -I ./sqlcipher/out/include/ -lsqlcipher
export MallocScribble=1 # This will make crash easier
export MallocPreScribble=1
./demo # Execute this binaray mulitple times, you will see the crash
Since we can't reproduce the same crash if macOS < 13.0-beta.2, is there any change with beta2 that related to malloc
?
Looking forward to getting the official feedback.