implement Milestone 8: SQLite Database with injection prevention
This commit is contained in:
@@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
# Find dependencies via vcpkg
|
||||
find_package(Lua REQUIRED)
|
||||
find_package(nlohmann_json CONFIG REQUIRED)
|
||||
find_package(unofficial-sqlite3 CONFIG REQUIRED)
|
||||
|
||||
# Sandbox library (the code being tested)
|
||||
add_library(mosis-sandbox STATIC
|
||||
@@ -19,6 +20,7 @@ add_library(mosis-sandbox STATIC
|
||||
../src/main/cpp/sandbox/json_api.cpp
|
||||
../src/main/cpp/sandbox/crypto_api.cpp
|
||||
../src/main/cpp/sandbox/virtual_fs.cpp
|
||||
../src/main/cpp/sandbox/database_manager.cpp
|
||||
)
|
||||
target_include_directories(mosis-sandbox PUBLIC
|
||||
../src/main/cpp/sandbox
|
||||
@@ -27,6 +29,7 @@ target_include_directories(mosis-sandbox PUBLIC
|
||||
target_link_libraries(mosis-sandbox PUBLIC
|
||||
${LUA_LIBRARIES}
|
||||
nlohmann_json::nlohmann_json
|
||||
unofficial::sqlite3::sqlite3
|
||||
)
|
||||
# Windows BCrypt for crypto API
|
||||
if(WIN32)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "json_api.h"
|
||||
#include "crypto_api.h"
|
||||
#include "virtual_fs.h"
|
||||
#include "database_manager.h"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
@@ -1259,6 +1260,215 @@ bool Test_VirtualFSMaxFileSize(std::string& error_msg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// MILESTONE 8: SQLITE DATABASE TESTS
|
||||
//=============================================================================
|
||||
|
||||
bool Test_DatabaseCreatesTables(std::string& error_msg) {
|
||||
std::string test_root = "test_db_app";
|
||||
mosis::DatabaseManager manager("test.app", test_root);
|
||||
|
||||
std::string err;
|
||||
auto db = manager.Open("test", err);
|
||||
EXPECT_TRUE(db != nullptr);
|
||||
|
||||
// Create table
|
||||
EXPECT_TRUE(db->Execute(
|
||||
"CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)",
|
||||
{}, err));
|
||||
|
||||
// Insert
|
||||
EXPECT_TRUE(db->Execute(
|
||||
"INSERT INTO items (name) VALUES (?)",
|
||||
{std::string("Test Item")}, err));
|
||||
|
||||
// Query
|
||||
auto rows = db->Query("SELECT * FROM items", {}, err);
|
||||
EXPECT_TRUE(rows.has_value());
|
||||
EXPECT_TRUE(rows->size() == 1);
|
||||
|
||||
db->Close();
|
||||
manager.CloseAll();
|
||||
|
||||
// Cleanup
|
||||
std::filesystem::remove_all(test_root);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_DatabasePreparedStatements(std::string& error_msg) {
|
||||
std::string test_root = "test_db_app";
|
||||
mosis::DatabaseManager manager("test.app", test_root);
|
||||
|
||||
std::string err;
|
||||
auto db = manager.Open("test", err);
|
||||
EXPECT_TRUE(db != nullptr);
|
||||
|
||||
db->Execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)", {}, err);
|
||||
db->Execute("INSERT INTO users (name) VALUES (?)", {std::string("Alice")}, err);
|
||||
|
||||
// Attempt SQL injection via parameter - should be safely escaped
|
||||
std::string malicious = "'; DROP TABLE users; --";
|
||||
auto rows = db->Query("SELECT * FROM users WHERE name = ?", {malicious}, err);
|
||||
|
||||
// Query should succeed (finding nothing) and table should still exist
|
||||
EXPECT_TRUE(rows.has_value());
|
||||
EXPECT_TRUE(rows->size() == 0);
|
||||
|
||||
// Table should still exist
|
||||
auto check = db->Query("SELECT * FROM users", {}, err);
|
||||
EXPECT_TRUE(check.has_value());
|
||||
EXPECT_TRUE(check->size() == 1);
|
||||
|
||||
db->Close();
|
||||
std::filesystem::remove_all(test_root);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_DatabaseBlocksAttach(std::string& error_msg) {
|
||||
std::string test_root = "test_db_app";
|
||||
mosis::DatabaseManager manager("test.app", test_root);
|
||||
|
||||
std::string err;
|
||||
auto db = manager.Open("test", err);
|
||||
EXPECT_TRUE(db != nullptr);
|
||||
|
||||
// Try to attach another database - should fail
|
||||
EXPECT_FALSE(db->Execute("ATTACH DATABASE '/etc/passwd' AS evil", {}, err));
|
||||
EXPECT_TRUE(err.find("authorized") != std::string::npos ||
|
||||
err.find("denied") != std::string::npos ||
|
||||
err.find("not authorized") != std::string::npos);
|
||||
|
||||
db->Close();
|
||||
std::filesystem::remove_all(test_root);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_DatabaseBlocksDangerousPragma(std::string& error_msg) {
|
||||
std::string test_root = "test_db_app";
|
||||
mosis::DatabaseManager manager("test.app", test_root);
|
||||
|
||||
std::string err;
|
||||
auto db = manager.Open("test", err);
|
||||
EXPECT_TRUE(db != nullptr);
|
||||
|
||||
// Try dangerous PRAGMAs - should fail
|
||||
EXPECT_FALSE(db->Execute("PRAGMA journal_mode = OFF", {}, err));
|
||||
err.clear();
|
||||
EXPECT_FALSE(db->Execute("PRAGMA synchronous = OFF", {}, err));
|
||||
|
||||
// Safe PRAGMAs should work
|
||||
err.clear();
|
||||
auto rows = db->Query("PRAGMA table_info(sqlite_master)", {}, err);
|
||||
EXPECT_TRUE(rows.has_value());
|
||||
|
||||
db->Close();
|
||||
std::filesystem::remove_all(test_root);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_DatabaseMultiple(std::string& error_msg) {
|
||||
std::string test_root = "test_db_app";
|
||||
mosis::DatabaseManager manager("test.app", test_root);
|
||||
|
||||
std::string err;
|
||||
auto db1 = manager.Open("db1", err);
|
||||
auto db2 = manager.Open("db2", err);
|
||||
|
||||
EXPECT_TRUE(db1 != nullptr);
|
||||
EXPECT_TRUE(db2 != nullptr);
|
||||
EXPECT_TRUE(manager.GetOpenDatabaseCount() == 2);
|
||||
|
||||
// They should be independent
|
||||
db1->Execute("CREATE TABLE t1 (x INTEGER)", {}, err);
|
||||
db2->Execute("CREATE TABLE t2 (y INTEGER)", {}, err);
|
||||
|
||||
// t1 shouldn't exist in db2
|
||||
auto rows = db2->Query("SELECT * FROM t1", {}, err);
|
||||
EXPECT_FALSE(rows.has_value()); // Should fail - table doesn't exist
|
||||
|
||||
manager.CloseAll();
|
||||
std::filesystem::remove_all(test_root);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_DatabaseLuaIntegration(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
|
||||
std::string test_root = "test_db_lua";
|
||||
mosis::DatabaseManager manager("test.app", test_root);
|
||||
mosis::RegisterDatabaseAPI(sandbox.GetState(), &manager);
|
||||
|
||||
std::string script = R"lua(
|
||||
-- Just test if database global exists
|
||||
if not database then
|
||||
error("database global not found")
|
||||
end
|
||||
if not database.open then
|
||||
error("database.open not found")
|
||||
end
|
||||
)lua";
|
||||
|
||||
bool ok = sandbox.LoadString(script, "db_test");
|
||||
manager.CloseAll();
|
||||
std::filesystem::remove_all(test_root);
|
||||
|
||||
if (!ok) {
|
||||
error_msg = "Lua test failed: " + sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_DatabaseInvalidNames(std::string& error_msg) {
|
||||
std::string test_root = "test_db_app";
|
||||
mosis::DatabaseManager manager("test.app", test_root);
|
||||
|
||||
std::string err;
|
||||
|
||||
// Path traversal
|
||||
auto db1 = manager.Open("../evil", err);
|
||||
EXPECT_TRUE(db1 == nullptr);
|
||||
|
||||
// Absolute path component
|
||||
err.clear();
|
||||
auto db2 = manager.Open("/etc/passwd", err);
|
||||
EXPECT_TRUE(db2 == nullptr);
|
||||
|
||||
// Special characters
|
||||
err.clear();
|
||||
auto db3 = manager.Open("test;drop", err);
|
||||
EXPECT_TRUE(db3 == nullptr);
|
||||
|
||||
std::filesystem::remove_all(test_root);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_DatabaseLastInsertAndChanges(std::string& error_msg) {
|
||||
std::string test_root = "test_db_app";
|
||||
mosis::DatabaseManager manager("test.app", test_root);
|
||||
|
||||
std::string err;
|
||||
auto db = manager.Open("test", err);
|
||||
EXPECT_TRUE(db != nullptr);
|
||||
|
||||
db->Execute("CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)", {}, err);
|
||||
|
||||
db->Execute("INSERT INTO items (name) VALUES (?)", {std::string("Item 1")}, err);
|
||||
EXPECT_TRUE(db->GetLastInsertRowId() == 1);
|
||||
EXPECT_TRUE(db->GetChanges() == 1);
|
||||
|
||||
db->Execute("INSERT INTO items (name) VALUES (?)", {std::string("Item 2")}, err);
|
||||
EXPECT_TRUE(db->GetLastInsertRowId() == 2);
|
||||
|
||||
db->Execute("UPDATE items SET name = ?", {std::string("Updated")}, err);
|
||||
EXPECT_TRUE(db->GetChanges() == 2); // Updated 2 rows
|
||||
|
||||
db->Close();
|
||||
std::filesystem::remove_all(test_root);
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// MAIN
|
||||
//=============================================================================
|
||||
@@ -1371,6 +1581,16 @@ int main(int argc, char* argv[]) {
|
||||
harness.AddTest("VirtualFSLuaIntegration", Test_VirtualFSLuaIntegration);
|
||||
harness.AddTest("VirtualFSMaxFileSize", Test_VirtualFSMaxFileSize);
|
||||
|
||||
// Milestone 8: SQLite Database
|
||||
harness.AddTest("DatabaseCreatesTables", Test_DatabaseCreatesTables);
|
||||
harness.AddTest("DatabasePreparedStatements", Test_DatabasePreparedStatements);
|
||||
harness.AddTest("DatabaseBlocksAttach", Test_DatabaseBlocksAttach);
|
||||
harness.AddTest("DatabaseBlocksDangerousPragma", Test_DatabaseBlocksDangerousPragma);
|
||||
harness.AddTest("DatabaseMultiple", Test_DatabaseMultiple);
|
||||
harness.AddTest("DatabaseLuaIntegration", Test_DatabaseLuaIntegration);
|
||||
harness.AddTest("DatabaseInvalidNames", Test_DatabaseInvalidNames);
|
||||
harness.AddTest("DatabaseLastInsertAndChanges", Test_DatabaseLastInsertAndChanges);
|
||||
|
||||
// Run tests
|
||||
auto results = harness.Run(filter);
|
||||
|
||||
|
||||
9
sandbox-test/vcpkg.json
Normal file
9
sandbox-test/vcpkg.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "sandbox-test",
|
||||
"version-string": "0.1.0",
|
||||
"dependencies": [
|
||||
"lua",
|
||||
"nlohmann-json",
|
||||
"sqlite3"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user