SQLite 是以檔案系統為基礎的資料庫, 因此除了檔案權限, 無法做到其他權限管理. 很難防止惡意變更 schema 來進行破壞的行為. 因此應用程式需要對 schema 進行檢查, 以避免 schema 受污染而產生漏洞.
SQLite is a file system-based database, so beyond file permissions, it cannot implement other permission management mechanisms. It is difficult to prevent malicious modifications to the schema for destructive purposes. Therefore, applications must perform schema checks to avoid vulnerabilities arising from schema corruption.
這個保護的方法是將 schema 的主要內容, 以 FNA-1a 算法產生一個 32bits 的 hash code. 應用程式在開啟資料庫時可以檢查該 hash code 來判斷 schema 有沒有被汙染.
This protection method involves generating a 32-bit hash code for the schema's main content using the FNA-1a algorithm. Applications can verify this hash code when opening the database to determine whether the schema has been tampered with.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sqlite3.h> #define FNV_32_PRIME 0x01000193U #define FNV_32_OFFSET_BASIS 0x811C9DC5U #define MAX_SCHEMA_SIZE 16384 uint32_t fnv1a_32_hash(const void *data, size_t len) { const unsigned char *bp = (const unsigned char *)data; uint32_t hash = FNV_32_OFFSET_BASIS; for (size_t i = 0; i < len; i++) { hash ^= (uint32_t)bp[i]; hash *= FNV_32_PRIME; } return hash; } uint32_t calculate_schema_hash(sqlite3 *db, char **err_msg_out) { sqlite3_stmt *stmt = NULL; int rc; char schema_buffer[MAX_SCHEMA_SIZE] = {0}; const char *sql_query = "SELECT sql FROM sqlite_master " "WHERE type='table' AND name NOT LIKE 'sqlite_%';" rc = sqlite3_prepare_v2(db, sql_query, -1, &stmt, NULL); if (rc != SQLITE_OK) { if (err_msg_out) { *err_msg_out = strdup(sqlite3_errmsg(db)); } free(schema_buffer); return 0; } while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { const char *schema_sql = (const char *)sqlite3_column_text(stmt, 0); if (schema_sql) { // Check buffer space and append new SQL statements to the buffer. size_t current_len = strlen(schema_buffer); size_t sql_len = strlen(schema_sql); if (current_len + sql_len + 1 < MAX_SCHEMA_SIZE) { // Concatenate all schema definition strings directly as raw fingerprint data. strcat(schema_buffer, schema_sql); } else { if (err_msg_out) { *err_msg_out = strdup("Schema buffer overflow. Increase MAX_SCHEMA_SIZE."); } sqlite3_finalize(stmt); free(schema_buffer); return 0; } } } sqlite3_finalize(stmt); if (rc != SQLITE_DONE) { // Handling other errors that may occur during the traversal process if (err_msg_out) { *err_msg_out = strdup(sqlite3_errmsg(db)); } free(schema_buffer); return 0; } // Calculate FNV-1a Hash uint32_t final_hash = 0; final_hash = fnv1a_32_hash(schema_buffer, strlen(schema_buffer)); free(schema_buffer); return final_hash; } |