From 18614b77c03beb8a13c8fdbfde3da2f59fae9a5d Mon Sep 17 00:00:00 2001 From: Chase Killorin Date: Fri, 24 Oct 2025 01:42:57 -0400 Subject: [PATCH 01/23] Scripts clean final, postgres startup script fixed --- Scripts/RunFuzzilli.sh | 1 + Scripts/SetupPostgres.sh | 103 ++++++++++++++++++++++++++++++++++++++ runFuzzilli.sh | 1 - scripts/setup-postgres.sh | 87 -------------------------------- 4 files changed, 104 insertions(+), 88 deletions(-) create mode 100755 Scripts/RunFuzzilli.sh create mode 100755 Scripts/SetupPostgres.sh delete mode 100755 runFuzzilli.sh delete mode 100755 scripts/setup-postgres.sh diff --git a/Scripts/RunFuzzilli.sh b/Scripts/RunFuzzilli.sh new file mode 100755 index 000000000..b0a83fbe2 --- /dev/null +++ b/Scripts/RunFuzzilli.sh @@ -0,0 +1 @@ +swift run FuzzilliCli --profile=v8 --engine=multi --corpus=postgresql --postgres-url=postgresql://fuzzilli:password@localhost:5432/fuzzilli --logLevel=verbose --timeout=1500 --diagnostics ~/projects/ritsec/vrig/vrigatoni/v8/out/fuzzbuild/d8 \ No newline at end of file diff --git a/Scripts/SetupPostgres.sh b/Scripts/SetupPostgres.sh new file mode 100755 index 000000000..1ce1442d1 --- /dev/null +++ b/Scripts/SetupPostgres.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +# Setup PostgreSQL for Fuzzilli testing +set -e + +echo "=== Fuzzilli PostgreSQL Setup ===" + +# Detect container runtime +if command -v docker &> /dev/null; then + CONTAINER_RUNTIME="docker" + echo "Using Docker" +elif command -v podman &> /dev/null; then + CONTAINER_RUNTIME="podman" + echo "Using Podman" +else + echo "Error: Neither docker-compose nor podman is available" + echo "Please install docker-compose or podman to continue" + exit 1 +fi + +# Detect compose command +if command -v docker &> /dev/null; then + COMPOSE_CMD="docker compose" +elif command -v podman-compose &> /dev/null; then + COMPOSE_CMD="podman-compose" +else + echo "Error: No compose command found, do you have docker-compose or po installed?" + exit 1 +fi + +# Check if container runtime is accessible +if ! $CONTAINER_RUNTIME info &> /dev/null; then + echo "Error: $CONTAINER_RUNTIME is not accessible" + echo "Please ensure $CONTAINER_RUNTIME is running and try again" + exit 1 +fi + +echo "Starting PostgreSQL container..." +$COMPOSE_CMD up -d postgres + +echo "Waiting for PostgreSQL to be ready..." +timeout=60 +counter=0 +while ! $COMPOSE_CMD exec postgres pg_isready -U fuzzilli -d fuzzilli &> /dev/null; do + if [ $counter -ge $timeout ]; then + echo "Error: PostgreSQL failed to start within $timeout seconds" + $COMPOSE_CMD logs postgres + exit 1 + fi + echo "Waiting for PostgreSQL... ($counter/$timeout)" + sleep 2 + counter=$((counter + 2)) +done + +echo "PostgreSQL is ready!" + +# Test connection +echo "Testing database connection..." +$COMPOSE_CMD exec postgres psql -U fuzzilli -d fuzzilli -c "SELECT version();" + +echo "Checking if tables exist..." +$COMPOSE_CMD exec postgres psql -U fuzzilli -d fuzzilli -c " +SELECT table_name +FROM information_schema.tables +WHERE table_schema = 'public' +ORDER BY table_name; +" + +echo "Checking execution types..." +$COMPOSE_CMD exec postgres psql -U fuzzilli -d fuzzilli -c " +SELECT id, title, description +FROM execution_type +ORDER BY id; +" + +echo "Checking mutator types..." +$COMPOSE_CMD exec postgres psql -U fuzzilli -d fuzzilli -c " +SELECT id, name, category +FROM mutator_type +ORDER BY id; +" + +echo "Checking execution outcomes..." +$COMPOSE_CMD exec postgres psql -U fuzzilli -d fuzzilli -c " +SELECT id, outcome, description +FROM execution_outcome +ORDER BY id; +" + +echo "" +echo "=== PostgreSQL Setup Complete ===" +echo "Connection string: postgresql://fuzzilli:fuzzilli123@localhost:5433/fuzzilli" +echo "" +echo "To start pgAdmin (optional):" +echo " $COMPOSE_CMD up -d pgadmin" +echo " Open http://localhost:8080" +echo " Login: admin@fuzzilli.local / admin123" +echo "" +echo "To stop PostgreSQL:" +echo " $COMPOSE_CMD down" +echo "" +echo "To view logs:" +echo " $COMPOSE_CMD logs postgres" \ No newline at end of file diff --git a/runFuzzilli.sh b/runFuzzilli.sh deleted file mode 100755 index fe7789a51..000000000 --- a/runFuzzilli.sh +++ /dev/null @@ -1 +0,0 @@ -swift run FuzzilliCli --profile=v8 --engine=multi --resume --corpus=basic --storagePath=./Corpus --logLevel=verbose --timeout=1500 --diagnostics /usr/share/vrigatoni/v8_2/v8/out/fuzzbuild/d8 diff --git a/scripts/setup-postgres.sh b/scripts/setup-postgres.sh deleted file mode 100755 index 48091bba7..000000000 --- a/scripts/setup-postgres.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash - -# Setup PostgreSQL for Fuzzilli testing -set -e - -echo "=== Fuzzilli PostgreSQL Setup ===" - -# Check if docker-compose is available -if ! command -v docker-compose &> /dev/null; then - echo "Error: docker-compose is not installed" - echo "Please install docker-compose to continue" - exit 1 -fi - -# Check if docker is running -if ! docker info &> /dev/null; then - echo "Error: Docker is not running" - echo "Please start Docker and try again" - exit 1 -fi - -echo "Starting PostgreSQL container..." -docker-compose up -d postgres - -echo "Waiting for PostgreSQL to be ready..." -timeout=60 -counter=0 -while ! docker-compose exec postgres pg_isready -U fuzzilli -d fuzzilli &> /dev/null; do - if [ $counter -ge $timeout ]; then - echo "Error: PostgreSQL failed to start within $timeout seconds" - docker-compose logs postgres - exit 1 - fi - echo "Waiting for PostgreSQL... ($counter/$timeout)" - sleep 2 - counter=$((counter + 2)) -done - -echo "PostgreSQL is ready!" - -# Test connection -echo "Testing database connection..." -docker-compose exec postgres psql -U fuzzilli -d fuzzilli -c "SELECT version();" - -echo "Checking if tables exist..." -docker-compose exec postgres psql -U fuzzilli -d fuzzilli -c " -SELECT table_name -FROM information_schema.tables -WHERE table_schema = 'public' -ORDER BY table_name; -" - -echo "Checking execution types..." -docker-compose exec postgres psql -U fuzzilli -d fuzzilli -c " -SELECT id, title, description -FROM execution_type -ORDER BY id; -" - -echo "Checking mutator types..." -docker-compose exec postgres psql -U fuzzilli -d fuzzilli -c " -SELECT id, name, category -FROM mutator_type -ORDER BY id; -" - -echo "Checking execution outcomes..." -docker-compose exec postgres psql -U fuzzilli -d fuzzilli -c " -SELECT id, outcome, description -FROM execution_outcome -ORDER BY id; -" - -echo "" -echo "=== PostgreSQL Setup Complete ===" -echo "Connection string: postgresql://fuzzilli:fuzzilli123@localhost:5433/fuzzilli" -echo "" -echo "To start pgAdmin (optional):" -echo " docker-compose up -d pgadmin" -echo " Open http://localhost:8080" -echo " Login: admin@fuzzilli.local / admin123" -echo "" -echo "To stop PostgreSQL:" -echo " docker-compose down" -echo "" -echo "To view logs:" -echo " docker-compose logs postgres" From 5c37c899cd62dbcd731d956ce4bcdd12e321b7ee Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Mon, 27 Oct 2025 07:56:19 -0400 Subject: [PATCH 02/23] fixed to show execution count + type --- .../Fuzzilli/Corpus/PostgreSQLCorpus.swift | 155 ++++++++++-- .../Fuzzilli/Database/PostgreSQLStorage.swift | 231 +++++++++++++++--- Sources/FuzzilliCli/TerminalUI.swift | 24 +- postgres-init.sql | 5 +- 4 files changed, 353 insertions(+), 62 deletions(-) diff --git a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift index de55302a3..ea4e985fc 100644 --- a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift +++ b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift @@ -203,6 +203,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { // Listen for PostExecute events to track all program executions fuzzer.registerEventListener(for: fuzzer.events.PostExecute) { execution in if let program = self.currentExecutionProgram, let purpose = self.currentExecutionPurpose { + // DEBUG: Log execution recording + self.logger.info("Recording execution: outcome=\(execution.outcome), execTime=\(execution.execTime)") + // Create ProgramAspects from the execution let aspects = ProgramAspects(outcome: execution.outcome) @@ -238,6 +241,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { Task { await self.storeExecutionWithCachedData(program, executionData, dbExecutionPurpose, aspects) } + } else { + // DEBUG: Log when execution is not recorded + self.logger.info("Skipping execution recording: program=\(self.currentExecutionProgram != nil), purpose=\(self.currentExecutionPurpose != nil)") } } @@ -616,7 +622,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } // Store the program with metadata - let programHash = try await storage.storeProgram( + _ = try await storage.storeProgram( program: program, fuzzerId: fuzzerId, metadata: metadata @@ -682,37 +688,35 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { do { // Use the registered fuzzer ID guard let fuzzerId = fuzzerId else { - return // Silent fail for performance + logger.info("Cannot store execution: fuzzer not registered") + return } - // Store the program in the program table - let programHash = try await storage.storeProgram( - program: program, - fuzzerId: fuzzerId, - metadata: ExecutionMetadata(lastOutcome: DatabaseExecutionOutcome( - id: DatabaseUtils.mapExecutionOutcome(outcome: aspects.outcome), - outcome: aspects.outcome.description, - description: aspects.outcome.description - )) - ) + // DEBUG: Log execution storage attempt + logger.info("Storing execution: fuzzerId=\(fuzzerId), outcome=\(executionData.outcome), execTime=\(executionData.execTime)") - // Store the execution record with cached execution metadata - let executionId = try await storage.storeExecution( + // Store both program and execution in a single transaction to avoid foreign key issues + _ = try await storage.storeProgramAndExecution( program: program, fuzzerId: fuzzerId, executionType: executionType, outcome: executionData.outcome, coverage: aspects is CovEdgeSet ? Double((aspects as! CovEdgeSet).count) : 0.0, - executionTimeMs: Int(executionData.execTime * 1000), // Convert to milliseconds + executionTimeMs: Int(executionData.execTime * 1000), stdout: executionData.stdout, stderr: executionData.stderr, - fuzzout: executionData.fuzzout + fuzzout: executionData.fuzzout, + metadata: ExecutionMetadata(lastOutcome: DatabaseExecutionOutcome( + id: DatabaseUtils.mapExecutionOutcome(outcome: aspects.outcome), + outcome: aspects.outcome.description, + description: aspects.outcome.description + )) ) - // No logging for performance - just store silently + logger.info("Successfully stored program and execution") } catch { - // Silent fail for performance - errors are not critical for fuzzing + logger.error("Failed to store execution: \(String(reflecting: error))") } } @@ -726,7 +730,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } // Store the program in the program table - let programHash = try await storage.storeProgram( + _ = try await storage.storeProgram( program: program, fuzzerId: fuzzerId, metadata: ExecutionMetadata(lastOutcome: DatabaseExecutionOutcome( @@ -737,7 +741,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { ) // Store the execution record with full execution metadata - let executionId = try await storage.storeExecution( + _ = try await storage.storeExecution( program: program, fuzzerId: fuzzerId, execution: execution, @@ -762,7 +766,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } // Store the program in the program table - let programHash = try await storage.storeProgram( + _ = try await storage.storeProgram( program: program, fuzzerId: fuzzerId, metadata: ExecutionMetadata(lastOutcome: DatabaseExecutionOutcome( @@ -773,7 +777,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { ) // Store the execution record - let executionId = try await storage.storeExecution( + _ = try await storage.storeExecution( program: program, fuzzerId: fuzzerId, executionType: executionType, @@ -800,9 +804,10 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { /// Update execution metadata for a program private func updateExecutionMetadata(for programHash: String, aspects: ProgramAspects) { - guard var (program, metadata) = programCache[programHash] else { return } - updateExecutionMetadata(&metadata, aspects: aspects) - programCache[programHash] = (program: program, metadata: metadata) + guard let (program, metadata) = programCache[programHash] else { return } + var updatedMetadata = metadata + updateExecutionMetadata(&updatedMetadata, aspects: aspects) + programCache[programHash] = (program: program, metadata: updatedMetadata) markForSync(programHash) } @@ -873,27 +878,125 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { let averageCoverage = programCache.values.isEmpty ? 0.0 : programCache.values.reduce(0.0) { $0 + $1.metadata.lastCoverage } / Double(programCache.count) + // Get current coverage from evaluator if available + var currentCoverage = 0.0 + if let coverageEvaluator = fuzzer.evaluator as? ProgramCoverageEvaluator { + currentCoverage = coverageEvaluator.currentScore + // TEMPORARY TEST: Seed with 999.99 if coverage is 0 (for testing display) + if currentCoverage == 0.0 { + currentCoverage = 999.99 + } + } + return CorpusStatistics( totalPrograms: programs.count, totalExecutions: totalExecutions, averageCoverage: averageCoverage, + currentCoverage: currentCoverage, pendingSyncOperations: pendingSyncOperations.count, fuzzerInstanceId: fuzzerInstanceId ) } + + /// Get enhanced statistics including database coverage + public func getEnhancedStatistics() async -> EnhancedCorpusStatistics { + cacheLock.lock() + defer { cacheLock.unlock() } + + let totalExecutions = programCache.values.reduce(0) { $0 + $1.metadata.executionCount } + let averageCoverage = programCache.values.isEmpty ? 0.0 : + programCache.values.reduce(0.0) { $0 + $1.metadata.lastCoverage } / Double(programCache.count) + + // Get database statistics + var dbStats = DatabaseStatistics() + if let fuzzerId = fuzzerId { + do { + dbStats = try await storage.getDatabaseStatistics(fuzzerId: fuzzerId) + } catch { + logger.warning("Failed to get database statistics: \(error)") + } + } + + return EnhancedCorpusStatistics( + totalPrograms: programs.count, + totalExecutions: totalExecutions, + averageCoverage: averageCoverage, + pendingSyncOperations: pendingSyncOperations.count, + fuzzerInstanceId: fuzzerInstanceId, + databasePrograms: dbStats.totalPrograms, + databaseExecutions: dbStats.totalExecutions, + databaseCrashes: dbStats.totalCrashes, + activeFuzzers: dbStats.activeFuzzers, + lastSyncTime: nil + ) + } } // MARK: - Supporting Types /// Statistics for PostgreSQL corpus public struct CorpusStatistics { + public let totalPrograms: Int + public let totalExecutions: Int + public let averageCoverage: Double + public let currentCoverage: Double + public let pendingSyncOperations: Int + public let fuzzerInstanceId: String + + public var description: String { + return "Programs: \(totalPrograms), Executions: \(totalExecutions), Avg Coverage: \(String(format: "%.2f%%", averageCoverage)), Current Coverage: \(String(format: "%.2f%%", currentCoverage)), Pending Sync: \(pendingSyncOperations)" + } +} + +/// Enhanced statistics for PostgreSQL corpus including database information +public struct EnhancedCorpusStatistics { public let totalPrograms: Int public let totalExecutions: Int public let averageCoverage: Double public let pendingSyncOperations: Int public let fuzzerInstanceId: String + public let databasePrograms: Int + public let databaseExecutions: Int + public let databaseCrashes: Int + public let activeFuzzers: Int + public let lastSyncTime: Date? public var description: String { - return "Programs: \(totalPrograms), Executions: \(totalExecutions), Coverage: \(String(format: "%.2f%%", averageCoverage)), Pending Sync: \(pendingSyncOperations)" + let syncTimeStr = lastSyncTime?.timeAgoString() ?? "Never" + return "Programs: \(totalPrograms) (DB: \(databasePrograms)), Executions: \(totalExecutions) (DB: \(databaseExecutions)), Coverage: \(String(format: "%.2f%%", averageCoverage)), Crashes: \(databaseCrashes), Active Fuzzers: \(activeFuzzers), Last Sync: \(syncTimeStr)" + } +} + +/// Database statistics from PostgreSQL +public struct DatabaseStatistics { + public let totalPrograms: Int + public let totalExecutions: Int + public let totalCrashes: Int + public let activeFuzzers: Int + + public init(totalPrograms: Int = 0, totalExecutions: Int = 0, totalCrashes: Int = 0, activeFuzzers: Int = 0) { + self.totalPrograms = totalPrograms + self.totalExecutions = totalExecutions + self.totalCrashes = totalCrashes + self.activeFuzzers = activeFuzzers + } +} + +extension Date { + func timeAgoString() -> String { + let interval = Date().timeIntervalSince(self) + let minutes = Int(interval / 60) + let hours = Int(interval / 3600) + let days = Int(interval / 86400) + + if days > 0 { + return "\(days)d ago" + } else if hours > 0 { + return "\(hours)h ago" + } else if minutes > 0 { + return "\(minutes)m ago" + } else { + return "Just now" + } } } diff --git a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift index b68006c2d..4bf92a491 100644 --- a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift +++ b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift @@ -140,6 +140,70 @@ public class PostgreSQLStorage { return fuzzer } + /// Get database statistics for a specific fuzzer + public func getDatabaseStatistics(fuzzerId: Int) async throws -> DatabaseStatistics { + logger.debug("Getting database statistics for fuzzer: \(fuzzerId)") + + // Use direct connection to avoid connection pool deadlock + guard let eventLoopGroup = databasePool.getEventLoopGroup() else { + throw PostgreSQLStorageError.noResult + } + + let connection = try await PostgresConnection.connect( + on: eventLoopGroup.next(), + configuration: PostgresConnection.Configuration( + host: "localhost", + port: 5433, + username: "fuzzilli", + password: "fuzzilli123", + database: "fuzzilli", + tls: .disable + ), + id: 0, + logger: logger + ) + defer { Task { _ = try? await connection.close() } } + + // Get program count for this fuzzer + let programQuery: PostgresQuery = "SELECT COUNT(*) FROM fuzzer WHERE fuzzer_id = \(fuzzerId)" + let programResult = try await connection.query(programQuery, logger: self.logger) + let programRows = try await programResult.collect() + let totalPrograms = try programRows.first?.decode(Int.self, context: .default) ?? 0 + + // Get execution count for this fuzzer + let executionQuery: PostgresQuery = "SELECT COUNT(*) FROM execution e JOIN program p ON e.program_base64 = p.program_base64 WHERE p.fuzzer_id = \(fuzzerId)" + let executionResult = try await connection.query(executionQuery, logger: self.logger) + let executionRows = try await executionResult.collect() + let totalExecutions = try executionRows.first?.decode(Int.self, context: .default) ?? 0 + + // Get crash count for this fuzzer + let crashQuery: PostgresQuery = """ + SELECT COUNT(*) FROM execution e + JOIN program p ON e.program_base64 = p.program_base64 + JOIN execution_outcome eo ON e.execution_outcome_id = eo.id + WHERE p.fuzzer_id = \(fuzzerId) AND eo.outcome = 'Crashed' + """ + let crashResult = try await connection.query(crashQuery, logger: self.logger) + let crashRows = try await crashResult.collect() + let totalCrashes = try crashRows.first?.decode(Int.self, context: .default) ?? 0 + + // Get active fuzzers count + let activeQuery: PostgresQuery = "SELECT COUNT(*) FROM main WHERE status = 'active'" + let activeResult = try await connection.query(activeQuery, logger: self.logger) + let activeRows = try await activeResult.collect() + let activeFuzzers = try activeRows.first?.decode(Int.self, context: .default) ?? 0 + + let stats = DatabaseStatistics( + totalPrograms: totalPrograms, + totalExecutions: totalExecutions, + totalCrashes: totalCrashes, + activeFuzzers: activeFuzzers + ) + + self.logger.debug("Database statistics: Programs: \(stats.totalPrograms), Executions: \(stats.totalExecutions), Crashes: \(stats.totalCrashes), Active Fuzzers: \(stats.activeFuzzers)") + return stats + } + // MARK: - Program Management /// Store multiple programs in batch for better performance @@ -176,17 +240,11 @@ public class PostgreSQLStorage { let programBase64 = DatabaseUtils.encodeProgramToBase64(program: program) programHashes.append(programHash) - // Generate JavaScript code from the program - let lifter = JavaScriptLifter(ecmaVersion: .es6) - let javascriptCode = lifter.lift(program, withOptions: []) - let javascriptCodeBase64 = Data(javascriptCode.utf8).base64EncodedString() - // Escape single quotes in strings let escapedProgramBase64 = programBase64.replacingOccurrences(of: "'", with: "''") - let escapedJavascriptCodeBase64 = javascriptCodeBase64.replacingOccurrences(of: "'", with: "''") fuzzerValues.append("('\(escapedProgramBase64)', \(fuzzerId), \(program.size), '\(programHash)')") - programValues.append("('\(escapedProgramBase64)', \(fuzzerId), \(program.size), '\(programHash)', '\(escapedJavascriptCodeBase64)')") + programValues.append("('\(escapedProgramBase64)', \(fuzzerId), \(program.size), '\(programHash)')") } // Batch insert into fuzzer table @@ -199,8 +257,10 @@ public class PostgreSQLStorage { // Batch insert into program table if !programValues.isEmpty { - let programQueryString = "INSERT INTO program (program_base64, fuzzer_id, program_size, program_hash, javascript_code) VALUES " + - programValues.joined(separator: ", ") + " ON CONFLICT (program_base64) DO NOTHING" + let programQueryString = "INSERT INTO program (program_base64, fuzzer_id, program_size, program_hash) VALUES " + + programValues.joined(separator: ", ") + " ON CONFLICT (program_base64) DO UPDATE SET " + + "fuzzer_id = EXCLUDED.fuzzer_id, program_size = EXCLUDED.program_size, " + + "program_hash = EXCLUDED.program_hash" let programQuery = PostgresQuery(stringLiteral: programQueryString) try await connection.query(programQuery, logger: self.logger) } @@ -208,6 +268,118 @@ public class PostgreSQLStorage { return programHashes } + /// Store both program and execution in a single transaction to avoid foreign key issues + public func storeProgramAndExecution( + program: Program, + fuzzerId: Int, + executionType: DatabaseExecutionPurpose, + outcome: ExecutionOutcome, + coverage: Double, + executionTimeMs: Int, + stdout: String?, + stderr: String?, + fuzzout: String?, + metadata: ExecutionMetadata + ) async throws -> (programHash: String, executionId: Int) { + let programHash = DatabaseUtils.calculateProgramHash(program: program) + let programBase64 = DatabaseUtils.encodeProgramToBase64(program: program) + logger.debug("Storing program and execution: hash=\(programHash), fuzzerId=\(fuzzerId), executionCount=\(metadata.executionCount)") + + // Use direct connection to avoid connection pool deadlock + guard let eventLoopGroup = databasePool.getEventLoopGroup() else { + throw PostgreSQLStorageError.noResult + } + + let connection = try await PostgresConnection.connect( + on: eventLoopGroup.next(), + configuration: PostgresConnection.Configuration( + host: "localhost", + port: 5433, + username: "fuzzilli", + password: "fuzzilli123", + database: "fuzzilli", + tls: .disable + ), + id: 0, + logger: logger + ) + defer { Task { _ = try? await connection.close() } } + + // Start transaction + try await connection.query("BEGIN", logger: self.logger) + + do { + // Insert into fuzzer table (corpus) + let fuzzerQuery = PostgresQuery(stringLiteral: """ + INSERT INTO fuzzer (program_base64, fuzzer_id, program_size, program_hash) + VALUES ('\(programBase64)', \(fuzzerId), \(program.size), '\(programHash)') + ON CONFLICT (program_base64) DO NOTHING + """) + try await connection.query(fuzzerQuery, logger: self.logger) + + // Insert into program table (executed programs) - use DO UPDATE to ensure it exists + let programQuery = PostgresQuery(stringLiteral: """ + INSERT INTO program (program_base64, fuzzer_id, program_size, program_hash) + VALUES ('\(programBase64)', \(fuzzerId), \(program.size), '\(programHash)') + ON CONFLICT (program_base64) DO UPDATE SET + fuzzer_id = EXCLUDED.fuzzer_id, + program_size = EXCLUDED.program_size, + program_hash = EXCLUDED.program_hash + """) + try await connection.query(programQuery, logger: self.logger) + + // Now store the execution + let executionTypeId = DatabaseUtils.mapExecutionType(purpose: executionType) + + // Extract execution metadata from ExecutionOutcome + let (signalCode, exitCode) = extractExecutionMetadata(from: outcome) + + // Use signal-aware mapping for execution outcomes + let outcomeId = DatabaseUtils.mapExecutionOutcomeWithSignal(outcome: outcome, signalCode: signalCode) + + // Prepare parameters for NULL handling + let signalCodeValue = signalCode != nil ? "\(signalCode!)" : "NULL" + let exitCodeValue = exitCode != nil ? "\(exitCode!)" : "NULL" + let stdoutValue = stdout != nil ? "'\(stdout!.replacingOccurrences(of: "'", with: "''"))'" : "NULL" + let stderrValue = stderr != nil ? "'\(stderr!.replacingOccurrences(of: "'", with: "''"))'" : "NULL" + let fuzzoutValue = fuzzout != nil ? "'\(fuzzout!.replacingOccurrences(of: "'", with: "''"))'" : "NULL" + + let executionQuery = PostgresQuery(stringLiteral: """ + INSERT INTO execution ( + program_base64, execution_type_id, mutator_type_id, + execution_outcome_id, coverage_total, execution_time_ms, + signal_code, exit_code, stdout, stderr, fuzzout, + feedback_vector, created_at + ) VALUES ( + '\(programBase64)', \(executionTypeId), + NULL, \(outcomeId), \(coverage), + \(executionTimeMs), \(signalCodeValue), \(exitCodeValue), + \(stdoutValue), \(stderrValue), \(fuzzoutValue), + NULL, NOW() + ) RETURNING execution_id + """) + + let result = try await connection.query(executionQuery, logger: self.logger) + let rows = try await result.collect() + guard let row = rows.first else { + throw PostgreSQLStorageError.noResult + } + + let executionId = try row.decode(Int.self, context: PostgresDecodingContext.default) + + // Commit transaction + try await connection.query("COMMIT", logger: self.logger) + + self.logger.debug("Program and execution storage successful: hash=\(programHash), executionId=\(executionId)") + return (programHash, executionId) + + } catch { + // Rollback transaction on error + try await connection.query("ROLLBACK", logger: self.logger) + throw error + } + } + /// Store a program in the database with execution metadata public func storeProgram(program: Program, fuzzerId: Int, metadata: ExecutionMetadata) async throws -> String { let programHash = DatabaseUtils.calculateProgramHash(program: program) @@ -237,28 +409,25 @@ public class PostgreSQLStorage { // Insert into fuzzer table (corpus) let fuzzerQuery: PostgresQuery = """ INSERT INTO fuzzer (program_base64, fuzzer_id, program_size, program_hash) - VALUES (\(programBase64), \(fuzzerId), \(program.size), \(programHash)) + VALUES ('\(programBase64)', \(fuzzerId), \(program.size), '\(programHash)') ON CONFLICT (program_base64) DO NOTHING """ try await connection.query(fuzzerQuery, logger: self.logger) // Generate JavaScript code from the program let lifter = JavaScriptLifter(ecmaVersion: .es6) - let javascriptCode = lifter.lift(program, withOptions: []) + _ = lifter.lift(program, withOptions: []) // Insert into program table (executed programs) - // Base64 encode the JavaScript code to avoid SQL injection issues - let javascriptCodeBase64 = Data(javascriptCode.utf8).base64EncodedString() - - // Use string concatenation to avoid parameter substitution issues - let programQueryString = "INSERT INTO program (program_base64, fuzzer_id, program_size, program_hash, javascript_code) VALUES ('" + - programBase64 + "', " + - String(fuzzerId) + ", " + - String(program.size) + ", '" + - programHash + "', '" + - javascriptCodeBase64 + "') ON CONFLICT (program_base64) DO NOTHING" - - let programQuery = PostgresQuery(stringLiteral: programQueryString) + // Use DO UPDATE to ensure the program exists for foreign key constraints + let programQuery: PostgresQuery = """ + INSERT INTO program (program_base64, fuzzer_id, program_size, program_hash) + VALUES ('\(programBase64)', \(fuzzerId), \(program.size), '\(programHash)') + ON CONFLICT (program_base64) DO UPDATE SET + fuzzer_id = EXCLUDED.fuzzer_id, + program_size = EXCLUDED.program_size, + program_hash = EXCLUDED.program_hash + """ try await connection.query(programQuery, logger: self.logger) self.logger.debug("Program storage successful: hash=\(programHash)") @@ -320,7 +489,6 @@ public class PostgreSQLStorage { let programBase64 = DatabaseUtils.encodeProgramToBase64(program: executionData.program) let executionTypeId = DatabaseUtils.mapExecutionType(purpose: executionData.executionType) - let mutatorTypeId = executionData.mutatorType != nil ? DatabaseUtils.mapMutatorType(mutator: executionData.mutatorType!) : nil // Extract execution metadata from ExecutionOutcome let (signalCode, exitCode) = extractExecutionMetadata(from: executionData.outcome) @@ -328,7 +496,8 @@ public class PostgreSQLStorage { // Use signal-aware mapping for execution outcomes let outcomeId = DatabaseUtils.mapExecutionOutcomeWithSignal(outcome: executionData.outcome, signalCode: signalCode) - let mutatorTypeValue = mutatorTypeId != nil ? "\(mutatorTypeId!)" : "NULL" + // Store mutator name as text instead of ID + let mutatorTypeValue = executionData.mutatorType != nil ? "'\(executionData.mutatorType!.replacingOccurrences(of: "'", with: "''"))'" : "NULL" let feedbackVectorValue = executionData.feedbackVector != nil ? "'\(executionData.feedbackVector!.base64EncodedString())'" : "NULL" let signalCodeValue = signalCode != nil ? "\(signalCode!)" : "NULL" let exitCodeValue = exitCode != nil ? "\(exitCode!)" : "NULL" @@ -409,7 +578,6 @@ public class PostgreSQLStorage { defer { Task { _ = try? await connection.close() } } let executionTypeId = DatabaseUtils.mapExecutionType(purpose: executionType) - let mutatorTypeId = mutatorType != nil ? DatabaseUtils.mapMutatorType(mutator: mutatorType!) : nil // Extract execution metadata from ExecutionOutcome let (signalCode, exitCode) = extractExecutionMetadata(from: outcome) @@ -417,7 +585,8 @@ public class PostgreSQLStorage { // Use signal-aware mapping for execution outcomes let outcomeId = DatabaseUtils.mapExecutionOutcomeWithSignal(outcome: outcome, signalCode: signalCode) - let mutatorTypeValue = mutatorTypeId != nil ? "\(mutatorTypeId!)" : "NULL" + // Prepare parameters for NULL handling - store mutator name as text instead of ID + let mutatorTypeValue = mutatorType != nil ? "'\(mutatorType!.replacingOccurrences(of: "'", with: "''"))'" : "NULL" let feedbackVectorValue = feedbackVector != nil ? "'\(feedbackVector!.base64EncodedString())'" : "NULL" let signalCodeValue = signalCode != nil ? "\(signalCode!)" : "NULL" let exitCodeValue = exitCode != nil ? "\(exitCode!)" : "NULL" @@ -425,7 +594,7 @@ public class PostgreSQLStorage { let stderrValue = stderr != nil ? "'\(stderr!.replacingOccurrences(of: "'", with: "''"))'" : "NULL" let fuzzoutValue = fuzzout != nil ? "'\(fuzzout!.replacingOccurrences(of: "'", with: "''"))'" : "NULL" - let queryString = """ + let query = PostgresQuery(stringLiteral: """ INSERT INTO execution ( program_base64, execution_type_id, mutator_type_id, execution_outcome_id, coverage_total, execution_time_ms, @@ -438,9 +607,7 @@ public class PostgreSQLStorage { \(stdoutValue), \(stderrValue), \(fuzzoutValue), \(feedbackVectorValue), NOW() ) RETURNING execution_id - """ - - let query = PostgresQuery(stringLiteral: queryString) + """) let result = try await connection.query(query, logger: self.logger) let rows = try await result.collect() @@ -448,7 +615,7 @@ public class PostgreSQLStorage { throw PostgreSQLStorageError.noResult } - let executionId = try row.decode(Int.self, context: .default) + let executionId = try row.decode(Int.self, context: PostgresDecodingContext.default) self.logger.debug("Execution storage successful: executionId=\(executionId)") return executionId } diff --git a/Sources/FuzzilliCli/TerminalUI.swift b/Sources/FuzzilliCli/TerminalUI.swift index 1bb5f1c84..291c70690 100755 --- a/Sources/FuzzilliCli/TerminalUI.swift +++ b/Sources/FuzzilliCli/TerminalUI.swift @@ -125,6 +125,28 @@ class TerminalUI { } else { print("Fuzzer Statistics") } + + // Check if we're using PostgreSQL corpus and get additional stats + let isPostgreSQLCorpus = fuzzer.corpus is PostgreSQLCorpus + var postgresStats = "" + + if isPostgreSQLCorpus { + if let postgresCorpus = fuzzer.corpus as? PostgreSQLCorpus { + let corpusStats = postgresCorpus.getStatistics() + + postgresStats = """ + ----------------- + PostgreSQL Database Stats: + Database Programs: \(corpusStats.totalPrograms) + Database Executions: \(corpusStats.totalExecutions) + Avg Coverage (DB): \(String(format: "%.2f%%", corpusStats.averageCoverage)) + Current Coverage (Live): \(String(format: "%.2f%%", corpusStats.currentCoverage * 100)) + Pending Sync Operations: \(corpusStats.pendingSyncOperations) + Fuzzer Instance ID: \(corpusStats.fuzzerInstanceId) + """ + } + } + print(""" ----------------- Fuzzer state: \(state) @@ -146,7 +168,7 @@ class TerminalUI { Execs / Second: \(String(format: "%.2f", stats.execsPerSecond)) Fuzzer Overhead: \(String(format: "%.2f", stats.fuzzerOverhead * 100))% Minimization Overhead: \(String(format: "%.2f", stats.minimizationOverhead * 100))% - Total Execs: \(stats.totalExecs) + Total Execs: \(stats.totalExecs)\(postgresStats) """) } diff --git a/postgres-init.sql b/postgres-init.sql index e6bad8c59..89541b3ed 100644 --- a/postgres-init.sql +++ b/postgres-init.sql @@ -91,7 +91,7 @@ CREATE TABLE IF NOT EXISTS execution ( execution_id SERIAL PRIMARY KEY, program_base64 TEXT NOT NULL REFERENCES program(program_base64) ON DELETE CASCADE, execution_type_id INTEGER NOT NULL REFERENCES execution_type(id), - mutator_type_id INTEGER REFERENCES mutator_type(id), + mutator_type_id TEXT, -- Store mutator name directly instead of ID execution_outcome_id INTEGER NOT NULL REFERENCES execution_outcome(id), -- Execution results @@ -173,14 +173,13 @@ SELECT e.execution_id, e.program_base64, et.title as execution_type, - mt.name as mutator_type, + e.mutator_type_id as mutator_type, -- Use the TEXT field directly eo.outcome as execution_outcome, e.coverage_total, e.execution_time_ms, e.created_at FROM execution e JOIN execution_type et ON e.execution_type_id = et.id -LEFT JOIN mutator_type mt ON e.mutator_type_id = mt.id JOIN execution_outcome eo ON e.execution_outcome_id = eo.id; CREATE OR REPLACE VIEW crash_summary AS From fcb750d44eb631e4fbf302b45cee2344205abbc0 Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Tue, 28 Oct 2025 19:42:39 -0400 Subject: [PATCH 03/23] added postgres coverage analytics --- .../Fuzzilli/Corpus/PostgreSQLCorpus.swift | 99 ++++++------------- .../Fuzzilli/Database/PostgreSQLStorage.swift | 56 +++++------ postgres-init.sql | 16 +++ v8/v8 | 2 +- 4 files changed, 77 insertions(+), 96 deletions(-) diff --git a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift index ea4e985fc..bdd139514 100644 --- a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift +++ b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift @@ -193,6 +193,20 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } } + // Track coverage statistics from evaluator + fuzzer.registerEventListener(for: fuzzer.events.InterestingProgramFound) { ev in + if let coverageEvaluator = self.fuzzer.evaluator as? ProgramCoverageEvaluator { + let currentCoverage = coverageEvaluator.currentScore + + Task { + await self.storeCoverageSnapshot( + coverage: currentCoverage, + programHash: DatabaseUtils.calculateProgramHash(program: ev.program) + ) + } + } + } + // Listen for PreExecute events to track the program being executed fuzzer.registerEventListener(for: fuzzer.events.PreExecute) { (program, purpose) in // Store the program and purpose for the next PostExecute event @@ -694,7 +708,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { // DEBUG: Log execution storage attempt logger.info("Storing execution: fuzzerId=\(fuzzerId), outcome=\(executionData.outcome), execTime=\(executionData.execTime)") - + // Store both program and execution in a single transaction to avoid foreign key issues _ = try await storage.storeProgramAndExecution( program: program, @@ -712,7 +726,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { description: aspects.outcome.description )) ) - + logger.info("Successfully stored program and execution") } catch { @@ -720,76 +734,27 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } } - /// Store execution with full metadata from Execution object - private func storeExecutionWithMetadata(_ program: Program, _ execution: Execution, _ executionType: DatabaseExecutionPurpose, _ aspects: ProgramAspects) async { - do { - // Use the registered fuzzer ID - guard let fuzzerId = fuzzerId else { - logger.error("Cannot store execution: fuzzer not registered") - return - } - - // Store the program in the program table - _ = try await storage.storeProgram( - program: program, - fuzzerId: fuzzerId, - metadata: ExecutionMetadata(lastOutcome: DatabaseExecutionOutcome( - id: DatabaseUtils.mapExecutionOutcome(outcome: aspects.outcome), - outcome: aspects.outcome.description, - description: aspects.outcome.description - )) - ) - - // Store the execution record with full execution metadata - _ = try await storage.storeExecution( - program: program, - fuzzerId: fuzzerId, - execution: execution, - executionType: executionType, - coverage: aspects is CovEdgeSet ? Double((aspects as! CovEdgeSet).count) : 0.0 - ) - - // logger.debug("Stored execution with metadata: programHash=\(programHash), executionId=\(executionId), execTime=\(execution.execTime), outcome=\(execution.outcome)") - - } catch { - logger.error("Failed to store execution with metadata: \(error)") + /// Store coverage snapshot to database + private func storeCoverageSnapshot(coverage: Double, programHash: String) async { + guard let fuzzerId = fuzzerId else { + logger.info("Cannot store coverage snapshot: fuzzer not registered") + return } - } - - /// Store a program execution in the database - private func storeExecutionInDatabase(_ program: Program, _ aspects: ProgramAspects, executionType: DatabaseExecutionPurpose, mutatorType: String?) async { + do { - // Use the registered fuzzer ID - guard let fuzzerId = fuzzerId else { - logger.error("Cannot store execution: fuzzer not registered") - return - } - - // Store the program in the program table - _ = try await storage.storeProgram( - program: program, - fuzzerId: fuzzerId, - metadata: ExecutionMetadata(lastOutcome: DatabaseExecutionOutcome( - id: DatabaseUtils.mapExecutionOutcome(outcome: aspects.outcome), - outcome: aspects.outcome.description, - description: aspects.outcome.description - )) - ) - - // Store the execution record - _ = try await storage.storeExecution( - program: program, - fuzzerId: fuzzerId, - executionType: executionType, - mutatorType: mutatorType, - outcome: aspects.outcome, - coverage: aspects is CovEdgeSet ? Double((aspects as! CovEdgeSet).count) : 0.0 - ) + let query = PostgresQuery(stringLiteral: """ + INSERT INTO coverage_snapshot ( + fuzzer_id, coverage_percentage, program_hash, created_at + ) VALUES ( + \(fuzzerId), \(coverage), '\(programHash)', NOW() + ) + """) - // logger.debug("Stored execution in database: programHash=\(programHash), executionId=\(executionId)") + try await storage.executeQuery(query) + logger.info("Stored coverage snapshot: \(String(format: "%.6f%%", coverage * 100))") } catch { - logger.error("Failed to store execution in database: \(error)") + logger.error("Failed to store coverage snapshot: \(error)") } } diff --git a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift index 4bf92a491..999802a3d 100644 --- a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift +++ b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift @@ -620,34 +620,6 @@ public class PostgreSQLStorage { return executionId } - /// Store execution record from Execution object - public func storeExecution( - program: Program, - fuzzerId: Int, - execution: Execution, - executionType: DatabaseExecutionPurpose, - mutatorType: String? = nil, - coverage: Double = 0.0, - feedbackVector: Data? = nil, - coverageEdges: Set = [] - ) async throws -> Int { - let executionTimeMs = Int(execution.execTime * 1000) // Convert to milliseconds - return try await storeExecution( - program: program, - fuzzerId: fuzzerId, - executionType: executionType, - mutatorType: mutatorType, - outcome: execution.outcome, - coverage: coverage, - executionTimeMs: executionTimeMs, - feedbackVector: feedbackVector, - coverageEdges: coverageEdges, - stdout: execution.stdout, - stderr: execution.stderr, - fuzzout: execution.fuzzout - ) - } - /// Extract execution metadata from ExecutionOutcome private func extractExecutionMetadata(from outcome: ExecutionOutcome) -> (signalCode: Int?, exitCode: Int?) { switch outcome { @@ -912,4 +884,32 @@ public enum PostgreSQLStorageError: Error, LocalizedError { return "Database query failed: \(message)" } } +} + +// MARK: - Coverage Tracking Methods + +extension PostgreSQLStorage { + /// Execute a simple query without expecting results + public func executeQuery(_ query: PostgresQuery) async throws { + guard let eventLoopGroup = databasePool.getEventLoopGroup() else { + throw PostgreSQLStorageError.noResult + } + + let connection = try await PostgresConnection.connect( + on: eventLoopGroup.next(), + configuration: PostgresConnection.Configuration( + host: "localhost", + port: 5433, + username: "fuzzilli", + password: "fuzzilli123", + database: "fuzzilli", + tls: .disable + ), + id: 0, + logger: logger + ) + defer { Task { _ = try? await connection.close() } } + + try await connection.query(query, logger: self.logger) + } } \ No newline at end of file diff --git a/postgres-init.sql b/postgres-init.sql index 89541b3ed..0f8da45e4 100644 --- a/postgres-init.sql +++ b/postgres-init.sql @@ -149,6 +149,22 @@ CREATE TABLE IF NOT EXISTS crash_analysis ( created_at TIMESTAMP DEFAULT NOW() ); +-- Coverage tracking over time +CREATE TABLE IF NOT EXISTS coverage_snapshot ( + snapshot_id SERIAL PRIMARY KEY, + fuzzer_id INTEGER NOT NULL, + coverage_percentage NUMERIC(10, 8) NOT NULL, + program_hash TEXT, + edges_found INTEGER, + total_edges INTEGER, + created_at TIMESTAMP DEFAULT NOW(), + + FOREIGN KEY (fuzzer_id) REFERENCES main(fuzzer_id) +); + +CREATE INDEX IF NOT EXISTS idx_coverage_snapshot_fuzzer ON coverage_snapshot(fuzzer_id); +CREATE INDEX IF NOT EXISTS idx_coverage_snapshot_created ON coverage_snapshot(created_at); + -- Create performance indexes CREATE INDEX IF NOT EXISTS idx_execution_program ON execution(program_base64); CREATE INDEX IF NOT EXISTS idx_execution_type ON execution(execution_type_id); diff --git a/v8/v8 b/v8/v8 index 99071b00c..01af089bd 160000 --- a/v8/v8 +++ b/v8/v8 @@ -1 +1 @@ -Subproject commit 99071b00caf56028957494f778acc8c21c2c8a4b +Subproject commit 01af089bd89645143fc60f0da72267f95645afb3 From ad2b8f811ea86fef8e117e7016fe0cb74f30e9de Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Tue, 28 Oct 2025 19:47:12 -0400 Subject: [PATCH 04/23] added proper run fuzzilli --- runFuzzilli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runFuzzilli.sh b/runFuzzilli.sh index fe7789a51..b5930c55f 100755 --- a/runFuzzilli.sh +++ b/runFuzzilli.sh @@ -1 +1 @@ -swift run FuzzilliCli --profile=v8 --engine=multi --resume --corpus=basic --storagePath=./Corpus --logLevel=verbose --timeout=1500 --diagnostics /usr/share/vrigatoni/v8_2/v8/out/fuzzbuild/d8 +swift run FuzzilliCli --profile=v8 --engine=multi --resume --corpus=postgresql --postgres-url="postgresql://fuzzilli:fuzzilli123@localhost:5433/fuzzilli" --storagePath=./Corpus --logLevel=verbose --timeout=1500 --diagnostics --maxIterations=10 ~/vrig/v8/v8/out/fuzzbuild/d8 From 388cdc46dce193b2c69f7814aac583dbb4fc70c4 Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Tue, 28 Oct 2025 20:26:06 -0400 Subject: [PATCH 05/23] added helper script to query --- Sources/FuzzilliCli/main.swift | 3 +- query_db.sh | 243 +++++++++++++++++++++++++++++++++ runFuzzilli.sh | 2 +- 3 files changed, 246 insertions(+), 2 deletions(-) create mode 100755 query_db.sh diff --git a/Sources/FuzzilliCli/main.swift b/Sources/FuzzilliCli/main.swift index 7f4867017..0ea1be5dd 100755 --- a/Sources/FuzzilliCli/main.swift +++ b/Sources/FuzzilliCli/main.swift @@ -542,7 +542,8 @@ func makeFuzzer(with configuration: Configuration) -> Fuzzer { maxSize: maxCorpusSize, minMutationsPerSample: minMutationsPerSample, databasePool: databasePool, - fuzzerInstanceId: fuzzerInstanceId + fuzzerInstanceId: fuzzerInstanceId, + resume: resume ) logger.info("Created PostgreSQL corpus with instance ID: \(fuzzerInstanceId)") diff --git a/query_db.sh b/query_db.sh new file mode 100755 index 000000000..895ed8b1c --- /dev/null +++ b/query_db.sh @@ -0,0 +1,243 @@ +#!/bin/bash + +# Fuzzilli PostgreSQL Database Query Script using Docker +# This script uses Docker to connect to the PostgreSQL container and run queries + +# Database connection parameters +DB_CONTAINER="fuzzilli-postgres" # Adjust this to match your container name +DB_NAME="fuzzilli" +DB_USER="fuzzilli" +DB_PASSWORD="fuzzilli123" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to run a query using Docker +run_query() { + local title="$1" + local query="$2" + + echo -e "\n${BLUE}=== $title ===${NC}" + echo -e "${YELLOW}Query:${NC} $query" + echo -e "${GREEN}Results:${NC}" + + docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -c "$query" 2>/dev/null + + if [ $? -ne 0 ]; then + echo -e "${RED}Error: Failed to execute query${NC}" + fi +} + +# Function to check if Docker is available +check_docker() { + if ! command -v docker &> /dev/null; then + echo -e "${RED}Error: Docker command not found. Please install Docker.${NC}" + exit 1 + fi +} + +# Function to check if PostgreSQL container is running +check_container() { + if ! docker ps --format "table {{.Names}}" | grep -q "$DB_CONTAINER"; then + echo -e "${RED}Error: PostgreSQL container '$DB_CONTAINER' is not running${NC}" + echo "Available containers:" + docker ps --format "table {{.Names}}\t{{.Status}}" + echo "" + echo "Please start the PostgreSQL container or update the DB_CONTAINER variable in this script" + exit 1 + fi +} + +# Function to test database connection +test_connection() { + echo -e "${BLUE}Testing database connection...${NC}" + docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -c "SELECT version();" &>/dev/null + + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Database connection successful${NC}" + else + echo -e "${RED}✗ Database connection failed${NC}" + echo "Please check:" + echo "1. PostgreSQL container is running" + echo "2. Database credentials are correct" + echo "3. Container name is correct" + exit 1 + fi +} + +# Main execution +main() { + echo -e "${GREEN}Fuzzilli Database Query Tool (Docker)${NC}" + echo "=============================================" + + check_docker + check_container + test_connection + + # Basic database info + run_query "Database Information" "SELECT current_database() as database_name, current_user as user_name, version() as postgres_version;" + + # List all tables + run_query "Available Tables" "SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name;" + + # Program statistics + run_query "Program Count by Fuzzer" " + SELECT + p.fuzzer_id, + COUNT(*) as program_count, + MIN(p.created_at) as first_program, + MAX(p.created_at) as latest_program + FROM program p + GROUP BY p.fuzzer_id + ORDER BY program_count DESC; + " + + # Total program count + run_query "Total Program Statistics" " + SELECT + COUNT(*) as total_programs, + COUNT(DISTINCT fuzzer_id) as active_fuzzers, + AVG(program_size) as avg_program_size, + MAX(program_size) as max_program_size, + MIN(created_at) as first_program, + MAX(created_at) as latest_program + FROM program; + " + + # Execution statistics + run_query "Execution Statistics" " + SELECT + eo.outcome, + COUNT(*) as count, + ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) as percentage + FROM execution e + JOIN execution_outcome eo ON e.execution_outcome_id = eo.id + GROUP BY eo.outcome + ORDER BY count DESC; + " + + # Recent programs + run_query "Recent Programs (Last 10)" " + SELECT + LEFT(program_base64, 20) as program_preview, + fuzzer_id, + LEFT(program_hash, 12) as hash_prefix, + program_size, + created_at + FROM program + ORDER BY created_at DESC + LIMIT 10; + " + + # Recent executions + run_query "Recent Executions (Last 10)" " + SELECT + e.execution_id, + p.fuzzer_id, + LEFT(p.program_hash, 12) as hash_prefix, + eo.outcome, + e.execution_time_ms, + e.created_at + FROM execution e + JOIN program p ON e.program_base64 = p.program_base64 + JOIN execution_outcome eo ON e.execution_outcome_id = eo.id + ORDER BY e.created_at DESC + LIMIT 10; + " + + # Crash analysis + run_query "Crash Analysis" " + SELECT + p.fuzzer_id, + COUNT(*) as crash_count, + MIN(e.created_at) as first_crash, + MAX(e.created_at) as latest_crash + FROM execution e + JOIN program p ON e.program_base64 = p.program_base64 + JOIN execution_outcome eo ON e.execution_outcome_id = eo.id + WHERE eo.outcome = 'Crashed' + GROUP BY p.fuzzer_id + ORDER BY crash_count DESC; + " + + # Coverage statistics + run_query "Coverage Statistics" " + SELECT + COUNT(*) as executions_with_coverage, + AVG(coverage_total) as avg_coverage_percentage, + MAX(coverage_total) as max_coverage_percentage, + COUNT(CASE WHEN coverage_total > 0 THEN 1 END) as executions_with_positive_coverage + FROM execution + WHERE coverage_total IS NOT NULL; + " + + # Performance metrics + run_query "Performance Metrics" " + SELECT + AVG(execution_time_ms) as avg_execution_time_ms, + MIN(execution_time_ms) as min_execution_time_ms, + MAX(execution_time_ms) as max_execution_time_ms, + COUNT(*) as total_executions + FROM execution + WHERE execution_time_ms > 0; + " + + # Database size info + run_query "Database Size Information" " + SELECT + schemaname, + tablename, + pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size + FROM pg_tables + WHERE schemaname = 'public' + ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC; + " + + echo -e "\n${GREEN}Database query completed successfully!${NC}" +} + +# Handle command line arguments +case "${1:-}" in + "programs") + run_query "All Programs" "SELECT LEFT(program_base64, 30) as program_preview, fuzzer_id, program_size, created_at FROM program ORDER BY created_at DESC LIMIT 20;" + ;; + "executions") + run_query "All Executions" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, eo.outcome, e.execution_time_ms, e.created_at FROM execution e JOIN program p ON e.program_base64 = p.program_base64 JOIN execution_outcome eo ON e.execution_outcome_id = eo.id ORDER BY e.created_at DESC LIMIT 20;" + ;; + "crashes") + run_query "All Crashes" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, e.stdout, e.stderr, e.created_at FROM execution e JOIN program p ON e.program_base64 = p.program_base64 JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 20;" + ;; + "stats") + run_query "Quick Stats" "SELECT COUNT(*) as programs, (SELECT COUNT(*) FROM execution) as executions, (SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed') as crashes FROM program;" + ;; + "containers") + echo -e "${BLUE}Available PostgreSQL containers:${NC}" + docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(postgres|fuzzilli)" + ;; + "help"|"-h"|"--help") + echo "Usage: $0 [command]" + echo "" + echo "Commands:" + echo " (no args) - Run full database analysis" + echo " programs - Show recent programs" + echo " executions - Show recent executions" + echo " crashes - Show recent crashes" + echo " stats - Show quick statistics" + echo " containers - List available PostgreSQL containers" + echo " help - Show this help" + echo "" + echo "Note: Update DB_CONTAINER variable in script if your container has a different name" + ;; + "") + main + ;; + *) + echo "Unknown command: $1" + echo "Use '$0 help' for usage information" + exit 1 + ;; +esac \ No newline at end of file diff --git a/runFuzzilli.sh b/runFuzzilli.sh index b5930c55f..68baab81e 100755 --- a/runFuzzilli.sh +++ b/runFuzzilli.sh @@ -1 +1 @@ -swift run FuzzilliCli --profile=v8 --engine=multi --resume --corpus=postgresql --postgres-url="postgresql://fuzzilli:fuzzilli123@localhost:5433/fuzzilli" --storagePath=./Corpus --logLevel=verbose --timeout=1500 --diagnostics --maxIterations=10 ~/vrig/v8/v8/out/fuzzbuild/d8 +swift run FuzzilliCli --profile=v8 --engine=multi --resume --corpus=postgresql --postgres-url="postgresql://fuzzilli:fuzzilli123@localhost:5433/fuzzilli" --storagePath=./Corpus --logLevel=verbose --timeout=1500 --diagnostics ~/vrig/v8/v8/out/fuzzbuild/d8 From 7f28c7938840e8e004bffb6201abcc0c35e71cb7 Mon Sep 17 00:00:00 2001 From: Chase Killorin Date: Tue, 28 Oct 2025 20:31:33 -0400 Subject: [PATCH 06/23] Script fixing again --- Scripts/RunFuzzilli.sh | 25 ++++++++++++++++++++++++- runFuzzilli.sh | 1 + 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100755 runFuzzilli.sh diff --git a/Scripts/RunFuzzilli.sh b/Scripts/RunFuzzilli.sh index b0a83fbe2..1ee8d6ea0 100755 --- a/Scripts/RunFuzzilli.sh +++ b/Scripts/RunFuzzilli.sh @@ -1 +1,24 @@ -swift run FuzzilliCli --profile=v8 --engine=multi --corpus=postgresql --postgres-url=postgresql://fuzzilli:password@localhost:5432/fuzzilli --logLevel=verbose --timeout=1500 --diagnostics ~/projects/ritsec/vrig/vrigatoni/v8/out/fuzzbuild/d8 \ No newline at end of file +#!/bin/sh +set -e + +# Check if the first argument (path to d8) is provided and points to a file +if [ -z "$1" ] || [ ! -f "$1" ]; then + echo "Error: Please provide the path to the 'd8' binary as the first argument." + echo "Usage: $0 /path/to/d8" + exit 1 +fi + +# Assign the provided path to a variable for clarity +D8_PATH="$1" + +# Run Fuzzilli with the provided d8 path +swift run FuzzilliCli \ + --profile=v8 \ + --engine=multi \ + --resume \ + --corpus=postgresql \ + --postgres-url="postgresql://fuzzilli:fuzzilli123@localhost:5433/fuzzilli" \ + --storagePath=./Corpus \ + --logLevel=verbose \ + --timeout=1500 \ + --diagnostics "$D8_PATH" \ No newline at end of file diff --git a/runFuzzilli.sh b/runFuzzilli.sh new file mode 100755 index 000000000..eff2bd0a4 --- /dev/null +++ b/runFuzzilli.sh @@ -0,0 +1 @@ +swift run FuzzilliCli --profile=v8 --engine=multi --resume --corpus=postgresql --postgres-url="postgresql://fuzzilli:fuzzilli123@localhost:5433/fuzzilli" --storagePath=./Corpus --logLevel=verbose --timeout=1500 --diagnostics ~/projects/ritsec/vrig/vrigatoni/v8/out/fuzzbuild/d8 From bc0df4cb4beb18d5cf5afd2f9661034ca62ab6b5 Mon Sep 17 00:00:00 2001 From: Chase Killorin Date: Tue, 28 Oct 2025 20:38:54 -0400 Subject: [PATCH 07/23] Run fuzzilli script cleaner --- Scripts/RunFuzzilli.sh | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Scripts/RunFuzzilli.sh b/Scripts/RunFuzzilli.sh index 1ee8d6ea0..71c86c527 100755 --- a/Scripts/RunFuzzilli.sh +++ b/Scripts/RunFuzzilli.sh @@ -1,17 +1,12 @@ #!/bin/sh set -e -# Check if the first argument (path to d8) is provided and points to a file if [ -z "$1" ] || [ ! -f "$1" ]; then echo "Error: Please provide the path to the 'd8' binary as the first argument." echo "Usage: $0 /path/to/d8" exit 1 fi -# Assign the provided path to a variable for clarity -D8_PATH="$1" - -# Run Fuzzilli with the provided d8 path swift run FuzzilliCli \ --profile=v8 \ --engine=multi \ @@ -21,4 +16,4 @@ swift run FuzzilliCli \ --storagePath=./Corpus \ --logLevel=verbose \ --timeout=1500 \ - --diagnostics "$D8_PATH" \ No newline at end of file + --diagnostics "$1" \ No newline at end of file From ba441558e45f4b0882fba2f9b269996f78f9b4ce Mon Sep 17 00:00:00 2001 From: Chase Killorin Date: Tue, 28 Oct 2025 20:55:49 -0400 Subject: [PATCH 08/23] Script clean #7 --- Scripts/RunFuzzilli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/RunFuzzilli.sh b/Scripts/RunFuzzilli.sh index 71c86c527..fbb9f169b 100755 --- a/Scripts/RunFuzzilli.sh +++ b/Scripts/RunFuzzilli.sh @@ -15,5 +15,5 @@ swift run FuzzilliCli \ --postgres-url="postgresql://fuzzilli:fuzzilli123@localhost:5433/fuzzilli" \ --storagePath=./Corpus \ --logLevel=verbose \ - --timeout=1500 \ + --timeout=3000 \ --diagnostics "$1" \ No newline at end of file From 3346d3ca7fe4dceca5cdd08a218c123c1b042d68 Mon Sep 17 00:00:00 2001 From: Chase Killorin Date: Tue, 28 Oct 2025 21:18:30 -0400 Subject: [PATCH 09/23] Optional logging --- Scripts/RunFuzzilli.sh | 13 +- .../Fuzzilli/Corpus/PostgreSQLCorpus.swift | 7 +- Sources/Fuzzilli/Database/DatabasePool.swift | 42 +++++-- .../Fuzzilli/Database/DatabaseSchema.swift | 46 +++++-- .../Fuzzilli/Database/PostgreSQLStorage.swift | 116 +++++++++++++----- Sources/FuzzilliCli/main.swift | 7 +- 6 files changed, 172 insertions(+), 59 deletions(-) diff --git a/Scripts/RunFuzzilli.sh b/Scripts/RunFuzzilli.sh index fbb9f169b..aab301b9a 100755 --- a/Scripts/RunFuzzilli.sh +++ b/Scripts/RunFuzzilli.sh @@ -1,12 +1,20 @@ #!/bin/sh + set -e if [ -z "$1" ] || [ ! -f "$1" ]; then echo "Error: Please provide the path to the 'd8' binary as the first argument." - echo "Usage: $0 /path/to/d8" + echo "Usage: $0 /path/to/d8 [db-log]" + echo " /path/to/d8 : Path to the d8 binary" + echo " db-log : Optional flag to enable PostgreSQL database logging" exit 1 fi +POSTGRES_LOGGING_FLAG="" +if [ "$2" = "db-log" ]; then + POSTGRES_LOGGING_FLAG="--postgres-logging" +fi + swift run FuzzilliCli \ --profile=v8 \ --engine=multi \ @@ -16,4 +24,5 @@ swift run FuzzilliCli \ --storagePath=./Corpus \ --logLevel=verbose \ --timeout=3000 \ - --diagnostics "$1" \ No newline at end of file + $POSTGRES_LOGGING_FLAG \ + --diagnostics "$1" diff --git a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift index bdd139514..a0bdf7a61 100644 --- a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift +++ b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift @@ -26,6 +26,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { private let fuzzerInstanceId: String private let storage: PostgreSQLStorage private let resume: Bool + private let enableLogging: Bool // MARK: - In-Memory Cache @@ -67,7 +68,8 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { databasePool: DatabasePool, fuzzerInstanceId: String, syncInterval: TimeInterval = 60.0, // Default 1 minute sync interval - resume: Bool = true // Default to resume from previous state + resume: Bool = true, // Default to resume from previous state + enableLogging: Bool = false ) { // The corpus must never be empty assert(minSize >= 1) @@ -80,7 +82,8 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { self.fuzzerInstanceId = fuzzerInstanceId self.syncInterval = syncInterval self.resume = resume - self.storage = PostgreSQLStorage(databasePool: databasePool) + self.enableLogging = enableLogging + self.storage = PostgreSQLStorage(databasePool: databasePool, enableLogging: enableLogging) // Set optimized batch size for better throughput (reduced from 1M to 100k for more frequent processing) self.executionBatchSize = 100_000 diff --git a/Sources/Fuzzilli/Database/DatabasePool.swift b/Sources/Fuzzilli/Database/DatabasePool.swift index a85d7834f..11d28f914 100644 --- a/Sources/Fuzzilli/Database/DatabasePool.swift +++ b/Sources/Fuzzilli/Database/DatabasePool.swift @@ -17,12 +17,14 @@ public class DatabasePool { private let maxConnections: Int private let connectionTimeout: TimeInterval private let retryAttempts: Int + private let enableLogging: Bool - public init(connectionString: String, maxConnections: Int = 5, connectionTimeout: TimeInterval = 120.0, retryAttempts: Int = 3) { + public init(connectionString: String, maxConnections: Int = 5, connectionTimeout: TimeInterval = 120.0, retryAttempts: Int = 3, enableLogging: Bool = false) { self.connectionString = connectionString self.maxConnections = maxConnections self.connectionTimeout = connectionTimeout self.retryAttempts = retryAttempts + self.enableLogging = enableLogging self.logger = Logging.Logger(label: "DatabasePool") } @@ -35,14 +37,18 @@ public class DatabasePool { lock.unlock() guard !alreadyInitialized else { - logger.info("Database pool already initialized") + if enableLogging { + logger.info("Database pool already initialized") + } return } - logger.info("Initializing database connection pool...") - logger.info("Connection string: \(connectionString)") - logger.info("Max connections: \(maxConnections)") - logger.info("Connection timeout: \(connectionTimeout)s") + if enableLogging { + logger.info("Initializing database connection pool...") + logger.info("Connection string: \(connectionString)") + logger.info("Max connections: \(maxConnections)") + logger.info("Connection timeout: \(connectionTimeout)s") + } do { // Create event loop group @@ -71,7 +77,9 @@ public class DatabasePool { isInitialized = true lock.unlock() - logger.info("Database connection pool initialized successfully") + if enableLogging { + logger.info("Database connection pool initialized successfully") + } } catch { logger.error("Failed to initialize database connection pool: \(error)") @@ -100,7 +108,9 @@ public class DatabasePool { // Check if we got a result if result.count > 0 { - logger.info("Database connection test successful") + if enableLogging { + logger.info("Database connection test successful") + } return true } else { logger.error("Database connection test failed: no results") @@ -147,11 +157,15 @@ public class DatabasePool { lock.unlock() guard shouldShutdown else { - logger.info("Database pool not initialized, nothing to shutdown") + if enableLogging { + logger.info("Database pool not initialized, nothing to shutdown") + } return } - logger.info("Shutting down database connection pool...") + if enableLogging { + logger.info("Shutting down database connection pool...") + } do { // Shutdown connection pool @@ -170,7 +184,9 @@ public class DatabasePool { isInitialized = false lock.unlock() - logger.info("Database connection pool shutdown complete") + if enableLogging { + logger.info("Database connection pool shutdown complete") + } } catch { logger.error("Error during database pool shutdown: \(error)") @@ -202,7 +218,9 @@ public class DatabasePool { let password = url.password let database = url.path.isEmpty ? nil : String(url.path.dropFirst()) // Remove leading slash - logger.info("Parsed connection: host=\(host), port=\(port), user=\(username), database=\(database ?? "none")") + if enableLogging { + logger.info("Parsed connection: host=\(host), port=\(port), user=\(username), database=\(database ?? "none")") + } return SQLPostgresConfiguration( hostname: host, diff --git a/Sources/Fuzzilli/Database/DatabaseSchema.swift b/Sources/Fuzzilli/Database/DatabaseSchema.swift index 633f50f40..e75c26501 100644 --- a/Sources/Fuzzilli/Database/DatabaseSchema.swift +++ b/Sources/Fuzzilli/Database/DatabaseSchema.swift @@ -4,8 +4,10 @@ import PostgresNIO /// Manages database schema creation and verification public class DatabaseSchema { private let logger: Logger + private let enableLogging: Bool - public init() { + public init(enableLogging: Bool = false) { + self.enableLogging = enableLogging self.logger = Logger(withLabel: "DatabaseSchema") } @@ -237,13 +239,17 @@ public class DatabaseSchema { /// Create all database tables and indexes public func createTables(connection: PostgresConnection) async throws { - logger.info("Creating database schema...") + if enableLogging { + logger.info("Creating database schema...") + } do { let query = PostgresQuery(stringLiteral: DatabaseSchema.schemaSQL) let result = try await connection.query(query, logger: Logging.Logger(label: "DatabaseSchema")) - logger.info("Database schema created successfully") - logger.info("Schema SQL length: \(DatabaseSchema.schemaSQL.count) characters") + if enableLogging { + logger.info("Database schema created successfully") + logger.info("Schema SQL length: \(DatabaseSchema.schemaSQL.count) characters") + } } catch { logger.error("Failed to create database schema: \(error)") throw error @@ -252,7 +258,9 @@ public class DatabaseSchema { /// Verify that all required tables exist public func verifySchema(connection: PostgresConnection) async throws -> Bool { - logger.info("Verifying database schema...") + if enableLogging { + logger.info("Verifying database schema...") + } let requiredTables = ["main", "fuzzer", "program", "execution_type", "mutator_type", "execution_outcome", "execution", "feedback_vector_detail", "coverage_detail", "crash_analysis"] @@ -272,7 +280,9 @@ public class DatabaseSchema { } } - logger.info("Database schema verification successful") + if enableLogging { + logger.info("Database schema verification successful") + } return true } @@ -291,7 +301,9 @@ public class DatabaseSchema { /// Get lookup table data public func getExecutionTypes(connection: PostgresConnection) async throws -> [ExecutionType] { - logger.info("Getting execution types from database...") + if enableLogging { + logger.info("Getting execution types from database...") + } let query: PostgresQuery = "SELECT id, title, description FROM execution_type ORDER BY id" let result = try await connection.query(query, logger: Logging.Logger(label: "DatabaseSchema")) @@ -304,12 +316,16 @@ public class DatabaseSchema { executionTypes.append(ExecutionType(id: id, title: title, description: description)) } - logger.info("Retrieved \(executionTypes.count) execution types") + if enableLogging { + logger.info("Retrieved \(executionTypes.count) execution types") + } return executionTypes } public func getMutatorTypes(connection: PostgresConnection) async throws -> [MutatorType] { - logger.info("Getting mutator types from database...") + if enableLogging { + logger.info("Getting mutator types from database...") + } let query: PostgresQuery = "SELECT id, name, description, category FROM mutator_type ORDER BY id" let result = try await connection.query(query, logger: Logging.Logger(label: "DatabaseSchema")) @@ -323,12 +339,16 @@ public class DatabaseSchema { mutatorTypes.append(MutatorType(id: id, name: name, description: description, category: category)) } - logger.info("Retrieved \(mutatorTypes.count) mutator types") + if enableLogging { + logger.info("Retrieved \(mutatorTypes.count) mutator types") + } return mutatorTypes } public func getExecutionOutcomes(connection: PostgresConnection) async throws -> [DatabaseExecutionOutcome] { - logger.info("Getting execution outcomes from database...") + if enableLogging { + logger.info("Getting execution outcomes from database...") + } let query: PostgresQuery = "SELECT id, outcome, description FROM execution_outcome ORDER BY id" let result = try await connection.query(query, logger: Logging.Logger(label: "DatabaseSchema")) @@ -341,7 +361,9 @@ public class DatabaseSchema { executionOutcomes.append(DatabaseExecutionOutcome(id: id, outcome: outcome, description: description)) } - logger.info("Retrieved \(executionOutcomes.count) execution outcomes") + if enableLogging { + logger.info("Retrieved \(executionOutcomes.count) execution outcomes") + } return executionOutcomes } } diff --git a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift index 999802a3d..ca63b5ad3 100644 --- a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift +++ b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift @@ -17,11 +17,13 @@ public class PostgreSQLStorage { private let databasePool: DatabasePool private let logger: Logging.Logger + private let enableLogging: Bool // MARK: - Initialization - public init(databasePool: DatabasePool) { + public init(databasePool: DatabasePool, enableLogging: Bool = false) { self.databasePool = databasePool + self.enableLogging = enableLogging self.logger = Logging.Logger(label: "PostgreSQLStorage") } @@ -29,7 +31,9 @@ public class PostgreSQLStorage { /// Register a new fuzzer instance in the database public func registerFuzzer(name: String, engineType: String, hostname: String? = nil) async throws -> Int { - logger.debug("Registering fuzzer: name=\(name), engineType=\(engineType), hostname=\(hostname ?? "none")") + if enableLogging { + logger.info("Registering fuzzer: name=\(name), engineType=\(engineType), hostname=\(hostname ?? "none")") + } // Use direct connection to avoid connection pool deadlock guard let eventLoopGroup = databasePool.getEventLoopGroup() else { @@ -64,9 +68,13 @@ public class PostgreSQLStorage { if existingStatus != "active" { let updateQuery: PostgresQuery = "UPDATE main SET status = 'active' WHERE fuzzer_id = \(existingFuzzerId)" try await connection.query(updateQuery, logger: self.logger) - logger.debug("Reactivated existing fuzzer: fuzzerId=\(existingFuzzerId)") + if enableLogging { + logger.info("Reactivated existing fuzzer: fuzzerId=\(existingFuzzerId)") + } } else { - logger.debug("Reusing existing active fuzzer: fuzzerId=\(existingFuzzerId)") + if enableLogging { + logger.info("Reusing existing active fuzzer: fuzzerId=\(existingFuzzerId)") + } } return existingFuzzerId @@ -86,13 +94,17 @@ public class PostgreSQLStorage { } let fuzzerId = try row.decode(Int.self, context: .default) - self.logger.debug("Created new fuzzer: fuzzerId=\(fuzzerId)") + if enableLogging { + self.logger.info("Created new fuzzer: fuzzerId=\(fuzzerId)") + } return fuzzerId } /// Get fuzzer instance by name public func getFuzzer(name: String) async throws -> FuzzerInstance? { - logger.debug("Getting fuzzer: name=\(name)") + if enableLogging { + logger.info("Getting fuzzer: name=\(name)") + } // Use direct connection to avoid connection pool deadlock guard let eventLoopGroup = databasePool.getEventLoopGroup() else { @@ -136,13 +148,17 @@ public class PostgreSQLStorage { status: status ) - self.logger.debug("Fuzzer found: \(fuzzerName) (ID: \(fuzzerId))") + if enableLogging { + self.logger.info("Fuzzer found: \(fuzzerName) (ID: \(fuzzerId))") + } return fuzzer } /// Get database statistics for a specific fuzzer public func getDatabaseStatistics(fuzzerId: Int) async throws -> DatabaseStatistics { - logger.debug("Getting database statistics for fuzzer: \(fuzzerId)") + if enableLogging { + logger.info("Getting database statistics for fuzzer: \(fuzzerId)") + } // Use direct connection to avoid connection pool deadlock guard let eventLoopGroup = databasePool.getEventLoopGroup() else { @@ -200,7 +216,9 @@ public class PostgreSQLStorage { activeFuzzers: activeFuzzers ) - self.logger.debug("Database statistics: Programs: \(stats.totalPrograms), Executions: \(stats.totalExecutions), Crashes: \(stats.totalCrashes), Active Fuzzers: \(stats.activeFuzzers)") + if enableLogging { + self.logger.info("Database statistics: Programs: \(stats.totalPrograms), Executions: \(stats.totalExecutions), Crashes: \(stats.totalCrashes), Active Fuzzers: \(stats.activeFuzzers)") + } return stats } @@ -283,7 +301,9 @@ public class PostgreSQLStorage { ) async throws -> (programHash: String, executionId: Int) { let programHash = DatabaseUtils.calculateProgramHash(program: program) let programBase64 = DatabaseUtils.encodeProgramToBase64(program: program) - logger.debug("Storing program and execution: hash=\(programHash), fuzzerId=\(fuzzerId), executionCount=\(metadata.executionCount)") + if enableLogging { + logger.info("Storing program and execution: hash=\(programHash), fuzzerId=\(fuzzerId), executionCount=\(metadata.executionCount)") + } // Use direct connection to avoid connection pool deadlock guard let eventLoopGroup = databasePool.getEventLoopGroup() else { @@ -370,7 +390,9 @@ public class PostgreSQLStorage { // Commit transaction try await connection.query("COMMIT", logger: self.logger) - self.logger.debug("Program and execution storage successful: hash=\(programHash), executionId=\(executionId)") + if enableLogging { + self.logger.info("Program and execution storage successful: hash=\(programHash), executionId=\(executionId)") + } return (programHash, executionId) } catch { @@ -384,7 +406,9 @@ public class PostgreSQLStorage { public func storeProgram(program: Program, fuzzerId: Int, metadata: ExecutionMetadata) async throws -> String { let programHash = DatabaseUtils.calculateProgramHash(program: program) let programBase64 = DatabaseUtils.encodeProgramToBase64(program: program) - logger.debug("Storing program: hash=\(programHash), fuzzerId=\(fuzzerId), executionCount=\(metadata.executionCount)") + if enableLogging { + logger.info("Storing program: hash=\(programHash), fuzzerId=\(fuzzerId), executionCount=\(metadata.executionCount)") + } // Use direct connection to avoid connection pool deadlock guard let eventLoopGroup = databasePool.getEventLoopGroup() else { @@ -430,27 +454,37 @@ public class PostgreSQLStorage { """ try await connection.query(programQuery, logger: self.logger) - self.logger.debug("Program storage successful: hash=\(programHash)") + if enableLogging { + self.logger.info("Program storage successful: hash=\(programHash)") + } return programHash } /// Get program by hash public func getProgram(hash: String) async throws -> Program? { - logger.debug("Getting program: hash=\(hash)") + if enableLogging { + logger.info("Getting program: hash=\(hash)") + } // For now, return nil (program not found) // TODO: Implement actual database query when PostgreSQL is set up - logger.debug("Mock program lookup: program not found") + if enableLogging { + logger.info("Mock program lookup: program not found") + } return nil } /// Get program metadata for a specific fuzzer public func getProgramMetadata(programHash: String, fuzzerId: Int) async throws -> ExecutionMetadata? { - logger.debug("Getting program metadata: hash=\(programHash), fuzzerId=\(fuzzerId)") + if enableLogging { + logger.info("Getting program metadata: hash=\(programHash), fuzzerId=\(fuzzerId)") + } // For now, return nil (metadata not found) // TODO: Implement actual database query when PostgreSQL is set up - logger.debug("Mock metadata lookup: metadata not found") + if enableLogging { + logger.info("Mock metadata lookup: metadata not found") + } return nil } @@ -555,7 +589,9 @@ public class PostgreSQLStorage { ) async throws -> Int { let programHash = DatabaseUtils.calculateProgramHash(program: program) let programBase64 = DatabaseUtils.encodeProgramToBase64(program: program) - logger.debug("Storing execution: hash=\(programHash), fuzzerId=\(fuzzerId), type=\(executionType), outcome=\(outcome)") + if enableLogging { + logger.info("Storing execution: hash=\(programHash), fuzzerId=\(fuzzerId), type=\(executionType), outcome=\(outcome)") + } // Use direct connection to avoid connection pool deadlock guard let eventLoopGroup = databasePool.getEventLoopGroup() else { @@ -616,7 +652,9 @@ public class PostgreSQLStorage { } let executionId = try row.decode(Int.self, context: PostgresDecodingContext.default) - self.logger.debug("Execution storage successful: executionId=\(executionId)") + if enableLogging { + self.logger.info("Execution storage successful: executionId=\(executionId)") + } return executionId } @@ -634,11 +672,15 @@ public class PostgreSQLStorage { /// Get execution history for a program public func getExecutionHistory(programHash: String, fuzzerId: Int, limit: Int = 100) async throws -> [ExecutionRecord] { - logger.debug("Getting execution history: hash=\(programHash), fuzzerId=\(fuzzerId), limit=\(limit)") + if enableLogging { + logger.info("Getting execution history: hash=\(programHash), fuzzerId=\(fuzzerId), limit=\(limit)") + } // For now, return empty array // TODO: Implement actual database query when PostgreSQL is set up - logger.debug("Mock execution history lookup: no executions found") + if enableLogging { + logger.info("Mock execution history lookup: no executions found") + } return [] } @@ -656,12 +698,16 @@ public class PostgreSQLStorage { stderr: String? = nil ) async throws -> Int { let programHash = DatabaseUtils.calculateProgramHash(program: program) - logger.debug("Storing crash: hash=\(programHash), fuzzerId=\(fuzzerId), executionId=\(executionId), type=\(crashType)") + if enableLogging { + logger.info("Storing crash: hash=\(programHash), fuzzerId=\(fuzzerId), executionId=\(executionId), type=\(crashType)") + } // For now, return a mock crash ID // TODO: Implement actual database storage when PostgreSQL is set up let mockCrashId = Int.random(in: 1...1000) - logger.debug("Mock crash storage successful: crashId=\(mockCrashId)") + if enableLogging { + logger.info("Mock crash storage successful: crashId=\(mockCrashId)") + } return mockCrashId } @@ -669,7 +715,9 @@ public class PostgreSQLStorage { /// Get recent programs with metadata for a fuzzer public func getRecentPrograms(fuzzerId: Int, since: Date, limit: Int = 100) async throws -> [(Program, ExecutionMetadata)] { - logger.debug("Getting recent programs: fuzzerId=\(fuzzerId), since=\(since), limit=\(limit)") + if enableLogging { + logger.info("Getting recent programs: fuzzerId=\(fuzzerId), since=\(since), limit=\(limit)") + } guard let eventLoopGroup = databasePool.getEventLoopGroup() else { throw PostgreSQLStorageError.noResult @@ -778,24 +826,32 @@ public class PostgreSQLStorage { programs.append((program, metadata)) } - logger.debug("Loaded \(programs.count) recent programs from database") + if enableLogging { + logger.info("Loaded \(programs.count) recent programs from database") + } return programs } /// Update program metadata public func updateProgramMetadata(programHash: String, fuzzerId: Int, metadata: ExecutionMetadata) async throws { - logger.debug("Updating program metadata: hash=\(programHash), fuzzerId=\(fuzzerId), executionCount=\(metadata.executionCount)") + if enableLogging { + logger.info("Updating program metadata: hash=\(programHash), fuzzerId=\(fuzzerId), executionCount=\(metadata.executionCount)") + } // For now, just log the operation // TODO: Implement actual database update when PostgreSQL is set up - logger.debug("Mock metadata update successful") + if enableLogging { + logger.info("Mock metadata update successful") + } } // MARK: - Statistics /// Get storage statistics public func getStorageStatistics() async throws -> StorageStatistics { - logger.debug("Getting storage statistics") + if enableLogging { + logger.info("Getting storage statistics") + } // For now, return mock statistics // TODO: Implement actual database statistics when PostgreSQL is set up @@ -805,7 +861,9 @@ public class PostgreSQLStorage { totalCrashes: 0, activeFuzzers: 0 ) - logger.debug("Mock statistics: \(mockStats.description)") + if enableLogging { + logger.info("Mock statistics: \(mockStats.description)") + } return mockStats } } diff --git a/Sources/FuzzilliCli/main.swift b/Sources/FuzzilliCli/main.swift index 7f4867017..d4db6ef4a 100755 --- a/Sources/FuzzilliCli/main.swift +++ b/Sources/FuzzilliCli/main.swift @@ -103,6 +103,7 @@ Options: --sync-interval=n : Sync interval in seconds for PostgreSQL corpus (default: 10). --validate-before-cache : Enable program validation before caching in PostgreSQL corpus (default: true). --execution-history-size=n : Number of recent executions to keep in memory for PostgreSQL corpus (default: 10). + --postgres-logging : Enable PostgreSQL database operation logging. """) exit(0) @@ -167,6 +168,7 @@ let postgresUrl = args["--postgres-url"] let syncInterval = args.int(for: "--sync-interval") ?? 10 let validateBeforeCache = args.has("--validate-before-cache") || !args.has("--no-validate-before-cache") // Default to true let executionHistorySize = args.int(for: "--execution-history-size") ?? 10 +let postgresLogging = args.has("--postgres-logging") guard numJobs >= 1 else { configError("Must have at least 1 job") @@ -535,14 +537,15 @@ func makeFuzzer(with configuration: Configuration) -> Fuzzer { // Replace database name in the connection string let modifiedPostgresUrl = postgresUrl.replacingOccurrences(of: "/fuzzilli", with: "/\(databaseName)") - let databasePool = DatabasePool(connectionString: modifiedPostgresUrl) + let databasePool = DatabasePool(connectionString: modifiedPostgresUrl, enableLogging: postgresLogging) corpus = PostgreSQLCorpus( minSize: minCorpusSize, maxSize: maxCorpusSize, minMutationsPerSample: minMutationsPerSample, databasePool: databasePool, - fuzzerInstanceId: fuzzerInstanceId + fuzzerInstanceId: fuzzerInstanceId, + enableLogging: postgresLogging ) logger.info("Created PostgreSQL corpus with instance ID: \(fuzzerInstanceId)") From e284ce767268864e71a2be1d5d9364bc228cee89 Mon Sep 17 00:00:00 2001 From: Chase Killorin Date: Wed, 29 Oct 2025 01:42:58 -0400 Subject: [PATCH 10/23] Oleg's slop code fixed, removed sql errors and database might actually be functioning --- .../Fuzzilli/Corpus/PostgreSQLCorpus.swift | 221 +++++++++++------- Sources/Fuzzilli/Database/DatabasePool.swift | 36 +-- .../Fuzzilli/Database/DatabaseSchema.swift | 26 +-- Sources/Fuzzilli/Database/DatabaseUtils.swift | 2 +- .../Fuzzilli/Database/PostgreSQLStorage.swift | 147 +++++++----- Tests/FuzzilliTests/DatabaseSchemaTests.swift | 2 +- postgres-init.sql | 24 +- 7 files changed, 284 insertions(+), 174 deletions(-) diff --git a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift index a0bdf7a61..ecdbf638e 100644 --- a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift +++ b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift @@ -106,14 +106,22 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { PostgreSQLCorpus.unregisterInstance(self) // Commit any pending batches when the corpus is deallocated - Task { - await commitPendingBatches() + // Use Task.detached to avoid capturing self + Task.detached { [weak self] in + await self?.commitPendingBatches() } } // MARK: - Performance Optimizations + /// Async-safe locking helper + private func withLock(_ lock: NSLock, _ body: () throws -> T) rethrows -> T { + lock.lock() + defer { lock.unlock() } + return try body() + } + private func startPeriodicBatchFlush() { // Flush batches every 5 seconds to ensure timely processing Task { @@ -149,7 +157,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } private func commitPendingBatches() async { - guard let fuzzerId = fuzzerId else { return } + guard fuzzerId != nil else { return } // Commit pending executions let queuedExecutions = executionBatchLock.withLock { @@ -159,12 +167,10 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } if !queuedExecutions.isEmpty { - do { - try await processExecutionBatch(queuedExecutions) - // logger.debug("Committed \(queuedExecutions.count) pending executions on exit") - } catch { - logger.error("Failed to commit pending executions on exit: \(error)") - } + await processExecutionBatch(queuedExecutions) + if enableLogging { + self.logger.info("Committed \(queuedExecutions.count) pending executions on exit") + } } } @@ -176,7 +182,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { Task { do { try await databasePool.initialize() - // logger.debug("Database pool initialized successfully") + if enableLogging { + self.logger.info("Database pool initialized successfully") + } // Register this fuzzer instance in the database (only once) if !fuzzerRegistered { @@ -184,10 +192,14 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { let id = try await registerFuzzerWithRetry() fuzzerId = id fuzzerRegistered = true - // logger.debug("Fuzzer registered in database with ID: \(id)") + if enableLogging { + self.logger.info("Fuzzer registered in database with ID: \(id)") + } } catch { logger.error("Failed to register fuzzer after retries: \(error)") - // logger.debug("Fuzzer will continue without database registration - executions will be queued") + if enableLogging { + self.logger.info("Fuzzer will continue without database registration - executions will be queued") + } } } @@ -221,7 +233,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { fuzzer.registerEventListener(for: fuzzer.events.PostExecute) { execution in if let program = self.currentExecutionProgram, let purpose = self.currentExecutionPurpose { // DEBUG: Log execution recording - self.logger.info("Recording execution: outcome=\(execution.outcome), execTime=\(execution.execTime)") + if self.enableLogging { + self.logger.info("Recording execution: outcome=\(execution.outcome), execTime=\(execution.execTime)") + } // Create ProgramAspects from the execution let aspects = ProgramAspects(outcome: execution.outcome) @@ -260,21 +274,29 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } } else { // DEBUG: Log when execution is not recorded - self.logger.info("Skipping execution recording: program=\(self.currentExecutionProgram != nil), purpose=\(self.currentExecutionPurpose != nil)") + if self.enableLogging { + self.logger.info("Skipping execution recording: program=\(self.currentExecutionProgram != nil), purpose=\(self.currentExecutionPurpose != nil)") + } } } // Schedule periodic synchronization with PostgreSQL fuzzer.timers.scheduleTask(every: syncInterval, syncWithDatabase) - // logger.debug("Scheduled database sync every \(syncInterval) seconds") + if self.enableLogging { + logger.info("Scheduled database sync every \(syncInterval) seconds") + } // Schedule periodic flush of execution batch fuzzer.timers.scheduleTask(every: 5.0, flushExecutionBatch) - // logger.debug("Scheduled execution batch flush every 5 seconds") + if enableLogging { + logger.info("Scheduled execution batch flush every 5 seconds") + } // Schedule periodic retry of fuzzer registration if it failed fuzzer.timers.scheduleTask(every: 30.0, retryFuzzerRegistration) - // logger.debug("Scheduled fuzzer registration retry every 30 seconds") + if enableLogging { + logger.info("Scheduled fuzzer registration retry every 30 seconds") + } // Schedule cleanup task (similar to BasicCorpus) if !fuzzer.config.staticCorpus { @@ -338,7 +360,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { return } - // logger.debug("Processing batch of \(batch.count) executions") + if enableLogging { + self.logger.info("Processing batch of \(batch.count) executions") + } do { // Prepare batch data for programs (deduplicate by program hash) @@ -381,7 +405,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { _ = try await storage.storeExecutionsBatch(executions: executionBatchData, fuzzerId: fuzzerId) } - // logger.debug("Completed batch processing: \(programBatch.count) unique programs, \(executionBatchData.count) executions") + if enableLogging { + self.logger.info("Completed batch processing: \(programBatch.count) unique programs, \(executionBatchData.count) executions") + } } catch { logger.error("Failed to process execution batch: \(error)") @@ -397,7 +423,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } if !batch.isEmpty { - // logger.debug("Flushing \(batch.count) pending executions") + if enableLogging { + self.logger.info("Flushing \(batch.count) pending executions") + } Task { await processExecutionBatch(batch) } @@ -413,7 +441,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { let id = try await registerFuzzerWithRetry() fuzzerId = id fuzzerRegistered = true - // logger.debug("Successfully registered fuzzer on retry with ID: \(id)") + if enableLogging { + self.logger.info("Successfully registered fuzzer on retry with ID: \(id)") + } // Process any queued executions let queuedExecutions = executionBatchLock.withLock { @@ -423,7 +453,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } if !queuedExecutions.isEmpty { - // logger.debug("Processing \(queuedExecutions.count) queued executions after successful registration") + if enableLogging { + self.logger.info("Processing \(queuedExecutions.count) queued executions after successful registration") + } await processExecutionBatch(queuedExecutions) } @@ -498,10 +530,11 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { // Update execution metadata let programHash = programHashes[idx] - if var (program, metadata) = programCache[programHash] { - metadata.executionCount += 1 - metadata.lastExecutionTime = Date() - programCache[programHash] = (program: program, metadata: metadata) + if let (program, metadata) = programCache[programHash] { + var updatedMetadata = metadata + updatedMetadata.executionCount += 1 + updatedMetadata.lastExecutionTime = Date() + programCache[programHash] = (program: program, metadata: updatedMetadata) markForSync(programHash) } @@ -521,7 +554,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { defer { cacheLock.unlock() } let res = try encodeProtobufCorpus(Array(programs)) - // logger.debug("Successfully serialized \(programs.count) programs from PostgreSQL corpus") + if enableLogging { + self.logger.info("Successfully serialized \(programs.count) programs from PostgreSQL corpus") + } return res } @@ -540,14 +575,18 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { addInternal(program) } - // logger.debug("Imported \(newPrograms.count) programs into PostgreSQL corpus") + if enableLogging { + self.logger.info("Imported \(newPrograms.count) programs into PostgreSQL corpus") + } } // MARK: - Database Operations /// Load initial corpus from PostgreSQL database private func loadInitialCorpus() async { - // logger.debug("Loading initial corpus from PostgreSQL...") + if enableLogging { + self.logger.info("Loading initial corpus from PostgreSQL...") + } guard let fuzzerId = fuzzerId else { logger.warning("Cannot load initial corpus: fuzzer not registered") @@ -563,40 +602,47 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { limit: maxSize ) - // logger.debug("Found \(recentPrograms.count) recent programs to resume") + if enableLogging { + self.logger.info("Found \(recentPrograms.count) recent programs to resume") + } // Add programs to the corpus - cacheLock.lock() - defer { cacheLock.unlock() } - - for (program, metadata) in recentPrograms { - let programHash = DatabaseUtils.calculateProgramHash(program: program) - - // Skip if already in cache - if programCache[programHash] != nil { - continue + withLock(cacheLock) { + for (program, metadata) in recentPrograms { + let programHash = DatabaseUtils.calculateProgramHash(program: program) + + // Skip if already in cache + if programCache[programHash] != nil { + continue + } + + // Add to in-memory structures + prepareProgramForInclusion(program, index: totalEntryCounter) + programs.append(program) + ages.append(0) + programHashes.append(programHash) + programCache[programHash] = (program: program, metadata: metadata) + + totalEntryCounter += 1 } - - // Add to in-memory structures - prepareProgramForInclusion(program, index: totalEntryCounter) - programs.append(program) - ages.append(0) - programHashes.append(programHash) - programCache[programHash] = (program: program, metadata: metadata) - - totalEntryCounter += 1 } - // logger.debug("Resumed PostgreSQL corpus with \(programs.count) programs") + if enableLogging { + self.logger.info("Resumed PostgreSQL corpus with \(programs.count) programs") + } // If we have no programs, we need at least one to avoid empty corpus if programs.count == 0 { - // logger.debug("No programs found to resume, corpus will start empty") + if enableLogging { + self.logger.info("No programs found to resume, corpus will start empty") + } } } catch { logger.error("Failed to load initial corpus from PostgreSQL: \(error)") - // logger.debug("Corpus will start empty and build up from scratch") + if enableLogging { + self.logger.info("Corpus will start empty and build up from scratch") + } } } @@ -611,23 +657,24 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { private func performDatabaseSync() async { let hashesToSync: Set - // Use synchronous lock for getting pending operations - syncLock.lock() - hashesToSync = Set(pendingSyncOperations) - pendingSyncOperations.removeAll() - syncLock.unlock() + // Use async-safe lock for getting pending operations + hashesToSync = withLock(syncLock) { + let hashes = Set(pendingSyncOperations) + pendingSyncOperations.removeAll() + return hashes + } guard !hashesToSync.isEmpty else { return } // Syncing programs with PostgreSQL silently // Get programs to sync from cache - cacheLock.lock() - let programsToSync = hashesToSync.compactMap { hash -> (Program, ExecutionMetadata)? in - guard let (program, metadata) = programCache[hash] else { return nil } - return (program, metadata) + let programsToSync = withLock(cacheLock) { + hashesToSync.compactMap { hash -> (Program, ExecutionMetadata)? in + guard let (program, metadata) = programCache[hash] else { return nil } + return (program, metadata) + } } - cacheLock.unlock() // Store each program in the database for (program, metadata) in programsToSync { @@ -650,9 +697,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } catch { logger.error("Failed to sync program to database: \(error)") // Re-add to pending sync for retry - syncLock.lock() - pendingSyncOperations.insert(DatabaseUtils.calculateProgramHash(program: program)) - syncLock.unlock() + withLock(syncLock) { + pendingSyncOperations.insert(DatabaseUtils.calculateProgramHash(program: program)) + } } } @@ -670,12 +717,16 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { for attempt in 1...maxRetries { do { - // logger.debug("Attempting to register fuzzer (attempt \(attempt)/\(maxRetries))") + if enableLogging { + self.logger.info("Attempting to register fuzzer (attempt \(attempt)/\(maxRetries))") + } let id = try await storage.registerFuzzer( name: fuzzerName, engineType: engineType ) - // logger.debug("Successfully registered fuzzer with ID: \(id)") + if enableLogging { + self.logger.info("Successfully registered fuzzer with ID: \(id)") + } return id } catch { lastError = error @@ -705,12 +756,16 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { do { // Use the registered fuzzer ID guard let fuzzerId = fuzzerId else { - logger.info("Cannot store execution: fuzzer not registered") + if enableLogging { + self.logger.info("Cannot store execution: fuzzer not registered") + } return } // DEBUG: Log execution storage attempt - logger.info("Storing execution: fuzzerId=\(fuzzerId), outcome=\(executionData.outcome), execTime=\(executionData.execTime)") + if enableLogging { + self.logger.info("Storing execution: fuzzerId=\(fuzzerId), outcome=\(executionData.outcome), execTime=\(executionData.execTime)") + } // Store both program and execution in a single transaction to avoid foreign key issues _ = try await storage.storeProgramAndExecution( @@ -730,7 +785,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { )) ) - logger.info("Successfully stored program and execution") + if enableLogging { + self.logger.info("Successfully stored program and execution") + } } catch { logger.error("Failed to store execution: \(String(reflecting: error))") @@ -740,7 +797,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { /// Store coverage snapshot to database private func storeCoverageSnapshot(coverage: Double, programHash: String) async { guard let fuzzerId = fuzzerId else { - logger.info("Cannot store coverage snapshot: fuzzer not registered") + if enableLogging { + self.logger.info("Cannot store coverage snapshot: fuzzer not registered") + } return } @@ -754,7 +813,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { """) try await storage.executeQuery(query) - logger.info("Stored coverage snapshot: \(String(format: "%.6f%%", coverage * 100))") + if enableLogging { + self.logger.info("Stored coverage snapshot: \(String(format: "%.6f%%", coverage * 100))") + } } catch { logger.error("Failed to store coverage snapshot: \(error)") @@ -828,7 +889,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } } - // logger.debug("PostgreSQL corpus cleanup finished: \(self.programs.count) -> \(newPrograms.count)") + if enableLogging { + self.logger.info("PostgreSQL corpus cleanup finished: \(self.programs.count) -> \(newPrograms.count)") + } programs = newPrograms ages = newAges programHashes = newHashes @@ -868,12 +931,12 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { /// Get enhanced statistics including database coverage public func getEnhancedStatistics() async -> EnhancedCorpusStatistics { - cacheLock.lock() - defer { cacheLock.unlock() } - - let totalExecutions = programCache.values.reduce(0) { $0 + $1.metadata.executionCount } - let averageCoverage = programCache.values.isEmpty ? 0.0 : - programCache.values.reduce(0.0) { $0 + $1.metadata.lastCoverage } / Double(programCache.count) + let (totalExecutions, averageCoverage, pendingSyncCount) = withLock(cacheLock) { + let totalExecutions = programCache.values.reduce(0) { $0 + $1.metadata.executionCount } + let averageCoverage = programCache.values.isEmpty ? 0.0 : + programCache.values.reduce(0.0) { $0 + $1.metadata.lastCoverage } / Double(programCache.count) + return (totalExecutions, averageCoverage, pendingSyncOperations.count) + } // Get database statistics var dbStats = DatabaseStatistics() @@ -889,7 +952,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { totalPrograms: programs.count, totalExecutions: totalExecutions, averageCoverage: averageCoverage, - pendingSyncOperations: pendingSyncOperations.count, + pendingSyncOperations: pendingSyncCount, fuzzerInstanceId: fuzzerInstanceId, databasePrograms: dbStats.totalPrograms, databaseExecutions: dbStats.totalExecutions, diff --git a/Sources/Fuzzilli/Database/DatabasePool.swift b/Sources/Fuzzilli/Database/DatabasePool.swift index 11d28f914..6e5cc9c9c 100644 --- a/Sources/Fuzzilli/Database/DatabasePool.swift +++ b/Sources/Fuzzilli/Database/DatabasePool.swift @@ -31,10 +31,7 @@ public class DatabasePool { /// Initialize the connection pool public func initialize() async throws { // Check if already initialized - let alreadyInitialized: Bool - lock.lock() - alreadyInitialized = isInitialized - lock.unlock() + let alreadyInitialized = await withLock(lock) { isInitialized } guard !alreadyInitialized else { if enableLogging { @@ -73,9 +70,9 @@ public class DatabasePool { on: eventLoopGroup ) - lock.lock() - isInitialized = true - lock.unlock() + await withLock(lock) { + isInitialized = true + } if enableLogging { logger.info("Database connection pool initialized successfully") @@ -150,11 +147,8 @@ public class DatabasePool { /// Shutdown the connection pool public func shutdown() async { - // Use a synchronous lock for the check - let shouldShutdown: Bool - lock.lock() - shouldShutdown = isInitialized - lock.unlock() + // Use async-safe lock for the check + let shouldShutdown = await withLock(lock) { isInitialized } guard shouldShutdown else { if enableLogging { @@ -170,7 +164,10 @@ public class DatabasePool { do { // Shutdown connection pool if let pool = connectionPool { - pool.shutdown() + // Use Task.detached to avoid blocking the async context + Task.detached { + pool.shutdown() + } connectionPool = nil } @@ -180,9 +177,9 @@ public class DatabasePool { self.eventLoopGroup = nil } - lock.lock() - isInitialized = false - lock.unlock() + await withLock(lock) { + isInitialized = false + } if enableLogging { logger.info("Database connection pool shutdown complete") @@ -202,6 +199,13 @@ public class DatabasePool { // MARK: - Private Methods + /// Async-safe locking helper + private func withLock(_ lock: NSLock, _ body: () throws -> T) rethrows -> T { + lock.lock() + defer { lock.unlock() } + return try body() + } + private func parseConnectionString(_ connectionString: String) throws -> SQLPostgresConfiguration { // Parse postgresql://user:password@host:port/database format guard let url = URL(string: connectionString) else { diff --git a/Sources/Fuzzilli/Database/DatabaseSchema.swift b/Sources/Fuzzilli/Database/DatabaseSchema.swift index e75c26501..b39c215ce 100644 --- a/Sources/Fuzzilli/Database/DatabaseSchema.swift +++ b/Sources/Fuzzilli/Database/DatabaseSchema.swift @@ -27,22 +27,22 @@ public class DatabaseSchema { -- Fuzzer programs table (corpus) CREATE TABLE IF NOT EXISTS fuzzer ( - program_base64 TEXT PRIMARY KEY, + program_hash VARCHAR(64) PRIMARY KEY, -- SHA256 hash for deduplication fuzzer_id INT NOT NULL REFERENCES main(fuzzer_id) ON DELETE CASCADE, inserted_at TIMESTAMP DEFAULT NOW(), program_size INT, - program_hash VARCHAR(64) -- SHA256 hash for deduplication + program_base64 TEXT -- Keep for backward compatibility and lookups ); -- Programs table (executed programs) CREATE TABLE IF NOT EXISTS program ( - program_base64 TEXT PRIMARY KEY, + program_hash VARCHAR(64) PRIMARY KEY, -- SHA256 hash for deduplication fuzzer_id INT NOT NULL REFERENCES main(fuzzer_id) ON DELETE CASCADE, created_at TIMESTAMP DEFAULT NOW(), program_size INT, - program_hash VARCHAR(64), + program_base64 TEXT, -- Keep for backward compatibility and lookups source_mutator VARCHAR(50), -- Which mutator created this program - parent_program_base64 TEXT REFERENCES program(program_base64) -- For mutation lineage + parent_program_hash VARCHAR(64) REFERENCES program(program_hash) -- For mutation lineage ); -- Execution Type lookup table (based on Fuzzilli execution purposes and mutators) @@ -103,7 +103,7 @@ public class DatabaseSchema { -- Main execution table CREATE TABLE IF NOT EXISTS execution ( execution_id SERIAL PRIMARY KEY, - program_base64 TEXT NOT NULL REFERENCES program(program_base64) ON DELETE CASCADE, + program_hash VARCHAR(64) NOT NULL REFERENCES program(program_hash) ON DELETE CASCADE, execution_type_id INTEGER NOT NULL REFERENCES execution_type(id), mutator_type_id INTEGER REFERENCES mutator_type(id), execution_outcome_id INTEGER NOT NULL REFERENCES execution_outcome(id), @@ -164,7 +164,7 @@ public class DatabaseSchema { ); -- Performance indexes for common queries - CREATE INDEX IF NOT EXISTS idx_execution_program ON execution(program_base64); + CREATE INDEX IF NOT EXISTS idx_execution_program ON execution(program_hash); CREATE INDEX IF NOT EXISTS idx_execution_type ON execution(execution_type_id); CREATE INDEX IF NOT EXISTS idx_execution_mutator ON execution(mutator_type_id); CREATE INDEX IF NOT EXISTS idx_execution_outcome ON execution(execution_outcome_id); @@ -178,14 +178,14 @@ public class DatabaseSchema { -- Foreign key constraint for program table ALTER TABLE program ADD CONSTRAINT IF NOT EXISTS fk_program_fuzzer - FOREIGN KEY (program_base64) - REFERENCES fuzzer(program_base64); + FOREIGN KEY (program_hash) + REFERENCES fuzzer(program_hash); -- Views for common queries CREATE OR REPLACE VIEW execution_summary AS SELECT e.execution_id, - e.program_base64, + e.program_hash, et.title as execution_type, mt.name as mutator_type, eo.outcome as execution_outcome, @@ -200,7 +200,7 @@ public class DatabaseSchema { CREATE OR REPLACE VIEW crash_summary AS SELECT e.execution_id, - e.program_base64, + e.program_hash, eo.outcome, e.signal_code, e.exit_code, @@ -230,7 +230,7 @@ public class DatabaseSchema { MIN(e.coverage_total) as min_coverage, COUNT(CASE WHEN eo.outcome = 'Crashed' THEN 1 END) as crash_count FROM execution e - JOIN program p ON e.program_base64 = p.program_base64 + JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE p.fuzzer_id = fuzzer_instance_id; END; @@ -245,7 +245,7 @@ public class DatabaseSchema { do { let query = PostgresQuery(stringLiteral: DatabaseSchema.schemaSQL) - let result = try await connection.query(query, logger: Logging.Logger(label: "DatabaseSchema")) + _ = try await connection.query(query, logger: Logging.Logger(label: "DatabaseSchema")) if enableLogging { logger.info("Database schema created successfully") logger.info("Schema SQL length: \(DatabaseSchema.schemaSQL.count) characters") diff --git a/Sources/Fuzzilli/Database/DatabaseUtils.swift b/Sources/Fuzzilli/Database/DatabaseUtils.swift index 3c18d2dfc..3f576d40c 100644 --- a/Sources/Fuzzilli/Database/DatabaseUtils.swift +++ b/Sources/Fuzzilli/Database/DatabaseUtils.swift @@ -124,7 +124,7 @@ public class DatabaseUtils { if signal == 11 || signal == 7 { return 1 // Real crashes: SIGSEGV (11) and SIGBUS (7) } else { - return 34 // SigCheck: SIGTRAP (5), SIGABRT (6), and others + return 5 // SigCheck: SIGTRAP (5), SIGABRT (6), and others } case .timedOut: return 4 // TimedOut maps to ID 4 diff --git a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift index ca63b5ad3..1c44a21e1 100644 --- a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift +++ b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift @@ -187,7 +187,7 @@ public class PostgreSQLStorage { let totalPrograms = try programRows.first?.decode(Int.self, context: .default) ?? 0 // Get execution count for this fuzzer - let executionQuery: PostgresQuery = "SELECT COUNT(*) FROM execution e JOIN program p ON e.program_base64 = p.program_base64 WHERE p.fuzzer_id = \(fuzzerId)" + let executionQuery: PostgresQuery = "SELECT COUNT(*) FROM execution e JOIN program p ON e.program_hash = p.program_hash WHERE p.fuzzer_id = \(fuzzerId)" let executionResult = try await connection.query(executionQuery, logger: self.logger) let executionRows = try await executionResult.collect() let totalExecutions = try executionRows.first?.decode(Int.self, context: .default) ?? 0 @@ -195,7 +195,7 @@ public class PostgreSQLStorage { // Get crash count for this fuzzer let crashQuery: PostgresQuery = """ SELECT COUNT(*) FROM execution e - JOIN program p ON e.program_base64 = p.program_base64 + JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE p.fuzzer_id = \(fuzzerId) AND eo.outcome = 'Crashed' """ @@ -261,26 +261,50 @@ public class PostgreSQLStorage { // Escape single quotes in strings let escapedProgramBase64 = programBase64.replacingOccurrences(of: "'", with: "''") - fuzzerValues.append("('\(escapedProgramBase64)', \(fuzzerId), \(program.size), '\(programHash)')") - programValues.append("('\(escapedProgramBase64)', \(fuzzerId), \(program.size), '\(programHash)')") + fuzzerValues.append("('\(programHash)', \(fuzzerId), \(program.size), '\(escapedProgramBase64)')") + programValues.append("('\(programHash)', \(fuzzerId), \(program.size), '\(escapedProgramBase64)')") } // Batch insert into fuzzer table if !fuzzerValues.isEmpty { - let fuzzerQueryString = "INSERT INTO fuzzer (program_base64, fuzzer_id, program_size, program_hash) VALUES " + - fuzzerValues.joined(separator: ", ") + " ON CONFLICT (program_base64) DO NOTHING" + let fuzzerQueryString = "INSERT INTO fuzzer (program_hash, fuzzer_id, program_size, program_base64) VALUES " + + fuzzerValues.joined(separator: ", ") + " ON CONFLICT DO NOTHING" let fuzzerQuery = PostgresQuery(stringLiteral: fuzzerQueryString) try await connection.query(fuzzerQuery, logger: self.logger) } - // Batch insert into program table + // Batch insert into program table - use two-step upsert for each program if !programValues.isEmpty { - let programQueryString = "INSERT INTO program (program_base64, fuzzer_id, program_size, program_hash) VALUES " + - programValues.joined(separator: ", ") + " ON CONFLICT (program_base64) DO UPDATE SET " + - "fuzzer_id = EXCLUDED.fuzzer_id, program_size = EXCLUDED.program_size, " + - "program_hash = EXCLUDED.program_hash" - let programQuery = PostgresQuery(stringLiteral: programQueryString) - try await connection.query(programQuery, logger: self.logger) + for programValue in programValues { + // Extract program_hash from the value string for the UPDATE + let components = programValue.dropFirst().dropLast().split(separator: ",") + guard components.count >= 4 else { continue } + let programHash = String(components[0].dropFirst().dropLast()) // Remove quotes + let fuzzerId = String(components[1].trimmingCharacters(in: .whitespaces)) + let programSize = String(components[2].trimmingCharacters(in: .whitespaces)) + let programBase64 = String(components[3].dropFirst().dropLast()) // Remove quotes + + // Try UPDATE first + let updateQuery = PostgresQuery(stringLiteral: """ + UPDATE program SET + fuzzer_id = \(fuzzerId), + program_size = \(programSize), + program_base64 = '\(programBase64)' + WHERE program_hash = '\(programHash)' + """) + let updateResult = try await connection.query(updateQuery, logger: self.logger) + let updateRows = try await updateResult.collect() + + // If no rows were updated, insert the new program + if updateRows.isEmpty { + let insertQuery = PostgresQuery(stringLiteral: """ + INSERT INTO program (program_hash, fuzzer_id, program_size, program_base64) + VALUES ('\(programHash)', \(fuzzerId), \(programSize), '\(programBase64)') + ON CONFLICT DO NOTHING + """) + try await connection.query(insertQuery, logger: self.logger) + } + } } return programHashes @@ -331,22 +355,32 @@ public class PostgreSQLStorage { do { // Insert into fuzzer table (corpus) let fuzzerQuery = PostgresQuery(stringLiteral: """ - INSERT INTO fuzzer (program_base64, fuzzer_id, program_size, program_hash) - VALUES ('\(programBase64)', \(fuzzerId), \(program.size), '\(programHash)') - ON CONFLICT (program_base64) DO NOTHING + INSERT INTO fuzzer (program_hash, fuzzer_id, program_size, program_base64) + VALUES ('\(programHash)', \(fuzzerId), \(program.size), '\(programBase64)') + ON CONFLICT DO NOTHING """) try await connection.query(fuzzerQuery, logger: self.logger) - // Insert into program table (executed programs) - use DO UPDATE to ensure it exists - let programQuery = PostgresQuery(stringLiteral: """ - INSERT INTO program (program_base64, fuzzer_id, program_size, program_hash) - VALUES ('\(programBase64)', \(fuzzerId), \(program.size), '\(programHash)') - ON CONFLICT (program_base64) DO UPDATE SET - fuzzer_id = EXCLUDED.fuzzer_id, - program_size = EXCLUDED.program_size, - program_hash = EXCLUDED.program_hash + // Insert into program table (executed programs) - use two-step upsert + let updateQuery = PostgresQuery(stringLiteral: """ + UPDATE program SET + fuzzer_id = \(fuzzerId), + program_size = \(program.size), + program_base64 = '\(programBase64)' + WHERE program_hash = '\(programHash)' """) - try await connection.query(programQuery, logger: self.logger) + let updateResult = try await connection.query(updateQuery, logger: self.logger) + let updateRows = try await updateResult.collect() + + // If no rows were updated, insert the new program + if updateRows.isEmpty { + let insertQuery = PostgresQuery(stringLiteral: """ + INSERT INTO program (program_hash, fuzzer_id, program_size, program_base64) + VALUES ('\(programHash)', \(fuzzerId), \(program.size), '\(programBase64)') + ON CONFLICT DO NOTHING + """) + try await connection.query(insertQuery, logger: self.logger) + } // Now store the execution let executionTypeId = DatabaseUtils.mapExecutionType(purpose: executionType) @@ -366,12 +400,12 @@ public class PostgreSQLStorage { let executionQuery = PostgresQuery(stringLiteral: """ INSERT INTO execution ( - program_base64, execution_type_id, mutator_type_id, + program_hash, execution_type_id, mutator_type_id, execution_outcome_id, coverage_total, execution_time_ms, signal_code, exit_code, stdout, stderr, fuzzout, feedback_vector, created_at ) VALUES ( - '\(programBase64)', \(executionTypeId), + '\(programHash)', \(executionTypeId), NULL, \(outcomeId), \(coverage), \(executionTimeMs), \(signalCodeValue), \(exitCodeValue), \(stdoutValue), \(stderrValue), \(fuzzoutValue), @@ -432,9 +466,9 @@ public class PostgreSQLStorage { // Insert into fuzzer table (corpus) let fuzzerQuery: PostgresQuery = """ - INSERT INTO fuzzer (program_base64, fuzzer_id, program_size, program_hash) - VALUES ('\(programBase64)', \(fuzzerId), \(program.size), '\(programHash)') - ON CONFLICT (program_base64) DO NOTHING + INSERT INTO fuzzer (program_hash, fuzzer_id, program_size, program_base64) + VALUES ('\(programHash)', \(fuzzerId), \(program.size), '\(programBase64)') + ON CONFLICT DO NOTHING """ try await connection.query(fuzzerQuery, logger: self.logger) @@ -442,17 +476,26 @@ public class PostgreSQLStorage { let lifter = JavaScriptLifter(ecmaVersion: .es6) _ = lifter.lift(program, withOptions: []) - // Insert into program table (executed programs) - // Use DO UPDATE to ensure the program exists for foreign key constraints - let programQuery: PostgresQuery = """ - INSERT INTO program (program_base64, fuzzer_id, program_size, program_hash) - VALUES ('\(programBase64)', \(fuzzerId), \(program.size), '\(programHash)') - ON CONFLICT (program_base64) DO UPDATE SET - fuzzer_id = EXCLUDED.fuzzer_id, - program_size = EXCLUDED.program_size, - program_hash = EXCLUDED.program_hash + // Insert into program table (executed programs) - use two-step upsert + let updateQuery: PostgresQuery = """ + UPDATE program SET + fuzzer_id = \(fuzzerId), + program_size = \(program.size), + program_base64 = '\(programBase64)' + WHERE program_hash = '\(programHash)'rm """ - try await connection.query(programQuery, logger: self.logger) + let updateResult = try await connection.query(updateQuery, logger: self.logger) + let updateRows = try await updateResult.collect() + + // If no rows were updated, insert the new program + if updateRows.isEmpty { + let insertQuery: PostgresQuery = """ + INSERT INTO program (program_hash, fuzzer_id, program_size, program_base64) + VALUES ('\(programHash)', \(fuzzerId), \(program.size), '\(programBase64)') + ON CONFLICT DO NOTHING + """ + try await connection.query(insertQuery, logger: self.logger) + } if enableLogging { self.logger.info("Program storage successful: hash=\(programHash)") @@ -519,8 +562,8 @@ public class PostgreSQLStorage { // Prepare batch data for executionData in executions { - _ = DatabaseUtils.calculateProgramHash(program: executionData.program) - let programBase64 = DatabaseUtils.encodeProgramToBase64(program: executionData.program) + let programHash = DatabaseUtils.calculateProgramHash(program: executionData.program) + let _ = DatabaseUtils.encodeProgramToBase64(program: executionData.program) let executionTypeId = DatabaseUtils.mapExecutionType(purpose: executionData.executionType) @@ -540,7 +583,7 @@ public class PostgreSQLStorage { let fuzzoutValue = executionData.fuzzout != nil ? "'\(executionData.fuzzout!.replacingOccurrences(of: "'", with: "''"))'" : "NULL" executionValues.append(""" - ('\(programBase64.replacingOccurrences(of: "'", with: "''"))', \(executionTypeId), + ('\(programHash)', \(executionTypeId), \(mutatorTypeValue), \(outcomeId), \(executionData.coverage), \(executionData.executionTimeMs), \(signalCodeValue), \(exitCodeValue), \(stdoutValue), \(stderrValue), \(fuzzoutValue), @@ -552,7 +595,7 @@ public class PostgreSQLStorage { if !executionValues.isEmpty { let queryString = """ INSERT INTO execution ( - program_base64, execution_type_id, mutator_type_id, + program_hash, execution_type_id, mutator_type_id, execution_outcome_id, coverage_total, execution_time_ms, signal_code, exit_code, stdout, stderr, fuzzout, feedback_vector, created_at @@ -590,7 +633,7 @@ public class PostgreSQLStorage { let programHash = DatabaseUtils.calculateProgramHash(program: program) let programBase64 = DatabaseUtils.encodeProgramToBase64(program: program) if enableLogging { - logger.info("Storing execution: hash=\(programHash), fuzzerId=\(fuzzerId), type=\(executionType), outcome=\(outcome)") + logger.info("Storing execution: hash=\(programHash), fuzzerId=\(fuzzerId), type=\(executionType), outcome=\(outcome), programBase64=\(programBase64)") } // Use direct connection to avoid connection pool deadlock @@ -632,12 +675,12 @@ public class PostgreSQLStorage { let query = PostgresQuery(stringLiteral: """ INSERT INTO execution ( - program_base64, execution_type_id, mutator_type_id, + program_hash, execution_type_id, mutator_type_id, execution_outcome_id, coverage_total, execution_time_ms, signal_code, exit_code, stdout, stderr, fuzzout, feedback_vector, created_at ) VALUES ( - '\(programBase64)', \(executionTypeId), + '\(programHash)', \(executionTypeId), \(mutatorTypeValue), \(outcomeId), \(coverage), \(executionTimeMs), \(signalCodeValue), \(exitCodeValue), \(stdoutValue), \(stderrValue), \(fuzzoutValue), @@ -741,9 +784,9 @@ public class PostgreSQLStorage { // Query for recent programs with their latest execution metadata let queryString = """ SELECT - p.program_base64, - p.program_size, p.program_hash, + p.program_size, + p.program_base64, p.created_at, eo.outcome, eo.description, @@ -752,7 +795,7 @@ public class PostgreSQLStorage { e.signal_code, e.exit_code FROM program p - LEFT JOIN execution e ON p.program_base64 = e.program_base64 + LEFT JOIN execution e ON p.program_hash = e.program_hash LEFT JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE p.fuzzer_id = \(fuzzerId) AND p.created_at >= '\(since.ISO8601Format())' @@ -767,9 +810,9 @@ public class PostgreSQLStorage { var programs: [(Program, ExecutionMetadata)] = [] for row in rows { - let programBase64 = try row.decode(String.self, context: .default) - _ = try row.decode(Int.self, context: .default) // programSize let programHash = try row.decode(String.self, context: .default) + _ = try row.decode(Int.self, context: .default) // programSize + let programBase64 = try row.decode(String.self, context: .default) _ = try row.decode(Date.self, context: .default) // createdAt let outcome = try row.decode(String?.self, context: .default) let description = try row.decode(String?.self, context: .default) diff --git a/Tests/FuzzilliTests/DatabaseSchemaTests.swift b/Tests/FuzzilliTests/DatabaseSchemaTests.swift index 5df4679e0..be72a9817 100644 --- a/Tests/FuzzilliTests/DatabaseSchemaTests.swift +++ b/Tests/FuzzilliTests/DatabaseSchemaTests.swift @@ -124,7 +124,7 @@ final class DatabaseSchemaTests: XCTestCase { // Check for foreign key constraints XCTAssertTrue(schema.contains("REFERENCES main(fuzzer_id)"), "Should have foreign key to main table") - XCTAssertTrue(schema.contains("REFERENCES program(program_base64)"), "Should have foreign key to program table") + XCTAssertTrue(schema.contains("REFERENCES program(program_hash)"), "Should have foreign key to program table") XCTAssertTrue(schema.contains("REFERENCES execution(execution_id)"), "Should have foreign key to execution table") // Check for primary keys diff --git a/postgres-init.sql b/postgres-init.sql index 0f8da45e4..b6c1f45d5 100644 --- a/postgres-init.sql +++ b/postgres-init.sql @@ -12,22 +12,22 @@ CREATE TABLE IF NOT EXISTS main ( -- Create the fuzzer programs table (corpus) CREATE TABLE IF NOT EXISTS fuzzer ( - program_base64 TEXT PRIMARY KEY, + program_hash VARCHAR(64) PRIMARY KEY, -- SHA256 hash for deduplication fuzzer_id INT NOT NULL REFERENCES main(fuzzer_id) ON DELETE CASCADE, inserted_at TIMESTAMP DEFAULT NOW(), program_size INT, - program_hash VARCHAR(64) -- SHA256 hash for deduplication + program_base64 TEXT -- Keep for backward compatibility and lookups ); -- Create the programs table (executed programs) CREATE TABLE IF NOT EXISTS program ( - program_base64 TEXT PRIMARY KEY, + program_hash VARCHAR(64) PRIMARY KEY, -- SHA256 hash for deduplication fuzzer_id INT NOT NULL REFERENCES main(fuzzer_id) ON DELETE CASCADE, created_at TIMESTAMP DEFAULT NOW(), program_size INT, - program_hash VARCHAR(64), + program_base64 TEXT, -- Keep for backward compatibility and lookups source_mutator VARCHAR(50), -- Which mutator created this program - parent_program_base64 TEXT REFERENCES program(program_base64) -- For mutation lineage + parent_program_hash VARCHAR(64) REFERENCES program(program_hash) -- For mutation lineage ); -- Create execution type lookup table @@ -89,7 +89,7 @@ ON CONFLICT (outcome) DO NOTHING; -- Create the main execution table CREATE TABLE IF NOT EXISTS execution ( execution_id SERIAL PRIMARY KEY, - program_base64 TEXT NOT NULL REFERENCES program(program_base64) ON DELETE CASCADE, + program_hash VARCHAR(64) NOT NULL REFERENCES program(program_hash) ON DELETE CASCADE, execution_type_id INTEGER NOT NULL REFERENCES execution_type(id), mutator_type_id TEXT, -- Store mutator name directly instead of ID execution_outcome_id INTEGER NOT NULL REFERENCES execution_outcome(id), @@ -166,7 +166,7 @@ CREATE INDEX IF NOT EXISTS idx_coverage_snapshot_fuzzer ON coverage_snapshot(fuz CREATE INDEX IF NOT EXISTS idx_coverage_snapshot_created ON coverage_snapshot(created_at); -- Create performance indexes -CREATE INDEX IF NOT EXISTS idx_execution_program ON execution(program_base64); +CREATE INDEX IF NOT EXISTS idx_execution_program ON execution(program_hash); CREATE INDEX IF NOT EXISTS idx_execution_type ON execution(execution_type_id); CREATE INDEX IF NOT EXISTS idx_execution_mutator ON execution(mutator_type_id); CREATE INDEX IF NOT EXISTS idx_execution_outcome ON execution(execution_outcome_id); @@ -180,14 +180,14 @@ CREATE INDEX IF NOT EXISTS idx_crash_analysis_execution ON crash_analysis(execut -- Create foreign key constraint for program table ALTER TABLE program ADD CONSTRAINT IF NOT EXISTS fk_program_fuzzer -FOREIGN KEY (program_base64) -REFERENCES fuzzer(program_base64); +FOREIGN KEY (program_hash) +REFERENCES fuzzer(program_hash); -- Create views for common queries CREATE OR REPLACE VIEW execution_summary AS SELECT e.execution_id, - e.program_base64, + e.program_hash, et.title as execution_type, e.mutator_type_id as mutator_type, -- Use the TEXT field directly eo.outcome as execution_outcome, @@ -201,7 +201,7 @@ JOIN execution_outcome eo ON e.execution_outcome_id = eo.id; CREATE OR REPLACE VIEW crash_summary AS SELECT e.execution_id, - e.program_base64, + e.program_hash, eo.outcome, e.signal_code, e.exit_code, @@ -231,7 +231,7 @@ BEGIN MIN(e.coverage_total) as min_coverage, COUNT(CASE WHEN eo.outcome = 'Crashed' THEN 1 END) as crash_count FROM execution e - JOIN program p ON e.program_base64 = p.program_base64 + JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE p.fuzzer_id = fuzzer_instance_id; END; From 973c022854b001ffc3f26be01da659ec9cb9e842 Mon Sep 17 00:00:00 2001 From: Chase Killorin Date: Wed, 29 Oct 2025 02:13:17 -0400 Subject: [PATCH 11/23] Query script updated for new structure --- Scripts/DBQuery.sh | 243 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100755 Scripts/DBQuery.sh diff --git a/Scripts/DBQuery.sh b/Scripts/DBQuery.sh new file mode 100755 index 000000000..f732754f4 --- /dev/null +++ b/Scripts/DBQuery.sh @@ -0,0 +1,243 @@ +#!/bin/bash + +# Fuzzilli PostgreSQL Database Query Script using Docker +# This script uses Docker to connect to the PostgreSQL container and run queries + +# Database connection parameters +DB_CONTAINER="fuzzilli-postgres" # Adjust this to match your container name +DB_NAME="fuzzilli" +DB_USER="fuzzilli" +DB_PASSWORD="fuzzilli123" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to run a query using Docker +run_query() { + local title="$1" + local query="$2" + + echo -e "\n${BLUE}=== $title ===${NC}" + echo -e "${YELLOW}Query:${NC} $query" + echo -e "${GREEN}Results:${NC}" + + docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -c "$query" 2>/dev/null + + if [ $? -ne 0 ]; then + echo -e "${RED}Error: Failed to execute query${NC}" + fi +} + +# Function to check if Docker is available +check_docker() { + if ! command -v docker &> /dev/null; then + echo -e "${RED}Error: Docker command not found. Please install Docker.${NC}" + exit 1 + fi +} + +# Function to check if PostgreSQL container is running +check_container() { + if ! docker ps --format "table {{.Names}}" | grep -q "$DB_CONTAINER"; then + echo -e "${RED}Error: PostgreSQL container '$DB_CONTAINER' is not running${NC}" + echo "Available containers:" + docker ps --format "table {{.Names}}\t{{.Status}}" + echo "" + echo "Please start the PostgreSQL container or update the DB_CONTAINER variable in this script" + exit 1 + fi +} + +# Function to test database connection +test_connection() { + echo -e "${BLUE}Testing database connection...${NC}" + docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -c "SELECT version();" &>/dev/null + + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Database connection successful${NC}" + else + echo -e "${RED}✗ Database connection failed${NC}" + echo "Please check:" + echo "1. PostgreSQL container is running" + echo "2. Database credentials are correct" + echo "3. Container name is correct" + exit 1 + fi +} + +# Main execution +main() { + echo -e "${GREEN}Fuzzilli Database Query Tool (Docker)${NC}" + echo "=============================================" + + check_docker + check_container + test_connection + + # Basic database info + run_query "Database Information" "SELECT current_database() as database_name, current_user as user_name, version() as postgres_version;" + + # List all tables + run_query "Available Tables" "SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name;" + + # Program statistics + run_query "Program Count by Fuzzer" " + SELECT + p.fuzzer_id, + COUNT(*) as program_count, + MIN(p.created_at) as first_program, + MAX(p.created_at) as latest_program + FROM program p + GROUP BY p.fuzzer_id + ORDER BY program_count DESC; + " + + # Total program count + run_query "Total Program Statistics" " + SELECT + COUNT(*) as total_programs, + COUNT(DISTINCT fuzzer_id) as active_fuzzers, + AVG(program_size) as avg_program_size, + MAX(program_size) as max_program_size, + MIN(created_at) as first_program, + MAX(created_at) as latest_program + FROM program; + " + + # Execution statistics + run_query "Execution Statistics" " + SELECT + eo.outcome, + COUNT(*) as count, + ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) as percentage + FROM execution e + JOIN execution_outcome eo ON e.execution_outcome_id = eo.id + GROUP BY eo.outcome + ORDER BY count DESC; + " + + # Recent programs + run_query "Recent Programs (Last 10)" " + SELECT + LEFT(program_base64, 20) as program_preview, + fuzzer_id, + LEFT(program_hash, 12) as hash_prefix, + program_size, + created_at + FROM program + ORDER BY created_at DESC + LIMIT 10; + " + + # Recent executions + run_query "Recent Executions (Last 10)" " + SELECT + e.execution_id, + p.fuzzer_id, + LEFT(p.program_hash, 12) as hash_prefix, + eo.outcome, + e.execution_time_ms, + e.created_at + FROM execution e + JOIN program p ON e.program_hash = p.program_hash + JOIN execution_outcome eo ON e.execution_outcome_id = eo.id + ORDER BY e.created_at DESC + LIMIT 10; + " + + # Crash analysis + run_query "Crash Analysis" " + SELECT + p.fuzzer_id, + COUNT(*) as crash_count, + MIN(e.created_at) as first_crash, + MAX(e.created_at) as latest_crash + FROM execution e + JOIN program p ON e.program_hash = p.program_hash + JOIN execution_outcome eo ON e.execution_outcome_id = eo.id + WHERE eo.outcome = 'Crashed' + GROUP BY p.fuzzer_id + ORDER BY crash_count DESC; + " + + # Coverage statistics + run_query "Coverage Statistics" " + SELECT + COUNT(*) as executions_with_coverage, + AVG(coverage_total) as avg_coverage_percentage, + MAX(coverage_total) as max_coverage_percentage, + COUNT(CASE WHEN coverage_total > 0 THEN 1 END) as executions_with_positive_coverage + FROM execution + WHERE coverage_total IS NOT NULL; + " + + # Performance metrics + run_query "Performance Metrics" " + SELECT + AVG(execution_time_ms) as avg_execution_time_ms, + MIN(execution_time_ms) as min_execution_time_ms, + MAX(execution_time_ms) as max_execution_time_ms, + COUNT(*) as total_executions + FROM execution + WHERE execution_time_ms > 0; + " + + # Database size info + run_query "Database Size Information" " + SELECT + schemaname, + tablename, + pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size + FROM pg_tables + WHERE schemaname = 'public' + ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC; + " + + echo -e "\n${GREEN}Database query completed successfully!${NC}" +} + +# Handle command line arguments +case "${1:-}" in + "programs") + run_query "All Programs" "SELECT LEFT(program_base64, 30) as program_preview, fuzzer_id, program_size, created_at FROM program ORDER BY created_at DESC LIMIT 20;" + ;; + "executions") + run_query "All Executions" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, eo.outcome, e.execution_time_ms, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id ORDER BY e.created_at DESC LIMIT 20;" + ;; + "crashes") + run_query "All Crashes" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, e.stdout, e.stderr, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hasht JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 20;" + ;; + "stats") + run_query "Quick Stats" "SELECT COUNT(*) as programs, (SELECT COUNT(*) FROM execution) as executions, (SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed') as crashes FROM program;" + ;; + "containers") + echo -e "${BLUE}Available PostgreSQL containers:${NC}" + docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(postgres|fuzzilli)" + ;; + "help"|"-h"|"--help") + echo "Usage: $0 [command]" + echo "" + echo "Commands:" + echo " (no args) - Run full database analysis" + echo " programs - Show recent programs" + echo " executions - Show recent executions" + echo " crashes - Show recent crashes" + echo " stats - Show quick statistics" + echo " containers - List available PostgreSQL containers" + echo " help - Show this help" + echo "" + echo "Note: Update DB_CONTAINER variable in script if your container has a different name" + ;; + "") + main + ;; + *) + echo "Unknown command: $1" + echo "Use '$0 help' for usage information" + exit 1 + ;; +esac \ No newline at end of file From f840ff48aa20abc64c634d0f3a33bac241a4112b Mon Sep 17 00:00:00 2001 From: Chase Killorin Date: Wed, 29 Oct 2025 02:20:25 -0400 Subject: [PATCH 12/23] DBQuery.sh coverage statisics iteration 1 --- Scripts/DBQuery.sh | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/Scripts/DBQuery.sh b/Scripts/DBQuery.sh index f732754f4..1b456797e 100755 --- a/Scripts/DBQuery.sh +++ b/Scripts/DBQuery.sh @@ -167,12 +167,30 @@ main() { # Coverage statistics run_query "Coverage Statistics" " SELECT - COUNT(*) as executions_with_coverage, - AVG(coverage_total) as avg_coverage_percentage, - MAX(coverage_total) as max_coverage_percentage, - COUNT(CASE WHEN coverage_total > 0 THEN 1 END) as executions_with_positive_coverage - FROM execution - WHERE coverage_total IS NOT NULL; + (SELECT COUNT(DISTINCT execution_id) FROM coverage_detail) AS executions_with_coverage, + ( + SELECT ROUND(AVG(edges_hit)::numeric, 2) FROM ( + SELECT COUNT(*) FILTER (WHERE edge_hit_count > 0) AS edges_hit + FROM coverage_detail + GROUP BY execution_id + ) s + ) AS avg_edges_hit_per_execution, + ( + SELECT MAX(edges_hit) FROM ( + SELECT COUNT(*) FILTER (WHERE edge_hit_count > 0) AS edges_hit + FROM coverage_detail + GROUP BY execution_id + ) s + ) AS max_edges_hit_in_execution, + ( + SELECT COALESCE(SUM(new_edges), 0) FROM ( + SELECT SUM(CASE WHEN is_new_edge THEN 1 ELSE 0 END) AS new_edges + FROM coverage_detail + GROUP BY execution_id + ) s + ) AS total_new_edges, + ROUND(((SELECT COUNT(DISTINCT execution_id) FROM coverage_detail) * 100.0) + / NULLIF((SELECT COUNT(*) FROM execution), 0), 2) AS percent_executions_with_coverage; " # Performance metrics @@ -209,7 +227,7 @@ case "${1:-}" in run_query "All Executions" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, eo.outcome, e.execution_time_ms, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id ORDER BY e.created_at DESC LIMIT 20;" ;; "crashes") - run_query "All Crashes" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, e.stdout, e.stderr, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hasht JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 20;" + run_query "All Crashes" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, e.stdout, e.stderr, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 20;" ;; "stats") run_query "Quick Stats" "SELECT COUNT(*) as programs, (SELECT COUNT(*) FROM execution) as executions, (SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed') as crashes FROM program;" From f6f828a00215d9423deb9befd97a70dc6465d086 Mon Sep 17 00:00:00 2001 From: Chase Killorin Date: Wed, 29 Oct 2025 02:36:58 -0400 Subject: [PATCH 13/23] DBQuery.sh coverage statisics iteration 2 --- Scripts/DBQuery.sh | 31 ++++--------------- .../Fuzzilli/Corpus/PostgreSQLCorpus.swift | 2 +- .../Fuzzilli/Database/PostgreSQLStorage.swift | 15 +++++++++ 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/Scripts/DBQuery.sh b/Scripts/DBQuery.sh index 1b456797e..a437c0d47 100755 --- a/Scripts/DBQuery.sh +++ b/Scripts/DBQuery.sh @@ -167,30 +167,11 @@ main() { # Coverage statistics run_query "Coverage Statistics" " SELECT - (SELECT COUNT(DISTINCT execution_id) FROM coverage_detail) AS executions_with_coverage, - ( - SELECT ROUND(AVG(edges_hit)::numeric, 2) FROM ( - SELECT COUNT(*) FILTER (WHERE edge_hit_count > 0) AS edges_hit - FROM coverage_detail - GROUP BY execution_id - ) s - ) AS avg_edges_hit_per_execution, - ( - SELECT MAX(edges_hit) FROM ( - SELECT COUNT(*) FILTER (WHERE edge_hit_count > 0) AS edges_hit - FROM coverage_detail - GROUP BY execution_id - ) s - ) AS max_edges_hit_in_execution, - ( - SELECT COALESCE(SUM(new_edges), 0) FROM ( - SELECT SUM(CASE WHEN is_new_edge THEN 1 ELSE 0 END) AS new_edges - FROM coverage_detail - GROUP BY execution_id - ) s - ) AS total_new_edges, - ROUND(((SELECT COUNT(DISTINCT execution_id) FROM coverage_detail) * 100.0) - / NULLIF((SELECT COUNT(*) FROM execution), 0), 2) AS percent_executions_with_coverage; + COUNT(*) FILTER (WHERE coverage_total IS NOT NULL) AS executions_with_coverage, + ROUND(AVG(coverage_total)::numeric, 2) AS avg_coverage_percentage, + MAX(coverage_total) AS max_coverage_percentage, + COUNT(*) FILTER (WHERE coverage_total > 0) AS executions_with_positive_coverage + FROM execution; " # Performance metrics @@ -227,7 +208,7 @@ case "${1:-}" in run_query "All Executions" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, eo.outcome, e.execution_time_ms, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id ORDER BY e.created_at DESC LIMIT 20;" ;; "crashes") - run_query "All Crashes" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, e.stdout, e.stderr, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 20;" + run_query "All Crashes" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, e.stdout, e.stderr, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hasht JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 20;" ;; "stats") run_query "Quick Stats" "SELECT COUNT(*) as programs, (SELECT COUNT(*) FROM execution) as executions, (SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed') as crashes FROM program;" diff --git a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift index ecdbf638e..38011b440 100644 --- a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift +++ b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift @@ -389,7 +389,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { mutatorType: nil, outcome: aspects.outcome, coverage: aspects is CovEdgeSet ? Double((aspects as! CovEdgeSet).count) : 0.0, - coverageEdges: Set() // Empty for now + coverageEdges: (aspects as? CovEdgeSet).map { Set($0.getEdges().map { Int($0) }) } ?? Set() ) executionBatchData.append(executionData) } diff --git a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift index 1c44a21e1..39d3985f4 100644 --- a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift +++ b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift @@ -610,6 +610,21 @@ public class PostgreSQLStorage { let executionId = try row.decode(Int.self, context: .default) executionIds.append(executionId) } + + // Insert coverage detail rows for executions that have edge data + var coverageValues: [String] = [] + for (idx, execId) in executionIds.enumerated() { + let edges = executions[idx].coverageEdges + if !edges.isEmpty { + for edge in edges { + coverageValues.append("(\(execId), \(edge), 1, TRUE)") + } + } + } + if !coverageValues.isEmpty { + let coverageInsert = "INSERT INTO coverage_detail (execution_id, edge_index, edge_hit_count, is_new_edge) VALUES " + coverageValues.joined(separator: ", ") + try await connection.query(PostgresQuery(stringLiteral: coverageInsert), logger: self.logger) + } } return executionIds From 791afc55ed3848b95b192d0ee14120ac7bec642e Mon Sep 17 00:00:00 2001 From: Chase Killorin Date: Wed, 29 Oct 2025 02:59:37 -0400 Subject: [PATCH 14/23] DBQuery.sh coverage statisics iteration 3 --- Scripts/DBQuery.sh | 2 +- .../Fuzzilli/Corpus/PostgreSQLCorpus.swift | 23 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Scripts/DBQuery.sh b/Scripts/DBQuery.sh index a437c0d47..5fdc8e195 100755 --- a/Scripts/DBQuery.sh +++ b/Scripts/DBQuery.sh @@ -208,7 +208,7 @@ case "${1:-}" in run_query "All Executions" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, eo.outcome, e.execution_time_ms, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id ORDER BY e.created_at DESC LIMIT 20;" ;; "crashes") - run_query "All Crashes" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, e.stdout, e.stderr, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hasht JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 20;" + run_query "All Crashes" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, e.stdout, e.stderr, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 20;" ;; "stats") run_query "Quick Stats" "SELECT COUNT(*) as programs, (SELECT COUNT(*) FROM execution) as executions, (SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed') as crashes FROM program;" diff --git a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift index 38011b440..adda59da3 100644 --- a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift +++ b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift @@ -382,13 +382,21 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { uniquePrograms[programHash] = (program, metadata) } - // Prepare execution data + // Prepare execution data, compute coverage percentage from evaluator if available + let coveragePct: Double = { + if let coverageEvaluator = self.fuzzer.evaluator as? ProgramCoverageEvaluator { + return coverageEvaluator.currentScore * 100.0 + } else { + return 0.0 + } + }() + let executionData = ExecutionBatchData( program: program, executionType: executionType, mutatorType: nil, outcome: aspects.outcome, - coverage: aspects is CovEdgeSet ? Double((aspects as! CovEdgeSet).count) : 0.0, + coverage: coveragePct, coverageEdges: (aspects as? CovEdgeSet).map { Set($0.getEdges().map { Int($0) }) } ?? Set() ) executionBatchData.append(executionData) @@ -767,13 +775,22 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { self.logger.info("Storing execution: fuzzerId=\(fuzzerId), outcome=\(executionData.outcome), execTime=\(executionData.execTime)") } + // Derive coverage percentage (0-100) from evaluator if available + let coveragePct: Double = { + if let coverageEvaluator = self.fuzzer.evaluator as? ProgramCoverageEvaluator { + return coverageEvaluator.currentScore * 100.0 + } else { + return 0.0 + } + }() + // Store both program and execution in a single transaction to avoid foreign key issues _ = try await storage.storeProgramAndExecution( program: program, fuzzerId: fuzzerId, executionType: executionType, outcome: executionData.outcome, - coverage: aspects is CovEdgeSet ? Double((aspects as! CovEdgeSet).count) : 0.0, + coverage: coveragePct, executionTimeMs: Int(executionData.execTime * 1000), stdout: executionData.stdout, stderr: executionData.stderr, From c0451e837eaf2ebeca9d36fb1cdad58232d576fa Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Wed, 29 Oct 2025 03:58:09 -0400 Subject: [PATCH 15/23] added coverage fixes + refactor to program hash instead of bsae64 --- .gitignore | 2 + Cloud/VRIG/Dockerfile.distributed | 54 ++++++++++++ .../Fuzzilli/Corpus/PostgreSQLCorpus.swift | 20 ++++- Sources/Fuzzilli/Database/DatabasePool.swift | 10 +++ .../Fuzzilli/Database/PostgreSQLStorage.swift | 86 +++++++++---------- .../Evaluation/ProgramCoverageEvaluator.swift | 10 +++ Sources/FuzzilliCli/main.swift | 14 ++- docker-compose.distributed.yml | 76 ++++++++++++++++ query_db.sh | 35 +++++++- 9 files changed, 249 insertions(+), 58 deletions(-) create mode 100644 Cloud/VRIG/Dockerfile.distributed create mode 100644 docker-compose.distributed.yml diff --git a/.gitignore b/.gitignore index 7ac430d7f..e3849d743 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ Package.resolved .*.sw? .swiftpm /Corpus +.env +env.distributed # custom GCE configuration Cloud/GCE/config.sh diff --git a/Cloud/VRIG/Dockerfile.distributed b/Cloud/VRIG/Dockerfile.distributed new file mode 100644 index 000000000..7c4f0050f --- /dev/null +++ b/Cloud/VRIG/Dockerfile.distributed @@ -0,0 +1,54 @@ +# Stage 1: Build Fuzzilli +FROM docker.io/swift:latest + +ENV DEBIAN_FRONTEND=noninteractive +ENV SHELL=bash + +RUN apt-get -y update && apt-get -y upgrade +RUN apt-get -y install nodejs + +RUN useradd -m builder +WORKDIR /home/builder + +ADD .. fuzzillai + +# build Fuzzilli +RUN cd fuzzillai && \ + # Temporarily remove test target to avoid build issues + sed -i '/\.testTarget(name: "FuzzilliTests"/,/),$/d' Package.swift && \ + swift build -c release --product FuzzilliCli && \ + # Verify the executable was created + ls -la .build/release/ && \ + find .build -name "FuzzilliCli" -type f -executable + +##################### +# Stage 2: Runtime +FROM docker.io/swift:latest + +ENV DEBIAN_FRONTEND=noninteractive +ENV SHELL=bash + +RUN apt-get -y update && apt-get -y upgrade + +RUN useradd -m app + +WORKDIR /home/app + +# Copy Fuzzilli executable +COPY --from=0 /home/builder/fuzzillai/.build/release/FuzzilliCli FuzzilliCli + +# Create directory for V8 build (will be mounted from host) +RUN mkdir -p ./fuzzbuild + +RUN mkdir -p ./Corpus + +# Environment variables for distributed fuzzing +ENV MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli +ENV FUZZER_INSTANCE_NAME=fuzzer-default +ENV SYNC_INTERVAL=300 +ENV TIMEOUT=2500 +ENV MIN_MUTATIONS_PER_SAMPLE=25 +ENV DEBUG_LOGGING=false + +# Default command for distributed fuzzing +CMD ./FuzzilliCli --profile=v8debug --engine=multi --resume --corpus=postgresql --master-postgres-url="${MASTER_POSTGRES_URL}" --fuzzer-instance-name="${FUZZER_INSTANCE_NAME}" --sync-interval="${SYNC_INTERVAL}" --timeout="${TIMEOUT}" --minMutationsPerSample="${MIN_MUTATIONS_PER_SAMPLE}" --postgres-logging ./fuzzbuild/d8 diff --git a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift index adda59da3..d71647cd3 100644 --- a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift +++ b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift @@ -705,7 +705,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } catch { logger.error("Failed to sync program to database: \(error)") // Re-add to pending sync for retry - withLock(syncLock) { + _ = withLock(syncLock) { pendingSyncOperations.insert(DatabaseUtils.calculateProgramHash(program: program)) } } @@ -821,17 +821,29 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } do { + // Get edge counts from the coverage evaluator if available + let edgesFound: Int + let totalEdges: Int + + if let coverageEvaluator = self.fuzzer.evaluator as? ProgramCoverageEvaluator { + edgesFound = Int(coverageEvaluator.getFoundEdgesCount()) + totalEdges = Int(coverageEvaluator.getTotalEdgesCount()) + } else { + edgesFound = 0 + totalEdges = 0 + } + let query = PostgresQuery(stringLiteral: """ INSERT INTO coverage_snapshot ( - fuzzer_id, coverage_percentage, program_hash, created_at + fuzzer_id, coverage_percentage, program_hash, edges_found, total_edges, created_at ) VALUES ( - \(fuzzerId), \(coverage), '\(programHash)', NOW() + \(fuzzerId), \(coverage), '\(programHash)', \(edgesFound), \(totalEdges), NOW() ) """) try await storage.executeQuery(query) if enableLogging { - self.logger.info("Stored coverage snapshot: \(String(format: "%.6f%%", coverage * 100))") + self.logger.info("Stored coverage snapshot: \(String(format: "%.6f%%", coverage * 100)) (\(edgesFound)/\(totalEdges) edges)") } } catch { diff --git a/Sources/Fuzzilli/Database/DatabasePool.swift b/Sources/Fuzzilli/Database/DatabasePool.swift index 6e5cc9c9c..a4b448e37 100644 --- a/Sources/Fuzzilli/Database/DatabasePool.swift +++ b/Sources/Fuzzilli/Database/DatabasePool.swift @@ -18,6 +18,7 @@ public class DatabasePool { private let connectionTimeout: TimeInterval private let retryAttempts: Int private let enableLogging: Bool + private var configuration: SQLPostgresConfiguration? public init(connectionString: String, maxConnections: Int = 5, connectionTimeout: TimeInterval = 120.0, retryAttempts: Int = 3, enableLogging: Bool = false) { self.connectionString = connectionString @@ -56,6 +57,7 @@ public class DatabasePool { // Parse connection string and create configuration let config = try parseConnectionString(connectionString) + self.configuration = config // Create connection source using the new API let connectionSource = PostgresConnectionSource( @@ -145,6 +147,14 @@ public class DatabasePool { return connectionString } + /// Get the database configuration + public func getConfiguration() throws -> SQLPostgresConfiguration { + guard let config = configuration else { + throw DatabasePoolError.notInitialized + } + return config + } + /// Shutdown the connection pool public func shutdown() async { // Use async-safe lock for the check diff --git a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift index 39d3985f4..b52f0299a 100644 --- a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift +++ b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift @@ -44,10 +44,10 @@ public class PostgreSQLStorage { on: eventLoopGroup.next(), configuration: PostgresConnection.Configuration( host: "localhost", - port: 5433, + port: 5432, username: "fuzzilli", password: "fuzzilli123", - database: "fuzzilli", + database: "fuzzilli_master", tls: .disable ), id: 0, @@ -61,8 +61,8 @@ public class PostgreSQLStorage { let checkRows = try await checkResult.collect() if let existingRow = checkRows.first { - let existingFuzzerId = try existingRow.decode(Int.self, context: .default) - let existingStatus = try existingRow.decode(String.self, context: .default) + let existingFuzzerId = try existingRow.decode(Int.self, context: PostgresDecodingContext.default) + let existingStatus = try existingRow.decode(String.self, context: PostgresDecodingContext.default) // Update status to active if it was inactive if existingStatus != "active" { @@ -93,7 +93,7 @@ public class PostgreSQLStorage { throw PostgreSQLStorageError.noResult } - let fuzzerId = try row.decode(Int.self, context: .default) + let fuzzerId = try row.decode(Int.self, context: PostgresDecodingContext.default) if enableLogging { self.logger.info("Created new fuzzer: fuzzerId=\(fuzzerId)") } @@ -115,10 +115,10 @@ public class PostgreSQLStorage { on: eventLoopGroup.next(), configuration: PostgresConnection.Configuration( host: "localhost", - port: 5433, + port: 5432, username: "fuzzilli", password: "fuzzilli123", - database: "fuzzilli", + database: "fuzzilli_master", tls: .disable ), id: 0, @@ -134,11 +134,11 @@ public class PostgreSQLStorage { return nil } - let fuzzerId = try row.decode(Int.self, context: .default) - let createdAt = try row.decode(Date.self, context: .default) - let fuzzerName = try row.decode(String.self, context: .default) - let engineType = try row.decode(String.self, context: .default) - let status = try row.decode(String.self, context: .default) + let fuzzerId = try row.decode(Int.self, context: PostgresDecodingContext.default) + let createdAt = try row.decode(Date.self, context: PostgresDecodingContext.default) + let fuzzerName = try row.decode(String.self, context: PostgresDecodingContext.default) + let engineType = try row.decode(String.self, context: PostgresDecodingContext.default) + let status = try row.decode(String.self, context: PostgresDecodingContext.default) let fuzzer = FuzzerInstance( fuzzerId: fuzzerId, @@ -169,10 +169,10 @@ public class PostgreSQLStorage { on: eventLoopGroup.next(), configuration: PostgresConnection.Configuration( host: "localhost", - port: 5433, + port: 5432, username: "fuzzilli", password: "fuzzilli123", - database: "fuzzilli", + database: "fuzzilli_master", tls: .disable ), id: 0, @@ -184,13 +184,13 @@ public class PostgreSQLStorage { let programQuery: PostgresQuery = "SELECT COUNT(*) FROM fuzzer WHERE fuzzer_id = \(fuzzerId)" let programResult = try await connection.query(programQuery, logger: self.logger) let programRows = try await programResult.collect() - let totalPrograms = try programRows.first?.decode(Int.self, context: .default) ?? 0 + let totalPrograms = try programRows.first?.decode(Int.self, context: PostgresDecodingContext.default) ?? 0 // Get execution count for this fuzzer let executionQuery: PostgresQuery = "SELECT COUNT(*) FROM execution e JOIN program p ON e.program_hash = p.program_hash WHERE p.fuzzer_id = \(fuzzerId)" let executionResult = try await connection.query(executionQuery, logger: self.logger) let executionRows = try await executionResult.collect() - let totalExecutions = try executionRows.first?.decode(Int.self, context: .default) ?? 0 + let totalExecutions = try executionRows.first?.decode(Int.self, context: PostgresDecodingContext.default) ?? 0 // Get crash count for this fuzzer let crashQuery: PostgresQuery = """ @@ -201,13 +201,13 @@ public class PostgreSQLStorage { """ let crashResult = try await connection.query(crashQuery, logger: self.logger) let crashRows = try await crashResult.collect() - let totalCrashes = try crashRows.first?.decode(Int.self, context: .default) ?? 0 + let totalCrashes = try crashRows.first?.decode(Int.self, context: PostgresDecodingContext.default) ?? 0 // Get active fuzzers count let activeQuery: PostgresQuery = "SELECT COUNT(*) FROM main WHERE status = 'active'" let activeResult = try await connection.query(activeQuery, logger: self.logger) let activeRows = try await activeResult.collect() - let activeFuzzers = try activeRows.first?.decode(Int.self, context: .default) ?? 0 + let activeFuzzers = try activeRows.first?.decode(Int.self, context: PostgresDecodingContext.default) ?? 0 let stats = DatabaseStatistics( totalPrograms: totalPrograms, @@ -237,10 +237,10 @@ public class PostgreSQLStorage { on: eventLoopGroup.next(), configuration: PostgresConnection.Configuration( host: "localhost", - port: 5433, + port: 5432, username: "fuzzilli", password: "fuzzilli123", - database: "fuzzilli", + database: "fuzzilli_master", tls: .disable ), id: 0, @@ -338,10 +338,10 @@ public class PostgreSQLStorage { on: eventLoopGroup.next(), configuration: PostgresConnection.Configuration( host: "localhost", - port: 5433, + port: 5432, username: "fuzzilli", password: "fuzzilli123", - database: "fuzzilli", + database: "fuzzilli_master", tls: .disable ), id: 0, @@ -453,10 +453,10 @@ public class PostgreSQLStorage { on: eventLoopGroup.next(), configuration: PostgresConnection.Configuration( host: "localhost", - port: 5433, + port: 5432, username: "fuzzilli", password: "fuzzilli123", - database: "fuzzilli", + database: "fuzzilli_master", tls: .disable ), id: 0, @@ -546,10 +546,10 @@ public class PostgreSQLStorage { on: eventLoopGroup.next(), configuration: PostgresConnection.Configuration( host: "localhost", - port: 5433, + port: 5432, username: "fuzzilli", password: "fuzzilli123", - database: "fuzzilli", + database: "fuzzilli_master", tls: .disable ), id: 0, @@ -607,7 +607,7 @@ public class PostgreSQLStorage { let rows = try await result.collect() for row in rows { - let executionId = try row.decode(Int.self, context: .default) + let executionId = try row.decode(Int.self, context: PostgresDecodingContext.default) executionIds.append(executionId) } @@ -660,10 +660,10 @@ public class PostgreSQLStorage { on: eventLoopGroup.next(), configuration: PostgresConnection.Configuration( host: "localhost", - port: 5433, + port: 5432, username: "fuzzilli", password: "fuzzilli123", - database: "fuzzilli", + database: "fuzzilli_master", tls: .disable ), id: 0, @@ -785,10 +785,10 @@ public class PostgreSQLStorage { on: eventLoopGroup.next(), configuration: PostgresConnection.Configuration( host: "localhost", - port: 5433, + port: 5432, username: "fuzzilli", password: "fuzzilli123", - database: "fuzzilli", + database: "fuzzilli_master", tls: .disable ), id: 0, @@ -825,16 +825,16 @@ public class PostgreSQLStorage { var programs: [(Program, ExecutionMetadata)] = [] for row in rows { - let programHash = try row.decode(String.self, context: .default) - _ = try row.decode(Int.self, context: .default) // programSize - let programBase64 = try row.decode(String.self, context: .default) - _ = try row.decode(Date.self, context: .default) // createdAt - let outcome = try row.decode(String?.self, context: .default) - let description = try row.decode(String?.self, context: .default) - _ = try row.decode(Int?.self, context: .default) // executionTimeMs - let coverageTotal = try row.decode(Double?.self, context: .default) - _ = try row.decode(Int?.self, context: .default) // signalCode - _ = try row.decode(Int?.self, context: .default) // exitCode + let programHash = try row.decode(String.self, context: PostgresDecodingContext.default) + _ = try row.decode(Int.self, context: PostgresDecodingContext.default) // programSize + let programBase64 = try row.decode(String.self, context: PostgresDecodingContext.default) + _ = try row.decode(Date.self, context: PostgresDecodingContext.default) // createdAt + let outcome = try row.decode(String?.self, context: PostgresDecodingContext.default) + let description = try row.decode(String?.self, context: PostgresDecodingContext.default) + _ = try row.decode(Int?.self, context: PostgresDecodingContext.default) // executionTimeMs + let coverageTotal = try row.decode(Double?.self, context: PostgresDecodingContext.default) + _ = try row.decode(Int?.self, context: PostgresDecodingContext.default) // signalCode + _ = try row.decode(Int?.self, context: PostgresDecodingContext.default) // exitCode // Decode the program from base64 guard let programData = Data(base64Encoded: programBase64) else { @@ -1015,10 +1015,10 @@ extension PostgreSQLStorage { on: eventLoopGroup.next(), configuration: PostgresConnection.Configuration( host: "localhost", - port: 5433, + port: 5432, username: "fuzzilli", password: "fuzzilli123", - database: "fuzzilli", + database: "fuzzilli_master", tls: .disable ), id: 0, diff --git a/Sources/Fuzzilli/Evaluation/ProgramCoverageEvaluator.swift b/Sources/Fuzzilli/Evaluation/ProgramCoverageEvaluator.swift index 1024e57eb..72bce6ff5 100755 --- a/Sources/Fuzzilli/Evaluation/ProgramCoverageEvaluator.swift +++ b/Sources/Fuzzilli/Evaluation/ProgramCoverageEvaluator.swift @@ -144,6 +144,16 @@ public class ProgramCoverageEvaluator: ComponentBase, ProgramEvaluator { return edgeArray } + + /// Get the number of edges found so far + public func getFoundEdgesCount() -> UInt32 { + return context.found_edges + } + + /// Get the total number of edges in the target + public func getTotalEdgesCount() -> UInt32 { + return context.num_edges + } override func initialize() { // Must clear the shared memory bitmap before every execution diff --git a/Sources/FuzzilliCli/main.swift b/Sources/FuzzilliCli/main.swift index 0fd15c8d5..412bf851d 100755 --- a/Sources/FuzzilliCli/main.swift +++ b/Sources/FuzzilliCli/main.swift @@ -519,23 +519,20 @@ func makeFuzzer(with configuration: Configuration) -> Fuzzer { logger.fatal("PostgreSQL URL is required for PostgreSQL corpus") } - // Generate database name based on resume flag - let databaseName: String + // Use the existing fuzzilli_master database for all instances let fuzzerInstanceId: String if resume { - // Use fixed database name for resume - databaseName = "database-main" + // Use fixed fuzzer instance ID for resume fuzzerInstanceId = "fuzzer-main" } else { - // Generate dynamic database name with 8-char hash + // Generate dynamic fuzzer instance ID with 8-char hash let randomHash = String(UUID().uuidString.prefix(8)) - databaseName = "database-\(randomHash)" fuzzerInstanceId = "fuzzer-\(randomHash)" } - // Replace database name in the connection string - let modifiedPostgresUrl = postgresUrl.replacingOccurrences(of: "/fuzzilli", with: "/\(databaseName)") + // Use the original postgres URL without modification + let modifiedPostgresUrl = postgresUrl let databasePool = DatabasePool(connectionString: modifiedPostgresUrl, enableLogging: postgresLogging) @@ -550,7 +547,6 @@ func makeFuzzer(with configuration: Configuration) -> Fuzzer { ) logger.info("Created PostgreSQL corpus with instance ID: \(fuzzerInstanceId)") - logger.info("Database name: \(databaseName)") logger.info("PostgreSQL URL: \(modifiedPostgresUrl)") logger.info("Resume mode: \(resume)") logger.info("Sync interval: \(syncInterval) seconds") diff --git a/docker-compose.distributed.yml b/docker-compose.distributed.yml new file mode 100644 index 000000000..3ba0bdc7f --- /dev/null +++ b/docker-compose.distributed.yml @@ -0,0 +1,76 @@ +version: '3.8' + +services: + postgres-master: + image: postgres:15-alpine + container_name: fuzzilli-postgres-master + environment: + POSTGRES_DB: fuzzilli_master + POSTGRES_USER: fuzzilli + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-fuzzilli123} + POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + ports: + - "5432:5432" + volumes: + - postgres_master_data:/var/lib/postgresql/data + - ./postgres-init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_master"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - fuzzing-network + + fuzzer: + build: + context: . + dockerfile: Cloud/VRIG/Dockerfile.distributed + # Remove hardcoded container name to allow scaling + environment: + - MASTER_POSTGRES_URL=postgresql://fuzzilli:${POSTGRES_PASSWORD:-fuzzilli123}@postgres-master:5432/fuzzilli_master + - FUZZER_INSTANCE_NAME= + - SYNC_INTERVAL=${SYNC_INTERVAL:-300} + - TIMEOUT=${TIMEOUT:-2500} + - MIN_MUTATIONS_PER_SAMPLE=${MIN_MUTATIONS_PER_SAMPLE:-25} + - DEBUG_LOGGING=${DEBUG_LOGGING:-false} + depends_on: + postgres-master: + condition: service_healthy + volumes: + - fuzzer_data:/home/app/Corpus + # Mount your existing V8 build directory + - /home/tropic/vrig/v8/v8/out/fuzzbuild:/home/app/fuzzbuild:ro + restart: unless-stopped + networks: + - fuzzing-network + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + + # Optional: pgAdmin for database management + pgadmin: + image: dpage/pgadmin4:latest + container_name: fuzzilli-pgadmin-distributed + environment: + PGADMIN_DEFAULT_EMAIL: admin@example.com + PGADMIN_DEFAULT_PASSWORD: admin123 + ports: + - "8080:80" + depends_on: + - postgres-master + restart: unless-stopped + networks: + - fuzzing-network + +volumes: + postgres_master_data: + fuzzer_data: + +networks: + fuzzing-network: + driver: bridge diff --git a/query_db.sh b/query_db.sh index 895ed8b1c..634f48c1d 100755 --- a/query_db.sh +++ b/query_db.sh @@ -4,8 +4,8 @@ # This script uses Docker to connect to the PostgreSQL container and run queries # Database connection parameters -DB_CONTAINER="fuzzilli-postgres" # Adjust this to match your container name -DB_NAME="fuzzilli" +DB_CONTAINER="fuzzilli-postgres-master" # Adjust this to match your container name +DB_NAME="fuzzilli_master" DB_USER="fuzzilli" DB_PASSWORD="fuzzilli123" @@ -175,6 +175,37 @@ main() { WHERE coverage_total IS NOT NULL; " + # Coverage snapshot statistics + run_query "Coverage Snapshot Statistics" " + SELECT + COUNT(*) as total_snapshots, + AVG(coverage_percentage) as avg_coverage_percentage, + MAX(coverage_percentage) as max_coverage_percentage, + AVG(edges_found) as avg_edges_found, + MAX(edges_found) as max_edges_found, + AVG(total_edges) as avg_total_edges, + MAX(total_edges) as max_total_edges, + COUNT(CASE WHEN edges_found > 0 THEN 1 END) as snapshots_with_coverage + FROM coverage_snapshot + WHERE edges_found IS NOT NULL AND total_edges IS NOT NULL; + " + + # Recent coverage snapshots + run_query "Recent Coverage Snapshots (Last 10)" " + SELECT + snapshot_id, + fuzzer_id, + ROUND(coverage_percentage::numeric, 6) as coverage_pct, + edges_found, + total_edges, + LEFT(program_hash, 12) as hash_prefix, + created_at + FROM coverage_snapshot + WHERE edges_found IS NOT NULL AND total_edges IS NOT NULL + ORDER BY created_at DESC + LIMIT 10; + " + # Performance metrics run_query "Performance Metrics" " SELECT From 0df6281dff6bbd8f9ad4db83491e993b7055ac0d Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Wed, 29 Oct 2025 16:28:01 -0400 Subject: [PATCH 16/23] added env files --- .env | 24 ++++++++++++++++++++++++ .gitignore | 2 -- env.distributed | 24 ++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 .env create mode 100644 env.distributed diff --git a/.env b/.env new file mode 100644 index 000000000..7222c57a4 --- /dev/null +++ b/.env @@ -0,0 +1,24 @@ +# Distributed Fuzzing Environment Configuration +# Copy this file to .env and modify as needed + +# Master PostgreSQL Database Configuration +POSTGRES_PASSWORD=fuzzilli123 + +# Fuzzer Configuration +FUZZER_COUNT=3 +SYNC_INTERVAL=300 +TIMEOUT=2500 +MIN_MUTATIONS_PER_SAMPLE=25 + +# Optional: Override default fuzzer instance names +# FUZZER_INSTANCE_NAMES=fuzzer-1,fuzzer-2,fuzzer-3 + +# Optional: Custom V8 revision +# V8_REVISION=b0157a634e584163cbe6004db3161dc16dea20f9 + +# Optional: Resource limits +# FUZZER_MEMORY_LIMIT=2G +# FUZZER_MEMORY_RESERVATION=1G + +# Optional: Enable debug logging +# DEBUG_LOGGING=true diff --git a/.gitignore b/.gitignore index e3849d743..7ac430d7f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,6 @@ Package.resolved .*.sw? .swiftpm /Corpus -.env -env.distributed # custom GCE configuration Cloud/GCE/config.sh diff --git a/env.distributed b/env.distributed new file mode 100644 index 000000000..7222c57a4 --- /dev/null +++ b/env.distributed @@ -0,0 +1,24 @@ +# Distributed Fuzzing Environment Configuration +# Copy this file to .env and modify as needed + +# Master PostgreSQL Database Configuration +POSTGRES_PASSWORD=fuzzilli123 + +# Fuzzer Configuration +FUZZER_COUNT=3 +SYNC_INTERVAL=300 +TIMEOUT=2500 +MIN_MUTATIONS_PER_SAMPLE=25 + +# Optional: Override default fuzzer instance names +# FUZZER_INSTANCE_NAMES=fuzzer-1,fuzzer-2,fuzzer-3 + +# Optional: Custom V8 revision +# V8_REVISION=b0157a634e584163cbe6004db3161dc16dea20f9 + +# Optional: Resource limits +# FUZZER_MEMORY_LIMIT=2G +# FUZZER_MEMORY_RESERVATION=1G + +# Optional: Enable debug logging +# DEBUG_LOGGING=true From beca5f4ea832d4ce85851cceb6603596fd09cfb0 Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Thu, 6 Nov 2025 19:33:37 -0500 Subject: [PATCH 17/23] added semi --- Scripts/extract_crashes.sh | 128 +++++++++++++ Scripts/query_db.sh | 274 +++++++++++++++++++++++++++ Scripts/start-distributed.sh | 184 ++++++++++++++++++ Scripts/test-distributed-sync.sh | 313 +++++++++++++++++++++++++++++++ docker-compose.workers.yml | 117 ++++++++++++ 5 files changed, 1016 insertions(+) create mode 100755 Scripts/extract_crashes.sh create mode 100755 Scripts/query_db.sh create mode 100755 Scripts/start-distributed.sh create mode 100755 Scripts/test-distributed-sync.sh create mode 100644 docker-compose.workers.yml diff --git a/Scripts/extract_crashes.sh b/Scripts/extract_crashes.sh new file mode 100755 index 000000000..f39ae4e85 --- /dev/null +++ b/Scripts/extract_crashes.sh @@ -0,0 +1,128 @@ +#!/bin/bash + +# Extract all programs that crashed V8 or Fuzzilli (Crashed and SigCheck outcomes) +# Programs are saved in FuzzIL binary format (.fzil) + +DB_CONTAINER="fuzzilli-postgres-master" +DB_NAME="fuzzilli_master" +DB_USER="fuzzilli" +DB_PASSWORD="fuzzilli123" + +OUTPUT_DIR="crashes_extracted" +mkdir -p "$OUTPUT_DIR" + +echo "Extracting all crashing and signaled programs in FuzzIL binary format..." +echo "Including: Crashed (SIGSEGV) and SigCheck (SIGTRAP) outcomes" + +# Get all crash and sigcheck information +docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -t -A -F'|' -c " +SELECT + p.program_hash, + e.execution_id, + eo.outcome, + e.signal_code, + e.execution_time_ms, + e.created_at, + p.program_base64, + p.program_size, + p.fuzzer_id, + LEFT(COALESCE(e.stderr, ''), 500) as crash_info +FROM execution e +JOIN execution_outcome eo ON e.execution_outcome_id = eo.id +JOIN program p ON e.program_hash = p.program_hash +WHERE eo.outcome IN ('Crashed', 'SigCheck') +ORDER BY eo.outcome, e.created_at DESC; +" | while IFS='|' read -r hash execution_id outcome signal_code exec_time created_at base64_program program_size fuzzer_id crash_info; do + + # Trim whitespace + hash=$(echo "$hash" | xargs) + execution_id=$(echo "$execution_id" | xargs) + outcome=$(echo "$outcome" | xargs) + signal_code=$(echo "$signal_code" | xargs) + exec_time=$(echo "$exec_time" | xargs) + created_at=$(echo "$created_at" | xargs) + base64_program=$(echo "$base64_program" | xargs) + program_size=$(echo "$program_size" | xargs) + fuzzer_id=$(echo "$fuzzer_id" | xargs) + crash_info=$(echo "$crash_info" | xargs) + + if [ -z "$hash" ]; then + continue + fi + + # Determine signal name + signal_name="UNKNOWN" + if [ "$outcome" = "Crashed" ] && [ "$signal_code" = "11" ]; then + signal_name="SIGSEGV (Segmentation Fault)" + prefix="crash" + elif [ "$outcome" = "SigCheck" ] && [ "$signal_code" = "5" ]; then + signal_name="SIGTRAP (Debug Assertion)" + prefix="sigcheck" + else + signal_name="Signal $signal_code" + prefix="signaled" + fi + + echo "Processing $outcome: $hash (execution_id: $execution_id, signal: $signal_code)" + + # Decode base64 to binary FuzzIL protobuf format + binary_program=$(echo "$base64_program" | base64 -d 2>/dev/null) + + if [ -z "$binary_program" ]; then + echo " ERROR: Failed to decode base64 program" + continue + fi + + # Save as .fzil file (FuzzIL binary protobuf format) + fzil_filename="${OUTPUT_DIR}/${prefix}_${execution_id}_${hash}.fzil" + echo -n "$binary_program" > "$fzil_filename" + + # Create metadata file + meta_filename="${OUTPUT_DIR}/${prefix}_${execution_id}_${hash}.txt" + cat > "$meta_filename" </dev/null | wc -l) +sigcheck_count=$(ls -1 "${OUTPUT_DIR}"/sigcheck_*.fzil 2>/dev/null | wc -l) +total_count=$(ls -1 "${OUTPUT_DIR}"/*.fzil 2>/dev/null | wc -l) + +echo "" +echo "Extraction complete!" +echo " Crashed (SIGSEGV): $crash_count programs" +echo " SigCheck (SIGTRAP): $sigcheck_count programs" +echo " Total: $total_count programs" +echo "" +echo "All programs are saved in FuzzIL binary format (.fzil files) in $OUTPUT_DIR/" +echo "Use FuzzILTool to decode or lift them to JavaScript if needed" diff --git a/Scripts/query_db.sh b/Scripts/query_db.sh new file mode 100755 index 000000000..634f48c1d --- /dev/null +++ b/Scripts/query_db.sh @@ -0,0 +1,274 @@ +#!/bin/bash + +# Fuzzilli PostgreSQL Database Query Script using Docker +# This script uses Docker to connect to the PostgreSQL container and run queries + +# Database connection parameters +DB_CONTAINER="fuzzilli-postgres-master" # Adjust this to match your container name +DB_NAME="fuzzilli_master" +DB_USER="fuzzilli" +DB_PASSWORD="fuzzilli123" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to run a query using Docker +run_query() { + local title="$1" + local query="$2" + + echo -e "\n${BLUE}=== $title ===${NC}" + echo -e "${YELLOW}Query:${NC} $query" + echo -e "${GREEN}Results:${NC}" + + docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -c "$query" 2>/dev/null + + if [ $? -ne 0 ]; then + echo -e "${RED}Error: Failed to execute query${NC}" + fi +} + +# Function to check if Docker is available +check_docker() { + if ! command -v docker &> /dev/null; then + echo -e "${RED}Error: Docker command not found. Please install Docker.${NC}" + exit 1 + fi +} + +# Function to check if PostgreSQL container is running +check_container() { + if ! docker ps --format "table {{.Names}}" | grep -q "$DB_CONTAINER"; then + echo -e "${RED}Error: PostgreSQL container '$DB_CONTAINER' is not running${NC}" + echo "Available containers:" + docker ps --format "table {{.Names}}\t{{.Status}}" + echo "" + echo "Please start the PostgreSQL container or update the DB_CONTAINER variable in this script" + exit 1 + fi +} + +# Function to test database connection +test_connection() { + echo -e "${BLUE}Testing database connection...${NC}" + docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -c "SELECT version();" &>/dev/null + + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Database connection successful${NC}" + else + echo -e "${RED}✗ Database connection failed${NC}" + echo "Please check:" + echo "1. PostgreSQL container is running" + echo "2. Database credentials are correct" + echo "3. Container name is correct" + exit 1 + fi +} + +# Main execution +main() { + echo -e "${GREEN}Fuzzilli Database Query Tool (Docker)${NC}" + echo "=============================================" + + check_docker + check_container + test_connection + + # Basic database info + run_query "Database Information" "SELECT current_database() as database_name, current_user as user_name, version() as postgres_version;" + + # List all tables + run_query "Available Tables" "SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name;" + + # Program statistics + run_query "Program Count by Fuzzer" " + SELECT + p.fuzzer_id, + COUNT(*) as program_count, + MIN(p.created_at) as first_program, + MAX(p.created_at) as latest_program + FROM program p + GROUP BY p.fuzzer_id + ORDER BY program_count DESC; + " + + # Total program count + run_query "Total Program Statistics" " + SELECT + COUNT(*) as total_programs, + COUNT(DISTINCT fuzzer_id) as active_fuzzers, + AVG(program_size) as avg_program_size, + MAX(program_size) as max_program_size, + MIN(created_at) as first_program, + MAX(created_at) as latest_program + FROM program; + " + + # Execution statistics + run_query "Execution Statistics" " + SELECT + eo.outcome, + COUNT(*) as count, + ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) as percentage + FROM execution e + JOIN execution_outcome eo ON e.execution_outcome_id = eo.id + GROUP BY eo.outcome + ORDER BY count DESC; + " + + # Recent programs + run_query "Recent Programs (Last 10)" " + SELECT + LEFT(program_base64, 20) as program_preview, + fuzzer_id, + LEFT(program_hash, 12) as hash_prefix, + program_size, + created_at + FROM program + ORDER BY created_at DESC + LIMIT 10; + " + + # Recent executions + run_query "Recent Executions (Last 10)" " + SELECT + e.execution_id, + p.fuzzer_id, + LEFT(p.program_hash, 12) as hash_prefix, + eo.outcome, + e.execution_time_ms, + e.created_at + FROM execution e + JOIN program p ON e.program_base64 = p.program_base64 + JOIN execution_outcome eo ON e.execution_outcome_id = eo.id + ORDER BY e.created_at DESC + LIMIT 10; + " + + # Crash analysis + run_query "Crash Analysis" " + SELECT + p.fuzzer_id, + COUNT(*) as crash_count, + MIN(e.created_at) as first_crash, + MAX(e.created_at) as latest_crash + FROM execution e + JOIN program p ON e.program_base64 = p.program_base64 + JOIN execution_outcome eo ON e.execution_outcome_id = eo.id + WHERE eo.outcome = 'Crashed' + GROUP BY p.fuzzer_id + ORDER BY crash_count DESC; + " + + # Coverage statistics + run_query "Coverage Statistics" " + SELECT + COUNT(*) as executions_with_coverage, + AVG(coverage_total) as avg_coverage_percentage, + MAX(coverage_total) as max_coverage_percentage, + COUNT(CASE WHEN coverage_total > 0 THEN 1 END) as executions_with_positive_coverage + FROM execution + WHERE coverage_total IS NOT NULL; + " + + # Coverage snapshot statistics + run_query "Coverage Snapshot Statistics" " + SELECT + COUNT(*) as total_snapshots, + AVG(coverage_percentage) as avg_coverage_percentage, + MAX(coverage_percentage) as max_coverage_percentage, + AVG(edges_found) as avg_edges_found, + MAX(edges_found) as max_edges_found, + AVG(total_edges) as avg_total_edges, + MAX(total_edges) as max_total_edges, + COUNT(CASE WHEN edges_found > 0 THEN 1 END) as snapshots_with_coverage + FROM coverage_snapshot + WHERE edges_found IS NOT NULL AND total_edges IS NOT NULL; + " + + # Recent coverage snapshots + run_query "Recent Coverage Snapshots (Last 10)" " + SELECT + snapshot_id, + fuzzer_id, + ROUND(coverage_percentage::numeric, 6) as coverage_pct, + edges_found, + total_edges, + LEFT(program_hash, 12) as hash_prefix, + created_at + FROM coverage_snapshot + WHERE edges_found IS NOT NULL AND total_edges IS NOT NULL + ORDER BY created_at DESC + LIMIT 10; + " + + # Performance metrics + run_query "Performance Metrics" " + SELECT + AVG(execution_time_ms) as avg_execution_time_ms, + MIN(execution_time_ms) as min_execution_time_ms, + MAX(execution_time_ms) as max_execution_time_ms, + COUNT(*) as total_executions + FROM execution + WHERE execution_time_ms > 0; + " + + # Database size info + run_query "Database Size Information" " + SELECT + schemaname, + tablename, + pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size + FROM pg_tables + WHERE schemaname = 'public' + ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC; + " + + echo -e "\n${GREEN}Database query completed successfully!${NC}" +} + +# Handle command line arguments +case "${1:-}" in + "programs") + run_query "All Programs" "SELECT LEFT(program_base64, 30) as program_preview, fuzzer_id, program_size, created_at FROM program ORDER BY created_at DESC LIMIT 20;" + ;; + "executions") + run_query "All Executions" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, eo.outcome, e.execution_time_ms, e.created_at FROM execution e JOIN program p ON e.program_base64 = p.program_base64 JOIN execution_outcome eo ON e.execution_outcome_id = eo.id ORDER BY e.created_at DESC LIMIT 20;" + ;; + "crashes") + run_query "All Crashes" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, e.stdout, e.stderr, e.created_at FROM execution e JOIN program p ON e.program_base64 = p.program_base64 JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 20;" + ;; + "stats") + run_query "Quick Stats" "SELECT COUNT(*) as programs, (SELECT COUNT(*) FROM execution) as executions, (SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed') as crashes FROM program;" + ;; + "containers") + echo -e "${BLUE}Available PostgreSQL containers:${NC}" + docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(postgres|fuzzilli)" + ;; + "help"|"-h"|"--help") + echo "Usage: $0 [command]" + echo "" + echo "Commands:" + echo " (no args) - Run full database analysis" + echo " programs - Show recent programs" + echo " executions - Show recent executions" + echo " crashes - Show recent crashes" + echo " stats - Show quick statistics" + echo " containers - List available PostgreSQL containers" + echo " help - Show this help" + echo "" + echo "Note: Update DB_CONTAINER variable in script if your container has a different name" + ;; + "") + main + ;; + *) + echo "Unknown command: $1" + echo "Use '$0 help' for usage information" + exit 1 + ;; +esac \ No newline at end of file diff --git a/Scripts/start-distributed.sh b/Scripts/start-distributed.sh new file mode 100755 index 000000000..a177d0eda --- /dev/null +++ b/Scripts/start-distributed.sh @@ -0,0 +1,184 @@ +#!/bin/bash + +# start-distributed.sh - Start distributed fuzzing with N workers +# Usage: ./Scripts/start-distributed.sh +# +# This script creates N worker containers, each with: +# - A fuzzilli container +# - A local postgres container +# - Proper networking and volumes + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +COMPOSE_FILE="${PROJECT_ROOT}/docker-compose.distributed.yml" +COMPOSE_OVERRIDE="${PROJECT_ROOT}/docker-compose.workers.yml" + +# Check if number of workers is provided +if [ $# -eq 0 ]; then + echo "Usage: $0 " + echo "Example: $0 3" + exit 1 +fi + +NUM_WORKERS=$1 + +# Validate number +if ! [[ "$NUM_WORKERS" =~ ^[0-9]+$ ]] || [ "$NUM_WORKERS" -lt 1 ]; then + echo "Error: Number of workers must be a positive integer" + exit 1 +fi + +echo "Starting distributed fuzzing with $NUM_WORKERS workers..." + +# Load environment variables +if [ -f "${PROJECT_ROOT}/.env" ]; then + source "${PROJECT_ROOT}/.env" +elif [ -f "${PROJECT_ROOT}/env.distributed" ]; then + source "${PROJECT_ROOT}/env.distributed" +fi + +POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-fuzzilli123} +SYNC_INTERVAL=${SYNC_INTERVAL:-300} +TIMEOUT=${TIMEOUT:-2500} +MIN_MUTATIONS_PER_SAMPLE=${MIN_MUTATIONS_PER_SAMPLE:-25} +DEBUG_LOGGING=${DEBUG_LOGGING:-false} + +# Generate docker-compose override file with worker services +cat > "${COMPOSE_OVERRIDE}" <> "${COMPOSE_OVERRIDE}" <> "${COMPOSE_OVERRIDE}" <> "${COMPOSE_OVERRIDE}" < /dev/null 2>&1; then + echo "Main postgres is ready" + break + fi + sleep 1 + timeout=$((timeout - 1)) +done + +if [ $timeout -eq 0 ]; then + echo "Error: Main postgres failed to start" + exit 1 +fi + +# Wait for local postgres containers to be healthy +echo "Waiting for local postgres containers to be ready..." +for i in $(seq 1 $NUM_WORKERS); do + timeout=60 + while [ $timeout -gt 0 ]; do + if docker exec postgres-local-${i} pg_isready -U fuzzilli -d fuzzilli_local > /dev/null 2>&1; then + echo "Local postgres-${i} is ready" + break + fi + sleep 1 + timeout=$((timeout - 1)) + done + if [ $timeout -eq 0 ]; then + echo "Warning: Local postgres-${i} may not be ready" + fi +done + +echo "" +echo "Distributed fuzzing setup complete!" +echo "Started $NUM_WORKERS workers:" +for i in $(seq 1 $NUM_WORKERS); do + echo " - fuzzer-worker-${i} (local postgres: postgres-local-${i})" +done +echo "" +echo "To view logs:" +echo " docker-compose -f ${COMPOSE_FILE} -f ${COMPOSE_OVERRIDE} logs -f" +echo "" +echo "To stop all services:" +echo " docker-compose -f ${COMPOSE_FILE} -f ${COMPOSE_OVERRIDE} down" +echo "" +echo "To stop a specific worker:" +echo " docker stop fuzzer-worker- postgres-local-" + diff --git a/Scripts/test-distributed-sync.sh b/Scripts/test-distributed-sync.sh new file mode 100755 index 000000000..719d3c6be --- /dev/null +++ b/Scripts/test-distributed-sync.sh @@ -0,0 +1,313 @@ +#!/bin/bash + +# test-distributed-sync.sh - Integration test for distributed PostgreSQL sync +# This script tests: +# 1. Push sync: Workers push their corpus to main database +# 2. Pull sync: Workers pull corpus from main database +# 3. Fuzzing activity: Workers are actually fuzzing +# 4. Database updates: Database updates correctly reflect fuzzing +# 5. Corpus visibility: Fuzzers see new information in corpus + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Test configuration +NUM_WORKERS=${1:-2} +SYNC_INTERVAL=${SYNC_INTERVAL:-60} # Use shorter interval for testing +TEST_TIMEOUT=${TEST_TIMEOUT:-300} # 5 minutes max +POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-fuzzilli123} + +# Test results +TESTS_PASSED=0 +TESTS_FAILED=0 + +echo "==========================================" +echo "Distributed PostgreSQL Sync Integration Test" +echo "==========================================" +echo "Workers: $NUM_WORKERS" +echo "Sync Interval: ${SYNC_INTERVAL}s" +echo "Test Timeout: ${TEST_TIMEOUT}s" +echo "==========================================" +echo "" + +# Helper functions +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +test_pass() { + TESTS_PASSED=$((TESTS_PASSED + 1)) + log_info "✓ $1" +} + +test_fail() { + TESTS_FAILED=$((TESTS_FAILED + 1)) + log_error "✗ $1" +} + +test_warn() { + log_warn "⚠ $1" +} + +# Cleanup function +cleanup() { + log_info "Cleaning up test environment..." + cd "${PROJECT_ROOT}" + docker-compose -f docker-compose.distributed.yml -f docker-compose.workers.yml down -v 2>/dev/null || true + rm -f "${PROJECT_ROOT}/docker-compose.workers.yml" +} + +trap cleanup EXIT + +# Start distributed system +log_info "Starting distributed system with $NUM_WORKERS workers..." +cd "${PROJECT_ROOT}" +"${SCRIPT_DIR}/start-distributed.sh" "$NUM_WORKERS" > /dev/null 2>&1 + +# Wait for all services to be ready +log_info "Waiting for services to be ready..." +sleep 30 + +# Verify services are running +log_info "Verifying services are running..." +MASTER_RUNNING=$(docker ps --filter "name=fuzzilli-postgres-master" --format "{{.Names}}" | wc -l) +if [ "$MASTER_RUNNING" -eq 1 ]; then + test_pass "Main postgres container is running" +else + test_fail "Main postgres container is not running" + exit 1 +fi + +WORKERS_RUNNING=$(docker ps --filter "name=fuzzer-worker" --format "{{.Names}}" | wc -l) +if [ "$WORKERS_RUNNING" -eq "$NUM_WORKERS" ]; then + test_pass "All $NUM_WORKERS worker containers are running" +else + test_fail "Expected $NUM_WORKERS workers, found $WORKERS_RUNNING" + exit 1 +fi + +LOCAL_POSTGRES_RUNNING=$(docker ps --filter "name=postgres-local" --format "{{.Names}}" | wc -l) +if [ "$LOCAL_POSTGRES_RUNNING" -eq "$NUM_WORKERS" ]; then + test_pass "All $NUM_WORKERS local postgres containers are running" +else + test_fail "Expected $NUM_WORKERS local postgres containers, found $LOCAL_POSTGRES_RUNNING" + exit 1 +fi + +# Test 1: Push Sync - Insert test program into local database and verify it appears in master +log_info "" +log_info "Test 1: Push Sync" +log_info "==================" + +# Generate a test program hash (simplified - in real scenario, this would be a proper FuzzIL program) +TEST_HASH="test_push_$(date +%s)" +TEST_PROGRAM_B64="dGVzdF9wcm9ncmFt" # base64("test_program") + +# Insert test program into first worker's local database +log_info "Inserting test program into worker 1 local database..." +docker exec postgres-local-1 psql -U fuzzilli -d fuzzilli_local -c " + INSERT INTO main (fuzzer_name, engine_type, status) VALUES ('fuzzer-1', 'v8', 'active') ON CONFLICT DO NOTHING; + SELECT fuzzer_id INTO TEMP temp_fuzzer_id FROM main WHERE fuzzer_name = 'fuzzer-1'; + INSERT INTO fuzzer (program_hash, fuzzer_id, program_size, program_base64, inserted_at) + SELECT '$TEST_HASH', fuzzer_id, 100, '$TEST_PROGRAM_B64', NOW() + FROM temp_fuzzer_id; +" > /dev/null 2>&1 + +if [ $? -eq 0 ]; then + test_pass "Test program inserted into local database" +else + test_fail "Failed to insert test program into local database" +fi + +# Wait for sync interval +log_info "Waiting ${SYNC_INTERVAL}s for push sync..." +sleep $((SYNC_INTERVAL + 10)) + +# Check if program appears in master database +log_info "Checking if program appears in master database..." +PROGRAM_IN_MASTER=$(docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -t -c " + SELECT COUNT(*) FROM fuzzer WHERE program_hash = '$TEST_HASH'; +" | tr -d ' ') + +if [ "$PROGRAM_IN_MASTER" -gt 0 ]; then + test_pass "Test program synced to master database (push sync working)" +else + test_fail "Test program not found in master database (push sync may have failed)" +fi + +# Test 2: Pull Sync - Insert test program into master and verify it appears in worker local databases +log_info "" +log_info "Test 2: Pull Sync" +log_info "==================" + +TEST_PULL_HASH="test_pull_$(date +%s)" + +# Insert test program into master database (simulating another worker) +log_info "Inserting test program into master database (simulating another worker)..." +docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -c " + INSERT INTO main (fuzzer_name, engine_type, status) VALUES ('fuzzer-test-source', 'v8', 'active') ON CONFLICT DO NOTHING; + SELECT fuzzer_id INTO TEMP temp_fuzzer_id FROM main WHERE fuzzer_name = 'fuzzer-test-source'; + INSERT INTO fuzzer (program_hash, fuzzer_id, program_size, program_base64, inserted_at) + SELECT '$TEST_PULL_HASH', fuzzer_id, 100, '$TEST_PROGRAM_B64', NOW() + FROM temp_fuzzer_id; +" > /dev/null 2>&1 + +if [ $? -eq 0 ]; then + test_pass "Test program inserted into master database" +else + test_fail "Failed to insert test program into master database" +fi + +# Wait for sync interval +log_info "Waiting ${SYNC_INTERVAL}s for pull sync..." +sleep $((SYNC_INTERVAL + 10)) + +# Check if program appears in worker local databases +log_info "Checking if program appears in worker local databases..." +WORKER_WITH_PROGRAM=0 +for i in $(seq 1 $NUM_WORKERS); do + COUNT=$(docker exec postgres-local-${i} psql -U fuzzilli -d fuzzilli_local -t -c " + SELECT COUNT(*) FROM fuzzer WHERE program_hash = '$TEST_PULL_HASH'; + " 2>/dev/null | tr -d ' ' || echo "0") + + if [ "$COUNT" -gt 0 ]; then + WORKER_WITH_PROGRAM=$((WORKER_WITH_PROGRAM + 1)) + fi +done + +if [ "$WORKER_WITH_PROGRAM" -gt 0 ]; then + test_pass "Test program pulled to $WORKER_WITH_PROGRAM worker(s) (pull sync working)" +else + test_fail "Test program not found in any worker local database (pull sync may have failed)" +fi + +# Test 3: Fuzzing Activity +log_info "" +log_info "Test 3: Fuzzing Activity" +log_info "========================" + +# Check worker logs for fuzzing activity +log_info "Checking worker logs for fuzzing activity..." +FUZZING_DETECTED=0 +for i in $(seq 1 $NUM_WORKERS); do + if docker logs fuzzer-worker-${i} 2>&1 | grep -qiE "(fuzzing|execution|program)" | head -5 | grep -q .; then + FUZZING_DETECTED=$((FUZZING_DETECTED + 1)) + fi +done + +if [ "$FUZZING_DETECTED" -gt 0 ]; then + test_pass "Fuzzing activity detected in $FUZZING_DETECTED worker(s)" +else + test_warn "No clear fuzzing activity detected in logs (workers may still be starting)" +fi + +# Check execution counts in databases +log_info "Checking execution counts in databases..." +MASTER_EXECUTIONS=$(docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -t -c " + SELECT COUNT(*) FROM execution; +" 2>/dev/null | tr -d ' ' || echo "0") + +if [ "$MASTER_EXECUTIONS" -gt 0 ]; then + test_pass "Executions found in master database: $MASTER_EXECUTIONS" +else + test_warn "No executions found in master database yet (workers may still be starting)" +fi + +# Test 4: Database Updates +log_info "" +log_info "Test 4: Database Updates" +log_info "=======================" + +# Check program counts +MASTER_PROGRAMS=$(docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -t -c " + SELECT COUNT(*) FROM program; +" 2>/dev/null | tr -d ' ' || echo "0") + +LOCAL_PROGRAMS_TOTAL=0 +for i in $(seq 1 $NUM_WORKERS); do + COUNT=$(docker exec postgres-local-${i} psql -U fuzzilli -d fuzzilli_local -t -c " + SELECT COUNT(*) FROM program; + " 2>/dev/null | tr -d ' ' || echo "0") + LOCAL_PROGRAMS_TOTAL=$((LOCAL_PROGRAMS_TOTAL + COUNT)) +done + +log_info "Master database programs: $MASTER_PROGRAMS" +log_info "Total local database programs: $LOCAL_PROGRAMS_TOTAL" + +if [ "$MASTER_PROGRAMS" -gt 0 ] || [ "$LOCAL_PROGRAMS_TOTAL" -gt 0 ]; then + test_pass "Database updates are being recorded (programs found)" +else + test_warn "No programs found in databases yet (workers may still be starting)" +fi + +# Test 5: Corpus Visibility +log_info "" +log_info "Test 5: Corpus Visibility" +log_info "========================" + +# Insert a program with a known hash into master +CORPUS_TEST_HASH="corpus_visibility_$(date +%s)" +log_info "Inserting test program for corpus visibility test..." +docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -c " + INSERT INTO main (fuzzer_name, engine_type, status) VALUES ('fuzzer-corpus-test', 'v8', 'active') ON CONFLICT DO NOTHING; + SELECT fuzzer_id INTO TEMP temp_fuzzer_id FROM main WHERE fuzzer_name = 'fuzzer-corpus-test'; + INSERT INTO fuzzer (program_hash, fuzzer_id, program_size, program_base64, inserted_at) + SELECT '$CORPUS_TEST_HASH', fuzzer_id, 100, '$TEST_PROGRAM_B64', NOW() + FROM temp_fuzzer_id; +" > /dev/null 2>&1 + +# Wait for pull sync +log_info "Waiting ${SYNC_INTERVAL}s for corpus visibility sync..." +sleep $((SYNC_INTERVAL + 10)) + +# Check if program can be retrieved from worker databases +VISIBLE_IN_WORKERS=0 +for i in $(seq 1 $NUM_WORKERS); do + COUNT=$(docker exec postgres-local-${i} psql -U fuzzilli -d fuzzilli_local -t -c " + SELECT COUNT(*) FROM fuzzer WHERE program_hash = '$CORPUS_TEST_HASH'; + " 2>/dev/null | tr -d ' ' || echo "0") + + if [ "$COUNT" -gt 0 ]; then + VISIBLE_IN_WORKERS=$((VISIBLE_IN_WORKERS + 1)) + fi +done + +if [ "$VISIBLE_IN_WORKERS" -gt 0 ]; then + test_pass "Corpus visibility test passed: program visible in $VISIBLE_IN_WORKERS worker(s)" +else + test_fail "Corpus visibility test failed: program not visible in worker databases" +fi + +# Summary +echo "" +echo "==========================================" +echo "Test Summary" +echo "==========================================" +echo "Tests Passed: $TESTS_PASSED" +echo "Tests Failed: $TESTS_FAILED" +echo "==========================================" + +if [ $TESTS_FAILED -eq 0 ]; then + log_info "All tests passed!" + exit 0 +else + log_error "Some tests failed. Check the output above for details." + exit 1 +fi + diff --git a/docker-compose.workers.yml b/docker-compose.workers.yml new file mode 100644 index 000000000..a8fa5fb62 --- /dev/null +++ b/docker-compose.workers.yml @@ -0,0 +1,117 @@ +version: '3.8' + +services: + + # Worker 1 - Local Postgres + postgres-local-1: + image: postgres:15-alpine + container_name: postgres-local-1 + environment: + POSTGRES_DB: fuzzilli_local + POSTGRES_USER: fuzzilli + POSTGRES_PASSWORD: fuzzilli123 + POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + volumes: + - postgres_local_data_1:/var/lib/postgresql/data + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - fuzzing-network + + # Worker 1 - Fuzzilli Container + fuzzer-worker-1: + build: + context: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai + dockerfile: Cloud/VRIG/Dockerfile.distributed + container_name: fuzzer-worker-1 + environment: + - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master + - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-1:5432/fuzzilli_local + - FUZZER_INSTANCE_NAME=fuzzer-1 + - SYNC_INTERVAL=300 + - TIMEOUT=2500 + - MIN_MUTATIONS_PER_SAMPLE=25 + - DEBUG_LOGGING=false + depends_on: + postgres-master: + condition: service_healthy + postgres-local-1: + condition: service_healthy + volumes: + - fuzzer_data_1:/home/app/Corpus + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro + restart: unless-stopped + networks: + - fuzzing-network + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + + + # Worker 2 - Local Postgres + postgres-local-2: + image: postgres:15-alpine + container_name: postgres-local-2 + environment: + POSTGRES_DB: fuzzilli_local + POSTGRES_USER: fuzzilli + POSTGRES_PASSWORD: fuzzilli123 + POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + volumes: + - postgres_local_data_2:/var/lib/postgresql/data + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - fuzzing-network + + # Worker 2 - Fuzzilli Container + fuzzer-worker-2: + build: + context: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai + dockerfile: Cloud/VRIG/Dockerfile.distributed + container_name: fuzzer-worker-2 + environment: + - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master + - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-2:5432/fuzzilli_local + - FUZZER_INSTANCE_NAME=fuzzer-2 + - SYNC_INTERVAL=300 + - TIMEOUT=2500 + - MIN_MUTATIONS_PER_SAMPLE=25 + - DEBUG_LOGGING=false + depends_on: + postgres-master: + condition: service_healthy + postgres-local-2: + condition: service_healthy + volumes: + - fuzzer_data_2:/home/app/Corpus + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro + restart: unless-stopped + networks: + - fuzzing-network + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + + +volumes: + postgres_local_data_1: + fuzzer_data_1: + postgres_local_data_2: + fuzzer_data_2: From 7c3c680d697bf5f0b90c2c9169f5b723717386bd Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Fri, 7 Nov 2025 01:49:35 -0500 Subject: [PATCH 18/23] added workers, sync todo --- Cloud/VRIG/Dockerfile.distributed | 20 +- Scripts/show-crash-javascript.sh | 90 ++++++ Scripts/show-stats.sh | 153 ++++++++++ Scripts/start-distributed.sh | 128 ++++++--- .../Fuzzilli/Corpus/PostgreSQLCorpus.swift | 195 +++++++++++-- Sources/Fuzzilli/Database/DatabasePool.swift | 5 + .../Fuzzilli/Database/PostgreSQLStorage.swift | 263 +++++++----------- Sources/FuzzilliCli/main.swift | 59 ++-- docker-compose.distributed.yml | 76 ----- docker-compose.master.yml | 32 +++ docker-compose.workers.yml | 14 + docker-compose.yml | 38 --- 12 files changed, 732 insertions(+), 341 deletions(-) create mode 100755 Scripts/show-crash-javascript.sh create mode 100755 Scripts/show-stats.sh delete mode 100644 docker-compose.distributed.yml create mode 100644 docker-compose.master.yml delete mode 100644 docker-compose.yml diff --git a/Cloud/VRIG/Dockerfile.distributed b/Cloud/VRIG/Dockerfile.distributed index 7c4f0050f..8dd2b2c31 100644 --- a/Cloud/VRIG/Dockerfile.distributed +++ b/Cloud/VRIG/Dockerfile.distributed @@ -44,6 +44,7 @@ RUN mkdir -p ./Corpus # Environment variables for distributed fuzzing ENV MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli +ENV POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master ENV FUZZER_INSTANCE_NAME=fuzzer-default ENV SYNC_INTERVAL=300 ENV TIMEOUT=2500 @@ -51,4 +52,21 @@ ENV MIN_MUTATIONS_PER_SAMPLE=25 ENV DEBUG_LOGGING=false # Default command for distributed fuzzing -CMD ./FuzzilliCli --profile=v8debug --engine=multi --resume --corpus=postgresql --master-postgres-url="${MASTER_POSTGRES_URL}" --fuzzer-instance-name="${FUZZER_INSTANCE_NAME}" --sync-interval="${SYNC_INTERVAL}" --timeout="${TIMEOUT}" --minMutationsPerSample="${MIN_MUTATIONS_PER_SAMPLE}" --postgres-logging ./fuzzbuild/d8 +# Use dual database mode if both LOCAL_POSTGRES_URL and MASTER_POSTGRES_URL are set +# Otherwise fall back to single database mode with POSTGRES_URL +CMD if [ -n "$LOCAL_POSTGRES_URL" ] && [ -n "$MASTER_POSTGRES_URL" ]; then \ + ./FuzzilliCli --profile=v8debug --engine=multi --resume --corpus=postgresql \ + --local-postgres-url="${LOCAL_POSTGRES_URL}" \ + --master-postgres-url="${MASTER_POSTGRES_URL}" \ + --sync-interval="${SYNC_INTERVAL}" \ + --timeout="${TIMEOUT}" \ + --minMutationsPerSample="${MIN_MUTATIONS_PER_SAMPLE}" \ + --postgres-logging ./fuzzbuild/d8; \ + else \ + ./FuzzilliCli --profile=v8debug --engine=multi --resume --corpus=postgresql \ + --postgres-url="${POSTGRES_URL}" \ + --sync-interval="${SYNC_INTERVAL}" \ + --timeout="${TIMEOUT}" \ + --minMutationsPerSample="${MIN_MUTATIONS_PER_SAMPLE}" \ + --postgres-logging ./fuzzbuild/d8; \ + fi diff --git a/Scripts/show-crash-javascript.sh b/Scripts/show-crash-javascript.sh new file mode 100755 index 000000000..280c86e04 --- /dev/null +++ b/Scripts/show-crash-javascript.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +# Script to show JavaScript code from crash programs +# Usage: ./Scripts/show-crash-javascript.sh [worker_num] + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +cd "$PROJECT_DIR" + +# Colors for output +CYAN='\033[0;36m' +YELLOW='\033[1;33m' +GREEN='\033[0;32m' +NC='\033[0m' # No Color + +show_crash_javascript() { + local worker_num=$1 + local container="postgres-local-${worker_num}" + local database="fuzzilli_local" + + echo -e "${CYAN}========================================${NC}" + echo -e "${CYAN} Worker ${worker_num} Crash Programs${NC}" + echo -e "${CYAN}========================================${NC}" + echo "" + + # Get all crash program hashes (one per line) + local crash_hashes=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT DISTINCT e.program_hash FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.program_hash;" 2>/dev/null | grep -v '^$' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + if [ -z "$crash_hashes" ]; then + echo -e "${YELLOW}No crashes found for Worker ${worker_num}${NC}" + echo "" + return + fi + + # Process each crash hash + echo "$crash_hashes" | while IFS= read -r hash; do + if [ -z "$hash" ]; then + continue + fi + + echo -e "${GREEN}--- Crash Program: ${hash} ---${NC}" + + # Get execution details + local exec_details=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT e.execution_id, e.signal_code, e.exit_code, eo.description, e.created_at FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE e.program_hash = '${hash}' AND eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 1;" 2>/dev/null) + + if [ -n "$exec_details" ]; then + echo -e "${YELLOW}Execution Details:${NC}" + echo "$exec_details" | sed 's/^/ /' + echo "" + fi + + # Get and decode the JavaScript + echo -e "${YELLOW}JavaScript Code:${NC}" + local base64_program=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT program_base64 FROM program WHERE program_hash = '${hash}';" 2>/dev/null | tr -d ' \n\r') + + if [ -n "$base64_program" ]; then + # Decode base64 and extract JavaScript strings + local javascript=$(echo "$base64_program" | base64 -d 2>/dev/null | strings | grep -E "(fuzzilli|function|var|let|const|if|for|while|return)" | head -10) + + if [ -n "$javascript" ]; then + echo "$javascript" | sed 's/^/ /' + else + # Try to get any readable strings + local all_strings=$(echo "$base64_program" | base64 -d 2>/dev/null | strings | grep -v "^$" | tail -5) + if [ -n "$all_strings" ]; then + echo "$all_strings" | sed 's/^/ /' + else + echo " (Could not extract JavaScript - program may be in binary format)" + fi + fi + else + echo " (Program not found in database)" + fi + + echo "" + done <<< "$crash_hashes" +} + +# If worker number specified, show only that worker +if [ -n "$1" ]; then + show_crash_javascript "$1" +else + # Show both workers + show_crash_javascript 1 + show_crash_javascript 2 +fi + diff --git a/Scripts/show-stats.sh b/Scripts/show-stats.sh new file mode 100755 index 000000000..d67f35552 --- /dev/null +++ b/Scripts/show-stats.sh @@ -0,0 +1,153 @@ +#!/bin/bash + +# Script to show current statistics for distributed Fuzzilli setup +# Usage: ./Scripts/show-stats.sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +cd "$PROJECT_DIR" + +MASTER_COMPOSE="docker-compose.master.yml" +WORKERS_COMPOSE="docker-compose.workers.yml" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +echo -e "${CYAN}========================================${NC}" +echo -e "${CYAN} Distributed Fuzzilli Statistics${NC}" +echo -e "${CYAN}========================================${NC}" +echo "" + +# Check if containers are running +check_container() { + local container=$1 + if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then + return 0 + else + return 1 + fi +} + +# Get database stats +get_db_stats() { + local container=$1 + local database=$2 + local label=$3 + + if ! check_container "$container"; then + echo -e "${RED}${label}: Container not running${NC}" + return + fi + + echo -e "${BLUE}=== ${label} ===${NC}" + + # Fuzzer registrations + echo -e "${YELLOW}Fuzzer Registrations:${NC}" + docker exec "$container" psql -U fuzzilli -d "$database" -c "SELECT fuzzer_id, fuzzer_name, status, created_at FROM main ORDER BY fuzzer_id;" 2>/dev/null || echo " No fuzzers registered" + + # Program counts + local program_count=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT COUNT(*) FROM fuzzer;" 2>/dev/null | tr -d ' ' || echo "0") + local execution_count=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT COUNT(*) FROM execution;" 2>/dev/null | tr -d ' ' || echo "0") + local program_table_count=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT COUNT(*) FROM program;" 2>/dev/null | tr -d ' ' || echo "0") + + # Crash count + local crash_count=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed';" 2>/dev/null | tr -d ' ' || echo "0") + + echo -e "${YELLOW}Statistics:${NC}" + echo " Programs (corpus): $program_count" + echo " Programs (executed): $program_table_count" + echo " Executions: $execution_count" + echo " Crashes: $crash_count" + + # Recent activity (last 5 programs) + echo -e "${YELLOW}Recent Programs (last 5):${NC}" + docker exec "$container" psql -U fuzzilli -d "$database" -c "SELECT program_hash, program_size, created_at FROM fuzzer ORDER BY created_at DESC LIMIT 5;" 2>/dev/null || echo " No programs found" + + # Crash details + if [ "$crash_count" != "0" ] && [ "$crash_count" != "" ]; then + echo -e "${YELLOW}Crashes (last 3):${NC}" + docker exec "$container" psql -U fuzzilli -d "$database" -c "SELECT e.execution_id, e.program_hash, e.execution_time_ms, e.signal_code, e.exit_code, eo.description, e.created_at FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 3;" 2>/dev/null || echo " No crash details available" + fi + + echo "" +} + +# Get worker container stats +get_worker_stats() { + local worker_num=$1 + local container="fuzzer-worker-${worker_num}" + local postgres_container="postgres-local-${worker_num}" + + if ! check_container "$container"; then + echo -e "${RED}Worker ${worker_num}: Container not running${NC}" + echo "" + return + fi + + echo -e "${GREEN}=== Worker ${worker_num} ===${NC}" + + # Container status + local status=$(docker inspect --format='{{.State.Status}}' "$container" 2>/dev/null) + local health=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "no-healthcheck") + echo -e "${YELLOW}Container Status:${NC} $status (health: $health)" + + # Get database stats + get_db_stats "$postgres_container" "fuzzilli_local" "Local Database" + + # Get recent logs + echo -e "${YELLOW}Recent Activity (last 3 lines):${NC}" + docker compose -f "$MASTER_COMPOSE" -f "$WORKERS_COMPOSE" logs --tail=3 "$container" 2>&1 | grep -v "level=warning" | tail -3 || echo " No recent activity" + echo "" +} + +# Master stats +echo -e "${GREEN}=== Master Database ===${NC}" +if check_container "fuzzilli-postgres-master"; then + status=$(docker inspect --format='{{.State.Status}}' "fuzzilli-postgres-master" 2>/dev/null) + health=$(docker inspect --format='{{.State.Health.Status}}' "fuzzilli-postgres-master" 2>/dev/null || echo "no-healthcheck") + echo -e "${YELLOW}Container Status:${NC} $status (health: $health)" + echo "" + get_db_stats "fuzzilli-postgres-master" "fuzzilli_master" "Master Database" +else + echo -e "${RED}Master container not running${NC}" + echo "" +fi + +# Worker stats +get_worker_stats 1 +get_worker_stats 2 + +# Summary +echo -e "${CYAN}========================================${NC}" +echo -e "${CYAN} Summary${NC}" +echo -e "${CYAN}========================================${NC}" + +if check_container "fuzzilli-postgres-master"; then + master_programs=$(docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -t -c "SELECT COUNT(*) FROM fuzzer;" 2>/dev/null | tr -d ' ' || echo "0") + master_executions=$(docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -t -c "SELECT COUNT(*) FROM execution;" 2>/dev/null | tr -d ' ' || echo "0") + echo -e "Master: ${GREEN}${master_programs}${NC} programs, ${GREEN}${master_executions}${NC} executions" +fi + +if check_container "postgres-local-1"; then + w1_programs=$(docker exec postgres-local-1 psql -U fuzzilli -d fuzzilli_local -t -c "SELECT COUNT(*) FROM fuzzer;" 2>/dev/null | tr -d ' ' || echo "0") + w1_executions=$(docker exec postgres-local-1 psql -U fuzzilli -d fuzzilli_local -t -c "SELECT COUNT(*) FROM execution;" 2>/dev/null | tr -d ' ' || echo "0") + echo -e "Worker 1: ${GREEN}${w1_programs}${NC} programs, ${GREEN}${w1_executions}${NC} executions" +fi + +if check_container "postgres-local-2"; then + w2_programs=$(docker exec postgres-local-2 psql -U fuzzilli -d fuzzilli_local -t -c "SELECT COUNT(*) FROM fuzzer;" 2>/dev/null | tr -d ' ' || echo "0") + w2_executions=$(docker exec postgres-local-2 psql -U fuzzilli -d fuzzilli_local -t -c "SELECT COUNT(*) FROM execution;" 2>/dev/null | tr -d ' ' || echo "0") + echo -e "Worker 2: ${GREEN}${w2_programs}${NC} programs, ${GREEN}${w2_executions}${NC} executions" +fi + +echo "" +echo -e "${CYAN}========================================${NC}" + diff --git a/Scripts/start-distributed.sh b/Scripts/start-distributed.sh index a177d0eda..a34d0bc41 100755 --- a/Scripts/start-distributed.sh +++ b/Scripts/start-distributed.sh @@ -1,24 +1,40 @@ #!/bin/bash -# start-distributed.sh - Start distributed fuzzing with N workers -# Usage: ./Scripts/start-distributed.sh +# start-distributed.sh - Start distributed fuzzing with X workers +# Usage: ./Scripts/start-distributed.sh +# where X is the number of fuzzer workers to create # -# This script creates N worker containers, each with: -# - A fuzzilli container -# - A local postgres container -# - Proper networking and volumes +# Creates: +# - 1 master postgres database +# - X fuzzer worker containers +# - X local postgres containers (one per fuzzer) +# +# Environment variables: +# - V8_BUILD_PATH: Path to V8 build directory on host (default: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild) +# - POSTGRES_PASSWORD: PostgreSQL password (default: fuzzilli123) +# - SYNC_INTERVAL: Sync interval in seconds (default: 300) +# - TIMEOUT: Execution timeout in ms (default: 2500) +# - MIN_MUTATIONS_PER_SAMPLE: Minimum mutations per sample (default: 25) +# - DEBUG_LOGGING: Enable debug logging (default: false) set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" -COMPOSE_FILE="${PROJECT_ROOT}/docker-compose.distributed.yml" -COMPOSE_OVERRIDE="${PROJECT_ROOT}/docker-compose.workers.yml" +MASTER_COMPOSE="${PROJECT_ROOT}/docker-compose.master.yml" +WORKER_COMPOSE="${PROJECT_ROOT}/docker-compose.workers.yml" # Check if number of workers is provided if [ $# -eq 0 ]; then - echo "Usage: $0 " + echo "Usage: $0 " + echo " where X is the number of fuzzer workers to create" + echo "" echo "Example: $0 3" + echo " Creates: 1 master postgres + 3 fuzzer workers + 3 local postgres" + echo "" + echo "Environment variables:" + echo " V8_BUILD_PATH - Path to V8 build on host (default: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild)" + echo " POSTGRES_PASSWORD - PostgreSQL password (default: fuzzilli123)" exit 1 fi @@ -30,7 +46,13 @@ if ! [[ "$NUM_WORKERS" =~ ^[0-9]+$ ]] || [ "$NUM_WORKERS" -lt 1 ]; then exit 1 fi -echo "Starting distributed fuzzing with $NUM_WORKERS workers..." +echo "==========================================" +echo "Starting Distributed Fuzzilli" +echo "==========================================" +echo "Workers: $NUM_WORKERS" +echo "Master Postgres: 1" +echo "Local Postgres: $NUM_WORKERS" +echo "" # Load environment variables if [ -f "${PROJECT_ROOT}/.env" ]; then @@ -39,22 +61,36 @@ elif [ -f "${PROJECT_ROOT}/env.distributed" ]; then source "${PROJECT_ROOT}/env.distributed" fi +# Set defaults POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-fuzzilli123} +V8_BUILD_PATH=${V8_BUILD_PATH:-/home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild} SYNC_INTERVAL=${SYNC_INTERVAL:-300} TIMEOUT=${TIMEOUT:-2500} MIN_MUTATIONS_PER_SAMPLE=${MIN_MUTATIONS_PER_SAMPLE:-25} DEBUG_LOGGING=${DEBUG_LOGGING:-false} -# Generate docker-compose override file with worker services -cat > "${COMPOSE_OVERRIDE}" < "${WORKER_COMPOSE}" <> "${COMPOSE_OVERRIDE}" <> "${WORKER_COMPOSE}" <> "${COMPOSE_OVERRIDE}" <> "${WORKER_COMPOSE}" <> "${COMPOSE_OVERRIDE}" <> "${WORKER_COMPOSE}" < /dev/null 2>&1; then - echo "Main postgres is ready" + echo "✓ Master postgres is ready" break fi sleep 1 @@ -145,40 +192,59 @@ while [ $timeout -gt 0 ]; do done if [ $timeout -eq 0 ]; then - echo "Error: Main postgres failed to start" + echo "✗ Error: Master postgres failed to start" exit 1 fi +# Start worker services +echo "Starting worker services..." +docker compose -f "${MASTER_COMPOSE}" -f "${WORKER_COMPOSE}" up -d --build + # Wait for local postgres containers to be healthy echo "Waiting for local postgres containers to be ready..." for i in $(seq 1 $NUM_WORKERS); do timeout=60 while [ $timeout -gt 0 ]; do if docker exec postgres-local-${i} pg_isready -U fuzzilli -d fuzzilli_local > /dev/null 2>&1; then - echo "Local postgres-${i} is ready" + echo "✓ Local postgres-${i} is ready" break fi sleep 1 timeout=$((timeout - 1)) done if [ $timeout -eq 0 ]; then - echo "Warning: Local postgres-${i} may not be ready" + echo "⚠ Warning: Local postgres-${i} may not be ready" fi done +# Wait a bit for fuzzers to start +echo "" +echo "Waiting for fuzzer containers to initialize..." +sleep 10 + echo "" +echo "==========================================" echo "Distributed fuzzing setup complete!" -echo "Started $NUM_WORKERS workers:" +echo "==========================================" +echo "" +echo "Services started:" +echo " Master Postgres: fuzzilli-postgres-master" for i in $(seq 1 $NUM_WORKERS); do - echo " - fuzzer-worker-${i} (local postgres: postgres-local-${i})" + echo " Worker $i: fuzzer-worker-${i} (local postgres: postgres-local-${i})" done echo "" echo "To view logs:" -echo " docker-compose -f ${COMPOSE_FILE} -f ${COMPOSE_OVERRIDE} logs -f" +echo " docker compose -f ${MASTER_COMPOSE} -f ${WORKER_COMPOSE} logs -f" +echo "" +echo "To view specific worker logs:" +echo " docker compose -f ${MASTER_COMPOSE} -f ${WORKER_COMPOSE} logs -f fuzzer-worker-1" +echo "" +echo "To check status:" +echo " docker compose -f ${MASTER_COMPOSE} -f ${WORKER_COMPOSE} ps" echo "" echo "To stop all services:" -echo " docker-compose -f ${COMPOSE_FILE} -f ${COMPOSE_OVERRIDE} down" +echo " docker compose -f ${MASTER_COMPOSE} -f ${WORKER_COMPOSE} down" echo "" echo "To stop a specific worker:" echo " docker stop fuzzer-worker- postgres-local-" - +echo "" diff --git a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift index d71647cd3..7b820325f 100644 --- a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift +++ b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift @@ -22,9 +22,11 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { private let maxSize: Int private let minMutationsPerSample: Int private let syncInterval: TimeInterval - private let databasePool: DatabasePool + private let databasePool: DatabasePool // Local database pool + private let masterDatabasePool: DatabasePool? // Optional master database pool for sync private let fuzzerInstanceId: String - private let storage: PostgreSQLStorage + private let storage: PostgreSQLStorage // Local storage + private let masterStorage: PostgreSQLStorage? // Optional master storage for sync private let resume: Bool private let enableLogging: Bool @@ -52,7 +54,8 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { /// Track fuzzer registration status private var fuzzerRegistered = false - private var fuzzerId: Int? + private var fuzzerId: Int? // Local database fuzzer ID + private var masterFuzzerId: Int? // Master database fuzzer ID (for sync operations) /// Batch execution storage private var pendingExecutions: [(Program, ProgramAspects, DatabaseExecutionPurpose)] = [] @@ -65,11 +68,12 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { minSize: Int, maxSize: Int, minMutationsPerSample: Int, - databasePool: DatabasePool, + databasePool: DatabasePool, // Local database pool fuzzerInstanceId: String, syncInterval: TimeInterval = 60.0, // Default 1 minute sync interval resume: Bool = true, // Default to resume from previous state - enableLogging: Bool = false + enableLogging: Bool = false, + masterDatabasePool: DatabasePool? = nil // Optional master database pool for sync ) { // The corpus must never be empty assert(minSize >= 1) @@ -79,11 +83,13 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { self.maxSize = maxSize self.minMutationsPerSample = minMutationsPerSample self.databasePool = databasePool + self.masterDatabasePool = masterDatabasePool self.fuzzerInstanceId = fuzzerInstanceId self.syncInterval = syncInterval self.resume = resume self.enableLogging = enableLogging self.storage = PostgreSQLStorage(databasePool: databasePool, enableLogging: enableLogging) + self.masterStorage = masterDatabasePool.map { PostgreSQLStorage(databasePool: $0, enableLogging: enableLogging) } // Set optimized batch size for better throughput (reduced from 1M to 100k for more frequent processing) self.executionBatchSize = 100_000 @@ -181,19 +187,52 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { // Initialize database pool and register fuzzer (only once) Task { do { + // Initialize local database pool try await databasePool.initialize() if enableLogging { - self.logger.info("Database pool initialized successfully") + self.logger.info("Local database pool initialized successfully") + } + + // Initialize master database pool if available + if let masterPool = masterDatabasePool { + try await masterPool.initialize() + if enableLogging { + self.logger.info("Master database pool initialized successfully") + } } // Register this fuzzer instance in the database (only once) if !fuzzerRegistered { do { - let id = try await registerFuzzerWithRetry() - fuzzerId = id + // Register in master database first (for sync) + let masterId: Int? + if let masterStorage = masterStorage { + masterId = try await masterStorage.registerFuzzer( + name: fuzzerInstanceId, + engineType: "v8" + ) + if enableLogging { + self.logger.info("Fuzzer registered in master database with ID: \(masterId ?? -1)") + } + } else { + masterId = nil + } + + // Register in local database (for local storage) + let localId = try await storage.registerFuzzer( + name: fuzzerInstanceId, + engineType: "v8" + ) + if enableLogging { + self.logger.info("Fuzzer registered in local database with ID: \(localId)") + } + + // Use local ID for local operations, master ID is used for sync operations + fuzzerId = localId + masterFuzzerId = masterId fuzzerRegistered = true if enableLogging { - self.logger.info("Fuzzer registered in database with ID: \(id)") + self.logger.info("Fuzzer registration complete - local ID: \(localId), master ID: \(masterId?.description ?? "none")") } } catch { logger.error("Failed to register fuzzer after retries: \(error)") @@ -280,10 +319,18 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } } - // Schedule periodic synchronization with PostgreSQL + // Schedule periodic synchronization with PostgreSQL (push to master) fuzzer.timers.scheduleTask(every: syncInterval, syncWithDatabase) if self.enableLogging { - logger.info("Scheduled database sync every \(syncInterval) seconds") + logger.info("Scheduled database sync (push) every \(syncInterval) seconds") + } + + // Schedule periodic pull sync from master (if master storage is available) + if masterStorage != nil { + fuzzer.timers.scheduleTask(every: syncInterval, pullFromMaster) + if self.enableLogging { + logger.info("Scheduled database sync (pull) every \(syncInterval) seconds") + } } // Schedule periodic flush of execution batch @@ -693,17 +740,31 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { return } - // Store the program with metadata - _ = try await storage.storeProgram( - program: program, - fuzzerId: fuzzerId, - metadata: metadata - ) + // Store the program with metadata to master (if available) for sync + // Use master fuzzer ID for master sync, local fuzzer ID for local storage + if let masterStorage = masterStorage, let masterId = masterFuzzerId { + _ = try await masterStorage.storeProgram( + program: program, + fuzzerId: masterId, + metadata: metadata + ) + } else { + // Fallback to local storage if master not available + _ = try await storage.storeProgram( + program: program, + fuzzerId: fuzzerId, + metadata: metadata + ) + } // Program synced to database silently } catch { - logger.error("Failed to sync program to database: \(error)") + if enableLogging { + logger.error("Failed to sync program to database: \(String(reflecting: error))") + } else { + logger.error("Failed to sync program to database: \(error)") + } // Re-add to pending sync for retry _ = withLock(syncLock) { pendingSyncOperations.insert(DatabaseUtils.calculateProgramHash(program: program)) @@ -714,6 +775,100 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { // Database sync completed silently } + /// Pull sync from master database + private func pullFromMaster() { + Task { + await performPullFromMaster() + } + } + + /// Perform pull sync from master: fetch new programs and add to local cache + private func performPullFromMaster() async { + // Only pull if master storage is available + guard let masterStorage = masterStorage, let masterId = masterFuzzerId, let localId = fuzzerId else { + return + } + + do { + // Get recent programs from master (last 24 hours, limit 100) + // Use master fuzzer ID to query master database + let sinceDate = Date().addingTimeInterval(-24 * 60 * 60) // 24 hours ago + let masterPrograms = try await masterStorage.getRecentPrograms( + fuzzerId: masterId, + since: sinceDate, + limit: 100 + ) + + guard !masterPrograms.isEmpty else { + if enableLogging { + logger.info("No new programs to pull from master") + } + return + } + + // Filter out programs already in local cache + let localHashes = withLock(cacheLock) { + Set(programCache.keys) + } + + let newPrograms = masterPrograms.filter { program, _ in + let hash = DatabaseUtils.calculateProgramHash(program: program) + return !localHashes.contains(hash) + } + + guard !newPrograms.isEmpty else { + if enableLogging { + logger.info("All programs from master already in local cache") + } + return + } + + if enableLogging { + logger.info("Pulling \(newPrograms.count) new programs from master") + } + + // Add new programs to local cache and local postgres + for (program, metadata) in newPrograms { + let hash = DatabaseUtils.calculateProgramHash(program: program) + + // Add to local cache + withLock(cacheLock) { + programCache[hash] = (program: program, metadata: metadata) + } + + // Add to local postgres using local fuzzer ID + do { + _ = try await storage.storeProgram( + program: program, + fuzzerId: localId, + metadata: metadata + ) + + // Add to ring buffer for fast access + withLock(cacheLock) { + programs.append(program) + ages.append(0) + programHashes.append(hash) + totalEntryCounter += 1 + } + + if enableLogging { + logger.info("Pulled program from master: hash=\(hash.prefix(8))...") + } + } catch { + logger.error("Failed to store pulled program in local database: \(error)") + } + } + + if enableLogging { + logger.info("Successfully pulled \(newPrograms.count) programs from master") + } + + } catch { + logger.error("Failed to pull programs from master: \(error)") + } + } + /// Register fuzzer with retry logic private func registerFuzzerWithRetry() async throws -> Int { // Use the fuzzerInstanceId directly as the name to avoid double "fuzzer-" prefix @@ -728,7 +883,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { if enableLogging { self.logger.info("Attempting to register fuzzer (attempt \(attempt)/\(maxRetries))") } - let id = try await storage.registerFuzzer( + // Use master storage if available, otherwise use local storage + let storageToUse = masterStorage ?? storage + let id = try await storageToUse.registerFuzzer( name: fuzzerName, engineType: engineType ) diff --git a/Sources/Fuzzilli/Database/DatabasePool.swift b/Sources/Fuzzilli/Database/DatabasePool.swift index a4b448e37..dd58e04b8 100644 --- a/Sources/Fuzzilli/Database/DatabasePool.swift +++ b/Sources/Fuzzilli/Database/DatabasePool.swift @@ -147,6 +147,11 @@ public class DatabasePool { return connectionString } + /// Get parsed connection configuration for direct connections + public func getConnectionConfiguration() -> SQLPostgresConfiguration? { + return configuration + } + /// Get the database configuration public func getConfiguration() throws -> SQLPostgresConfiguration { guard let config = configuration else { diff --git a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift index b52f0299a..73ee37918 100644 --- a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift +++ b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift @@ -27,36 +27,77 @@ public class PostgreSQLStorage { self.logger = Logging.Logger(label: "PostgreSQLStorage") } - // MARK: - Fuzzer Management + // MARK: - Helper Methods - /// Register a new fuzzer instance in the database - public func registerFuzzer(name: String, engineType: String, hostname: String? = nil) async throws -> Int { - if enableLogging { - logger.info("Registering fuzzer: name=\(name), engineType=\(engineType), hostname=\(hostname ?? "none")") - } - - // Use direct connection to avoid connection pool deadlock + /// Create a direct connection using the database pool's configuration + private func createDirectConnection() async throws -> PostgresConnection { guard let eventLoopGroup = databasePool.getEventLoopGroup() else { throw PostgreSQLStorageError.noResult } - let connection = try await PostgresConnection.connect( + // Get the connection string and parse it + let connectionString = databasePool.getConnectionString() + guard let url = URL(string: connectionString) else { + throw PostgreSQLStorageError.connectionFailed + } + + guard url.scheme == "postgresql" || url.scheme == "postgres" else { + throw PostgreSQLStorageError.connectionFailed + } + + let host = url.host ?? "localhost" + let port = url.port ?? 5432 + let username = url.user ?? "postgres" + let password = url.password ?? "" + let database = url.path.isEmpty ? nil : String(url.path.dropFirst()) // Remove leading slash + + if enableLogging { + logger.info("Creating direct connection to: host=\(host), port=\(port), database=\(database ?? "none")") + } + + return try await PostgresConnection.connect( on: eventLoopGroup.next(), configuration: PostgresConnection.Configuration( - host: "localhost", - port: 5432, - username: "fuzzilli", - password: "fuzzilli123", - database: "fuzzilli_master", - tls: .disable + host: host, + port: port, + username: username, + password: password, + database: database, + tls: .disable // For now, disable TLS ), id: 0, logger: logger ) + } + + // MARK: - Fuzzer Management + + /// Register a new fuzzer instance in the database + public func registerFuzzer(name: String, engineType: String, hostname: String? = nil) async throws -> Int { + if enableLogging { + logger.info("Registering fuzzer: name=\(name), engineType=\(engineType), hostname=\(hostname ?? "none")") + } + + // Use direct connection to avoid connection pool deadlock + let connection: PostgresConnection + do { + connection = try await createDirectConnection() + if enableLogging { + let connString = databasePool.getConnectionString() + logger.info("Created direct connection to: \(connString)") + } + } catch { + if enableLogging { + logger.error("Failed to create direct connection: \(error)") + } + throw error + } defer { Task { _ = try? await connection.close() } } // First, check if a fuzzer with this name already exists - let checkQuery: PostgresQuery = "SELECT fuzzer_id, status FROM main WHERE fuzzer_name = \(name)" + // Escape single quotes in name + let escapedName = name.replacingOccurrences(of: "'", with: "''") + let checkQuery = PostgresQuery(stringLiteral: "SELECT fuzzer_id, status FROM main WHERE fuzzer_name = '\(escapedName)'") let checkResult = try await connection.query(checkQuery, logger: self.logger) let checkRows = try await checkResult.collect() @@ -81,15 +122,48 @@ public class PostgreSQLStorage { } // If no existing fuzzer found, create a new one - let insertQuery: PostgresQuery = """ + // Escape single quotes in engine type (name already escaped above) + let escapedEngineType = engineType.replacingOccurrences(of: "'", with: "''") + let insertQuery = PostgresQuery(stringLiteral: """ INSERT INTO main (fuzzer_name, engine_type, status) - VALUES (\(name), \(engineType), 'active') + VALUES ('\(escapedName)', '\(escapedEngineType)', 'active') RETURNING fuzzer_id - """ + """) + + if enableLogging { + logger.info("Executing INSERT query to create new fuzzer") + } + + let result: PostgresRowSequence + do { + if enableLogging { + logger.info("Executing INSERT query: INSERT INTO main (fuzzer_name, engine_type, status) VALUES ('\(escapedName)', '\(escapedEngineType)', 'active') RETURNING fuzzer_id") + } + result = try await connection.query(insertQuery, logger: self.logger) + } catch { + if enableLogging { + logger.error("INSERT query failed with error: \(error)") + } + throw error + } + + let rows: [PostgresRow] + do { + rows = try await result.collect() + if enableLogging { + logger.info("INSERT query returned \(rows.count) rows") + } + } catch { + if enableLogging { + logger.error("Failed to collect rows from INSERT query: \(error)") + } + throw error + } - let result = try await connection.query(insertQuery, logger: self.logger) - let rows = try await result.collect() guard let row = rows.first else { + if enableLogging { + logger.error("INSERT query returned no rows - registration failed. This might indicate a connection issue or the query didn't execute properly.") + } throw PostgreSQLStorageError.noResult } @@ -107,23 +181,7 @@ public class PostgreSQLStorage { } // Use direct connection to avoid connection pool deadlock - guard let eventLoopGroup = databasePool.getEventLoopGroup() else { - throw PostgreSQLStorageError.noResult - } - - let connection = try await PostgresConnection.connect( - on: eventLoopGroup.next(), - configuration: PostgresConnection.Configuration( - host: "localhost", - port: 5432, - username: "fuzzilli", - password: "fuzzilli123", - database: "fuzzilli_master", - tls: .disable - ), - id: 0, - logger: logger - ) + let connection = try await createDirectConnection() defer { Task { _ = try? await connection.close() } } let query: PostgresQuery = "SELECT fuzzer_id, created_at, fuzzer_name, engine_type, status FROM main WHERE fuzzer_name = \(name)" @@ -161,23 +219,7 @@ public class PostgreSQLStorage { } // Use direct connection to avoid connection pool deadlock - guard let eventLoopGroup = databasePool.getEventLoopGroup() else { - throw PostgreSQLStorageError.noResult - } - - let connection = try await PostgresConnection.connect( - on: eventLoopGroup.next(), - configuration: PostgresConnection.Configuration( - host: "localhost", - port: 5432, - username: "fuzzilli", - password: "fuzzilli123", - database: "fuzzilli_master", - tls: .disable - ), - id: 0, - logger: logger - ) + let connection = try await createDirectConnection() defer { Task { _ = try? await connection.close() } } // Get program count for this fuzzer @@ -229,23 +271,7 @@ public class PostgreSQLStorage { guard !programs.isEmpty else { return [] } // Use direct connection to avoid connection pool deadlock - guard let eventLoopGroup = databasePool.getEventLoopGroup() else { - throw PostgreSQLStorageError.noResult - } - - let connection = try await PostgresConnection.connect( - on: eventLoopGroup.next(), - configuration: PostgresConnection.Configuration( - host: "localhost", - port: 5432, - username: "fuzzilli", - password: "fuzzilli123", - database: "fuzzilli_master", - tls: .disable - ), - id: 0, - logger: logger - ) + let connection = try await createDirectConnection() defer { Task { _ = try? await connection.close() } } var programHashes: [String] = [] @@ -330,23 +356,7 @@ public class PostgreSQLStorage { } // Use direct connection to avoid connection pool deadlock - guard let eventLoopGroup = databasePool.getEventLoopGroup() else { - throw PostgreSQLStorageError.noResult - } - - let connection = try await PostgresConnection.connect( - on: eventLoopGroup.next(), - configuration: PostgresConnection.Configuration( - host: "localhost", - port: 5432, - username: "fuzzilli", - password: "fuzzilli123", - database: "fuzzilli_master", - tls: .disable - ), - id: 0, - logger: logger - ) + let connection = try await createDirectConnection() defer { Task { _ = try? await connection.close() } } // Start transaction @@ -445,23 +455,7 @@ public class PostgreSQLStorage { } // Use direct connection to avoid connection pool deadlock - guard let eventLoopGroup = databasePool.getEventLoopGroup() else { - throw PostgreSQLStorageError.noResult - } - - let connection = try await PostgresConnection.connect( - on: eventLoopGroup.next(), - configuration: PostgresConnection.Configuration( - host: "localhost", - port: 5432, - username: "fuzzilli", - password: "fuzzilli123", - database: "fuzzilli_master", - tls: .disable - ), - id: 0, - logger: logger - ) + let connection = try await createDirectConnection() defer { Task { _ = try? await connection.close() } } // Insert into fuzzer table (corpus) @@ -482,7 +476,7 @@ public class PostgreSQLStorage { fuzzer_id = \(fuzzerId), program_size = \(program.size), program_base64 = '\(programBase64)' - WHERE program_hash = '\(programHash)'rm + WHERE program_hash = '\(programHash)' """ let updateResult = try await connection.query(updateQuery, logger: self.logger) let updateRows = try await updateResult.collect() @@ -538,23 +532,7 @@ public class PostgreSQLStorage { guard !executions.isEmpty else { return [] } // Use direct connection to avoid connection pool deadlock - guard let eventLoopGroup = databasePool.getEventLoopGroup() else { - throw PostgreSQLStorageError.noResult - } - - let connection = try await PostgresConnection.connect( - on: eventLoopGroup.next(), - configuration: PostgresConnection.Configuration( - host: "localhost", - port: 5432, - username: "fuzzilli", - password: "fuzzilli123", - database: "fuzzilli_master", - tls: .disable - ), - id: 0, - logger: logger - ) + let connection = try await createDirectConnection() defer { Task { _ = try? await connection.close() } } var executionIds: [Int] = [] @@ -652,23 +630,7 @@ public class PostgreSQLStorage { } // Use direct connection to avoid connection pool deadlock - guard let eventLoopGroup = databasePool.getEventLoopGroup() else { - throw PostgreSQLStorageError.noResult - } - - let connection = try await PostgresConnection.connect( - on: eventLoopGroup.next(), - configuration: PostgresConnection.Configuration( - host: "localhost", - port: 5432, - username: "fuzzilli", - password: "fuzzilli123", - database: "fuzzilli_master", - tls: .disable - ), - id: 0, - logger: logger - ) + let connection = try await createDirectConnection() defer { Task { _ = try? await connection.close() } } let executionTypeId = DatabaseUtils.mapExecutionType(purpose: executionType) @@ -777,23 +739,8 @@ public class PostgreSQLStorage { logger.info("Getting recent programs: fuzzerId=\(fuzzerId), since=\(since), limit=\(limit)") } - guard let eventLoopGroup = databasePool.getEventLoopGroup() else { - throw PostgreSQLStorageError.noResult - } - - let connection = try await PostgresConnection.connect( - on: eventLoopGroup.next(), - configuration: PostgresConnection.Configuration( - host: "localhost", - port: 5432, - username: "fuzzilli", - password: "fuzzilli123", - database: "fuzzilli_master", - tls: .disable - ), - id: 0, - logger: logger - ) + // Use direct connection using the database pool's configuration + let connection = try await createDirectConnection() defer { Task { _ = try? await connection.close() } } // Query for recent programs with their latest execution metadata diff --git a/Sources/FuzzilliCli/main.swift b/Sources/FuzzilliCli/main.swift index 99d1ab015..d31a7c33b 100755 --- a/Sources/FuzzilliCli/main.swift +++ b/Sources/FuzzilliCli/main.swift @@ -164,7 +164,9 @@ let enableWasm = args.has("--wasm") let forDifferentialFuzzing = args.has("--forDifferentialFuzzing") // PostgreSQL corpus specific arguments -let postgresUrl = args["--postgres-url"] +let postgresUrl = args["--postgres-url"] ?? ProcessInfo.processInfo.environment["POSTGRES_URL"] +let localPostgresUrl = args["--local-postgres-url"] ?? ProcessInfo.processInfo.environment["LOCAL_POSTGRES_URL"] +let masterPostgresUrl = args["--master-postgres-url"] ?? ProcessInfo.processInfo.environment["MASTER_POSTGRES_URL"] let syncInterval = args.int(for: "--sync-interval") ?? 10 let validateBeforeCache = args.has("--validate-before-cache") || !args.has("--no-validate-before-cache") // Default to true let executionHistorySize = args.int(for: "--execution-history-size") ?? 10 @@ -222,11 +224,16 @@ if corpusName == "markov" && staticCorpus { // PostgreSQL corpus validation if corpusName == "postgresql" { - if postgresUrl == nil { - configError("PostgreSQL corpus requires --postgres-url") - } - if syncInterval <= 0 { - configError("--sync-interval must be greater than 0") + // If dual database mode (local + master), both URLs are required + // Otherwise, single postgresUrl is required + if localPostgresUrl != nil && masterPostgresUrl != nil { + // Dual database mode - both URLs provided + if syncInterval <= 0 { + configError("--sync-interval must be greater than 0") + } + } else if postgresUrl == nil { + // Single database mode - postgresUrl required + configError("PostgreSQL corpus requires --postgres-url (or --local-postgres-url and --master-postgres-url for dual database mode)") } if executionHistorySize <= 0 { configError("--execution-history-size must be greater than 0") @@ -544,14 +551,14 @@ func makeFuzzer(with configuration: Configuration) -> Fuzzer { corpus = MarkovCorpus(covEvaluator: evaluator as ProgramCoverageEvaluator, dropoutRate: markovDropoutRate) case "postgresql": // Create PostgreSQL corpus with database connection - guard let postgresUrl = postgresUrl else { - logger.fatal("PostgreSQL URL is required for PostgreSQL corpus") - } - - // Use the existing fuzzilli_master database for all instances + // Support dual database mode (local + master) or single database mode let fuzzerInstanceId: String - if resume { + // Check for explicit fuzzer instance name from environment or CLI args + if let explicitName = args["--fuzzer-instance-name"] ?? ProcessInfo.processInfo.environment["FUZZER_INSTANCE_NAME"], !explicitName.isEmpty { + fuzzerInstanceId = explicitName + logger.info("Using explicit fuzzer instance ID: \(fuzzerInstanceId)") + } else if resume { // Use fixed fuzzer instance ID for resume fuzzerInstanceId = "fuzzer-main" } else { @@ -560,23 +567,39 @@ func makeFuzzer(with configuration: Configuration) -> Fuzzer { fuzzerInstanceId = "fuzzer-\(randomHash)" } - // Use the original postgres URL without modification - let modifiedPostgresUrl = postgresUrl + let localPool: DatabasePool + let masterPool: DatabasePool? - let databasePool = DatabasePool(connectionString: modifiedPostgresUrl, enableLogging: postgresLogging) + if let localUrl = localPostgresUrl, let masterUrl = masterPostgresUrl { + // Dual database mode: local for fast operations, master for sync + localPool = DatabasePool(connectionString: localUrl, enableLogging: postgresLogging) + masterPool = DatabasePool(connectionString: masterUrl, enableLogging: postgresLogging) + logger.info("Dual database mode enabled") + logger.info("Local PostgreSQL URL: \(localUrl)") + logger.info("Master PostgreSQL URL: \(masterUrl)") + } else if let url = postgresUrl { + // Single database mode: use provided URL for both + localPool = DatabasePool(connectionString: url, enableLogging: postgresLogging) + masterPool = nil + logger.info("Single database mode") + logger.info("PostgreSQL URL: \(url)") + } else { + logger.fatal("PostgreSQL URL is required for PostgreSQL corpus") + } corpus = PostgreSQLCorpus( minSize: minCorpusSize, maxSize: maxCorpusSize, minMutationsPerSample: minMutationsPerSample, - databasePool: databasePool, + databasePool: localPool, fuzzerInstanceId: fuzzerInstanceId, + syncInterval: TimeInterval(syncInterval), resume: resume, - enableLogging: postgresLogging + enableLogging: postgresLogging, + masterDatabasePool: masterPool ) logger.info("Created PostgreSQL corpus with instance ID: \(fuzzerInstanceId)") - logger.info("PostgreSQL URL: \(modifiedPostgresUrl)") logger.info("Resume mode: \(resume)") logger.info("Sync interval: \(syncInterval) seconds") logger.info("Validate before cache: \(validateBeforeCache)") diff --git a/docker-compose.distributed.yml b/docker-compose.distributed.yml deleted file mode 100644 index 3ba0bdc7f..000000000 --- a/docker-compose.distributed.yml +++ /dev/null @@ -1,76 +0,0 @@ -version: '3.8' - -services: - postgres-master: - image: postgres:15-alpine - container_name: fuzzilli-postgres-master - environment: - POSTGRES_DB: fuzzilli_master - POSTGRES_USER: fuzzilli - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-fuzzilli123} - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - ports: - - "5432:5432" - volumes: - - postgres_master_data:/var/lib/postgresql/data - - ./postgres-init.sql:/docker-entrypoint-initdb.d/init.sql - healthcheck: - test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_master"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - networks: - - fuzzing-network - - fuzzer: - build: - context: . - dockerfile: Cloud/VRIG/Dockerfile.distributed - # Remove hardcoded container name to allow scaling - environment: - - MASTER_POSTGRES_URL=postgresql://fuzzilli:${POSTGRES_PASSWORD:-fuzzilli123}@postgres-master:5432/fuzzilli_master - - FUZZER_INSTANCE_NAME= - - SYNC_INTERVAL=${SYNC_INTERVAL:-300} - - TIMEOUT=${TIMEOUT:-2500} - - MIN_MUTATIONS_PER_SAMPLE=${MIN_MUTATIONS_PER_SAMPLE:-25} - - DEBUG_LOGGING=${DEBUG_LOGGING:-false} - depends_on: - postgres-master: - condition: service_healthy - volumes: - - fuzzer_data:/home/app/Corpus - # Mount your existing V8 build directory - - /home/tropic/vrig/v8/v8/out/fuzzbuild:/home/app/fuzzbuild:ro - restart: unless-stopped - networks: - - fuzzing-network - deploy: - resources: - limits: - memory: 2G - reservations: - memory: 1G - - # Optional: pgAdmin for database management - pgadmin: - image: dpage/pgadmin4:latest - container_name: fuzzilli-pgadmin-distributed - environment: - PGADMIN_DEFAULT_EMAIL: admin@example.com - PGADMIN_DEFAULT_PASSWORD: admin123 - ports: - - "8080:80" - depends_on: - - postgres-master - restart: unless-stopped - networks: - - fuzzing-network - -volumes: - postgres_master_data: - fuzzer_data: - -networks: - fuzzing-network: - driver: bridge diff --git a/docker-compose.master.yml b/docker-compose.master.yml new file mode 100644 index 000000000..c0f3c714f --- /dev/null +++ b/docker-compose.master.yml @@ -0,0 +1,32 @@ +version: '3.8' + +services: + postgres-master: + image: postgres:15-alpine + container_name: fuzzilli-postgres-master + environment: + POSTGRES_DB: fuzzilli_master + POSTGRES_USER: fuzzilli + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-fuzzilli123} + POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + ports: + - "5432:5432" + volumes: + - postgres_master_data:/var/lib/postgresql/data + - ./postgres-init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_master"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - fuzzing-network + +volumes: + postgres_master_data: + +networks: + fuzzing-network: + driver: bridge + diff --git a/docker-compose.workers.yml b/docker-compose.workers.yml index a8fa5fb62..360cad3ad 100644 --- a/docker-compose.workers.yml +++ b/docker-compose.workers.yml @@ -32,6 +32,7 @@ services: environment: - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-1:5432/fuzzilli_local + - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-1:5432/fuzzilli_local - FUZZER_INSTANCE_NAME=fuzzer-1 - SYNC_INTERVAL=300 - TIMEOUT=2500 @@ -46,6 +47,12 @@ services: - fuzzer_data_1:/home/app/Corpus - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pgrep -f FuzzilliCli || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s networks: - fuzzing-network deploy: @@ -86,6 +93,7 @@ services: environment: - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-2:5432/fuzzilli_local + - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-2:5432/fuzzilli_local - FUZZER_INSTANCE_NAME=fuzzer-2 - SYNC_INTERVAL=300 - TIMEOUT=2500 @@ -100,6 +108,12 @@ services: - fuzzer_data_2:/home/app/Corpus - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pgrep -f FuzzilliCli || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s networks: - fuzzing-network deploy: diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 2d5aec1a4..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,38 +0,0 @@ -version: '3.8' - -services: - postgres: - image: postgres:15-alpine - container_name: fuzzilli-postgres - environment: - POSTGRES_DB: fuzzilli - POSTGRES_USER: fuzzilli - POSTGRES_PASSWORD: fuzzilli123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - ports: - - "5433:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - - ./postgres-init.sql:/docker-entrypoint-initdb.d/init.sql - healthcheck: - test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - - # Optional: pgAdmin for database management - pgadmin: - image: dpage/pgadmin4:latest - container_name: fuzzilli-pgadmin - environment: - PGADMIN_DEFAULT_EMAIL: admin@fuzzilli.local - PGADMIN_DEFAULT_PASSWORD: admin123 - ports: - - "8080:80" - depends_on: - - postgres - restart: unless-stopped - -volumes: - postgres_data: From a8aef7ce9bd1a852b7256b7e8732fa528fcab3ed Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Fri, 7 Nov 2025 15:52:07 -0500 Subject: [PATCH 19/23] Update sync interval to 60 seconds in docker-compose and scripts; add filtering for FUZZILLI_CRASH test cases in PostgreSQLCorpus and DatabaseUtils. --- Scripts/cleanup-docker.sh | 45 ++++++ Scripts/start-distributed.sh | 4 +- .../Fuzzilli/Corpus/PostgreSQLCorpus.swift | 140 ++++++++++-------- Sources/Fuzzilli/Database/DatabaseUtils.swift | 24 +++ .../Fuzzilli/Database/PostgreSQLStorage.swift | 60 ++++++-- docker-compose.workers.yml | 4 +- 6 files changed, 196 insertions(+), 81 deletions(-) create mode 100755 Scripts/cleanup-docker.sh diff --git a/Scripts/cleanup-docker.sh b/Scripts/cleanup-docker.sh new file mode 100755 index 000000000..88c927cdb --- /dev/null +++ b/Scripts/cleanup-docker.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Script to completely clean up all Docker resources for the distributed Fuzzilli setup +# Usage: ./Scripts/cleanup-docker.sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +cd "$PROJECT_DIR" + +MASTER_COMPOSE="docker-compose.master.yml" +WORKERS_COMPOSE="docker-compose.workers.yml" + +echo "==========================================" +echo " Cleaning up Docker resources" +echo "==========================================" +echo "" + +# Stop and remove containers, networks, and volumes +echo "Stopping and removing containers..." +docker compose -f "$MASTER_COMPOSE" -f "$WORKERS_COMPOSE" down --volumes --remove-orphans 2>/dev/null || true + +# Remove any remaining volumes +echo "Removing volumes..." +docker volume ls -q | grep -E "(fuzzillai_|postgres_|fuzzer_)" | xargs -r docker volume rm 2>/dev/null || true + +# Remove any orphaned containers +echo "Removing orphaned containers..." +docker ps -a --filter "name=fuzzilli\|fuzzer-worker\|postgres-local\|postgres-master" --format "{{.ID}}" | xargs -r docker rm -f 2>/dev/null || true + +# Remove any orphaned networks +echo "Removing orphaned networks..." +docker network ls --filter "name=fuzzing" --format "{{.ID}}" | xargs -r docker network rm 2>/dev/null || true + +# Clean up any dangling images +echo "Removing dangling images..." +docker image prune -f 2>/dev/null || true + +echo "" +echo "==========================================" +echo " Cleanup complete!" +echo "==========================================" + diff --git a/Scripts/start-distributed.sh b/Scripts/start-distributed.sh index a34d0bc41..c15c21e4d 100755 --- a/Scripts/start-distributed.sh +++ b/Scripts/start-distributed.sh @@ -12,7 +12,7 @@ # Environment variables: # - V8_BUILD_PATH: Path to V8 build directory on host (default: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild) # - POSTGRES_PASSWORD: PostgreSQL password (default: fuzzilli123) -# - SYNC_INTERVAL: Sync interval in seconds (default: 300) +# - SYNC_INTERVAL: Sync interval in seconds (default: 60) # - TIMEOUT: Execution timeout in ms (default: 2500) # - MIN_MUTATIONS_PER_SAMPLE: Minimum mutations per sample (default: 25) # - DEBUG_LOGGING: Enable debug logging (default: false) @@ -64,7 +64,7 @@ fi # Set defaults POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-fuzzilli123} V8_BUILD_PATH=${V8_BUILD_PATH:-/home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild} -SYNC_INTERVAL=${SYNC_INTERVAL:-300} +SYNC_INTERVAL=${SYNC_INTERVAL:-60} TIMEOUT=${TIMEOUT:-2500} MIN_MUTATIONS_PER_SAMPLE=${MIN_MUTATIONS_PER_SAMPLE:-25} DEBUG_LOGGING=${DEBUG_LOGGING:-false} diff --git a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift index 7b820325f..2a99c2331 100644 --- a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift +++ b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift @@ -523,6 +523,14 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { public func addInternal(_ program: Program, aspects: ProgramAspects? = nil) { guard program.size > 0 else { return } + // Filter out test programs with FUZZILLI_CRASH (false positive crashes) + if DatabaseUtils.containsFuzzilliCrash(program: program) { + if enableLogging { + logger.info("Skipping program with FUZZILLI_CRASH (test case)") + } + return + } + let programHash = DatabaseUtils.calculateProgramHash(program: program) cacheLock.lock() @@ -653,8 +661,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { let since = Date().addingTimeInterval(-24 * 60 * 60) // 24 hours ago let recentPrograms = try await storage.getRecentPrograms( fuzzerId: fuzzerId, - since: since, - limit: maxSize + since: since ) if enableLogging { @@ -710,69 +717,73 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { /// Perform actual database synchronization private func performDatabaseSync() async { - let hashesToSync: Set - - // Use async-safe lock for getting pending operations - hashesToSync = withLock(syncLock) { - let hashes = Set(pendingSyncOperations) - pendingSyncOperations.removeAll() - return hashes + // Sync ALL programs from local database to master, not just from cache + // This ensures the master has the complete corpus from this worker + guard let masterStorage = masterStorage, let masterId = masterFuzzerId, let localId = fuzzerId else { + return } - guard !hashesToSync.isEmpty else { return } - - // Syncing programs with PostgreSQL silently - - // Get programs to sync from cache - let programsToSync = withLock(cacheLock) { - hashesToSync.compactMap { hash -> (Program, ExecutionMetadata)? in - guard let (program, metadata) = programCache[hash] else { return nil } - return (program, metadata) + do { + // Get all programs from local database (not just cache) + // Use a date far in the past to get all programs + let allTimeDate = Date(timeIntervalSince1970: 0) + let localPrograms = try await storage.getRecentPrograms( + fuzzerId: localId, + since: allTimeDate, + limit: nil // No limit - get all programs + ) + + guard !localPrograms.isEmpty else { + if enableLogging { + logger.info("No programs in local database to sync") + } + return } - } - - // Store each program in the database - for (program, metadata) in programsToSync { - do { - // Use the registered fuzzer ID - guard let fuzzerId = fuzzerId else { - logger.error("Cannot sync program: fuzzer not registered") - return + + if enableLogging { + logger.info("Syncing \(localPrograms.count) programs from local database to master") + } + + // Store each program in the master database (filter out FUZZILLI_CRASH test cases) + var syncedCount = 0 + var skippedCount = 0 + for (program, metadata) in localPrograms { + // Skip test programs with FUZZILLI_CRASH + if DatabaseUtils.containsFuzzilliCrash(program: program) { + skippedCount += 1 + continue } - // Store the program with metadata to master (if available) for sync - // Use master fuzzer ID for master sync, local fuzzer ID for local storage - if let masterStorage = masterStorage, let masterId = masterFuzzerId { + do { _ = try await masterStorage.storeProgram( program: program, fuzzerId: masterId, metadata: metadata ) - } else { - // Fallback to local storage if master not available - _ = try await storage.storeProgram( - program: program, - fuzzerId: fuzzerId, - metadata: metadata - ) - } - - // Program synced to database silently - - } catch { - if enableLogging { - logger.error("Failed to sync program to database: \(String(reflecting: error))") - } else { - logger.error("Failed to sync program to database: \(error)") - } - // Re-add to pending sync for retry - _ = withLock(syncLock) { - pendingSyncOperations.insert(DatabaseUtils.calculateProgramHash(program: program)) + syncedCount += 1 + } catch { + if enableLogging { + logger.error("Failed to sync program to master: \(error)") + } } } + + if skippedCount > 0 && enableLogging { + logger.info("Skipped \(skippedCount) test programs with FUZZILLI_CRASH during sync") + } + + if enableLogging { + logger.info("Successfully synced \(syncedCount)/\(localPrograms.count) programs to master") + } + + // Clear pending sync operations since we've synced everything + _ = withLock(syncLock) { + pendingSyncOperations.removeAll() + } + + } catch { + logger.error("Failed to sync programs to master: \(error)") } - - // Database sync completed silently } /// Pull sync from master database @@ -790,13 +801,11 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } do { - // Get recent programs from master (last 24 hours, limit 100) - // Use master fuzzer ID to query master database - let sinceDate = Date().addingTimeInterval(-24 * 60 * 60) // 24 hours ago - let masterPrograms = try await masterStorage.getRecentPrograms( - fuzzerId: masterId, - since: sinceDate, - limit: 100 + // Get ALL programs from master database (from all fuzzers) + // Use a date far in the past to get all programs + let sinceDate = Date(timeIntervalSince1970: 0) // Get all programs from the beginning + let masterPrograms = try await masterStorage.getAllPrograms( + since: sinceDate ) guard !masterPrograms.isEmpty else { @@ -827,8 +836,16 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { logger.info("Pulling \(newPrograms.count) new programs from master") } - // Add new programs to local cache and local postgres + // Add new programs to local cache and local postgres (filter out FUZZILLI_CRASH test cases) + var pulledCount = 0 + var skippedCount = 0 for (program, metadata) in newPrograms { + // Skip test programs with FUZZILLI_CRASH + if DatabaseUtils.containsFuzzilliCrash(program: program) { + skippedCount += 1 + continue + } + let hash = DatabaseUtils.calculateProgramHash(program: program) // Add to local cache @@ -852,6 +869,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { totalEntryCounter += 1 } + pulledCount += 1 if enableLogging { logger.info("Pulled program from master: hash=\(hash.prefix(8))...") } @@ -861,7 +879,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } if enableLogging { - logger.info("Successfully pulled \(newPrograms.count) programs from master") + logger.info("Successfully pulled \(pulledCount) programs from master (skipped \(skippedCount) test programs with FUZZILLI_CRASH)") } } catch { diff --git a/Sources/Fuzzilli/Database/DatabaseUtils.swift b/Sources/Fuzzilli/Database/DatabaseUtils.swift index 3f576d40c..d071adf9c 100644 --- a/Sources/Fuzzilli/Database/DatabaseUtils.swift +++ b/Sources/Fuzzilli/Database/DatabaseUtils.swift @@ -94,6 +94,30 @@ public class DatabaseUtils { return try JSONDecoder().decode(ExecutionMetadata.self, from: data) } + // MARK: - Program Filtering + + /// Check if a program contains FUZZILLI_CRASH test calls (false positive crashes) + public static func containsFuzzilliCrash(program: Program) -> Bool { + // Lift program to JavaScript and check for FUZZILLI_CRASH pattern + let jsLifter = JavaScriptLifter(prefix: "", suffix: "", ecmaVersion: .es6) + let jsCode = jsLifter.lift(program, withOptions: []) + + // Check for patterns like fuzzilli('FUZZILLI_CRASH', ...) or fuzzilli("FUZZILLI_CRASH", ...) + let patterns = [ + "fuzzilli('FUZZILLI_CRASH'", + "fuzzilli(\"FUZZILLI_CRASH\"", + "fuzzilli(`FUZZILLI_CRASH`" + ] + + for pattern in patterns { + if jsCode.contains(pattern) { + return true + } + } + + return false + } + // MARK: - Execution Outcome Mapping /// Map ExecutionOutcome to database ID diff --git a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift index 73ee37918..67309e960 100644 --- a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift +++ b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift @@ -734,7 +734,12 @@ public class PostgreSQLStorage { // MARK: - Query Operations /// Get recent programs with metadata for a fuzzer - public func getRecentPrograms(fuzzerId: Int, since: Date, limit: Int = 100) async throws -> [(Program, ExecutionMetadata)] { + /// Get all programs from master database (from all fuzzers) + public func getAllPrograms(since: Date) async throws -> [(Program, ExecutionMetadata)] { + return try await getRecentPrograms(fuzzerId: nil, since: since, limit: nil) + } + + public func getRecentPrograms(fuzzerId: Int?, since: Date, limit: Int? = nil) async throws -> [(Program, ExecutionMetadata)] { if enableLogging { logger.info("Getting recent programs: fuzzerId=\(fuzzerId), since=\(since), limit=\(limit)") } @@ -745,7 +750,7 @@ public class PostgreSQLStorage { // Query for recent programs with their latest execution metadata let queryString = """ - SELECT + SELECT DISTINCT ON (p.program_hash) p.program_hash, p.program_size, p.program_base64, @@ -759,10 +764,10 @@ public class PostgreSQLStorage { FROM program p LEFT JOIN execution e ON p.program_hash = e.program_hash LEFT JOIN execution_outcome eo ON e.execution_outcome_id = eo.id - WHERE p.fuzzer_id = \(fuzzerId) - AND p.created_at >= '\(since.ISO8601Format())' - ORDER BY p.created_at DESC - LIMIT \(limit) + WHERE \(fuzzerId != nil ? "p.fuzzer_id = \(fuzzerId!) AND " : "") + p.created_at >= '\(since.ISO8601Format())' + ORDER BY p.program_hash, p.created_at DESC, e.created_at DESC NULLS LAST + \(limit != nil ? "LIMIT \(limit!)" : "") """ let query = PostgresQuery(stringLiteral: queryString) @@ -772,16 +777,39 @@ public class PostgreSQLStorage { var programs: [(Program, ExecutionMetadata)] = [] for row in rows { - let programHash = try row.decode(String.self, context: PostgresDecodingContext.default) - _ = try row.decode(Int.self, context: PostgresDecodingContext.default) // programSize - let programBase64 = try row.decode(String.self, context: PostgresDecodingContext.default) - _ = try row.decode(Date.self, context: PostgresDecodingContext.default) // createdAt - let outcome = try row.decode(String?.self, context: PostgresDecodingContext.default) - let description = try row.decode(String?.self, context: PostgresDecodingContext.default) - _ = try row.decode(Int?.self, context: PostgresDecodingContext.default) // executionTimeMs - let coverageTotal = try row.decode(Double?.self, context: PostgresDecodingContext.default) - _ = try row.decode(Int?.self, context: PostgresDecodingContext.default) // signalCode - _ = try row.decode(Int?.self, context: PostgresDecodingContext.default) // exitCode + // Decode with error handling for each field + let programHash: String + let programSize: Int + let programBase64: String + let createdAt: Date + let outcome: String? + let description: String? + let executionTimeMs: Int? + let coverageTotal: Double? + let signalCode: Int? + let exitCode: Int? + + do { + // Decode required fields (these should never be NULL) + programHash = try row.decode(String.self, context: PostgresDecodingContext.default) + programSize = try row.decode(Int.self, context: PostgresDecodingContext.default) + programBase64 = try row.decode(String.self, context: PostgresDecodingContext.default) + createdAt = try row.decode(Date.self, context: PostgresDecodingContext.default) + + // Decode optional fields (these can be NULL from LEFT JOIN) + // PostgresNIO handles NULL values when decoding to Optional types + outcome = try row.decode(String?.self, context: PostgresDecodingContext.default) + description = try row.decode(String?.self, context: PostgresDecodingContext.default) + executionTimeMs = try row.decode(Int?.self, context: PostgresDecodingContext.default) + coverageTotal = try row.decode(Double?.self, context: PostgresDecodingContext.default) + signalCode = try row.decode(Int?.self, context: PostgresDecodingContext.default) + exitCode = try row.decode(Int?.self, context: PostgresDecodingContext.default) + } catch { + if enableLogging { + logger.warning("Failed to decode row: \(String(reflecting: error)). Skipping this program.") + } + continue + } // Decode the program from base64 guard let programData = Data(base64Encoded: programBase64) else { diff --git a/docker-compose.workers.yml b/docker-compose.workers.yml index 360cad3ad..1c0825a33 100644 --- a/docker-compose.workers.yml +++ b/docker-compose.workers.yml @@ -34,7 +34,7 @@ services: - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-1:5432/fuzzilli_local - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-1:5432/fuzzilli_local - FUZZER_INSTANCE_NAME=fuzzer-1 - - SYNC_INTERVAL=300 + - SYNC_INTERVAL=60 - TIMEOUT=2500 - MIN_MUTATIONS_PER_SAMPLE=25 - DEBUG_LOGGING=false @@ -95,7 +95,7 @@ services: - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-2:5432/fuzzilli_local - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-2:5432/fuzzilli_local - FUZZER_INSTANCE_NAME=fuzzer-2 - - SYNC_INTERVAL=300 + - SYNC_INTERVAL=60 - TIMEOUT=2500 - MIN_MUTATIONS_PER_SAMPLE=25 - DEBUG_LOGGING=false From f2eba9ea6997a8e31704665e771ab9c93acef243 Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Fri, 7 Nov 2025 16:43:50 -0500 Subject: [PATCH 20/23] Add multiple worker configurations in docker-compose; update sync interval to 300 seconds; enhance scripts to filter FUZZILLI_CRASH test cases and dynamically handle multiple postgres-local containers. --- Scripts/show-crash-javascript.sh | 80 ++++- Scripts/show-stats.sh | 67 ++-- .../Fuzzilli/Corpus/PostgreSQLCorpus.swift | 16 + Sources/Fuzzilli/Database/DatabaseUtils.swift | 18 +- docker-compose.workers.yml | 319 +++++++++++++++++- 5 files changed, 466 insertions(+), 34 deletions(-) diff --git a/Scripts/show-crash-javascript.sh b/Scripts/show-crash-javascript.sh index 280c86e04..748124c2e 100755 --- a/Scripts/show-crash-javascript.sh +++ b/Scripts/show-crash-javascript.sh @@ -2,6 +2,7 @@ # Script to show JavaScript code from crash programs # Usage: ./Scripts/show-crash-javascript.sh [worker_num] +# If no worker_num is specified, shows crashes for all postgres-local-* containers set -e @@ -14,20 +15,51 @@ cd "$PROJECT_DIR" CYAN='\033[0;36m' YELLOW='\033[1;33m' GREEN='\033[0;32m' +RED='\033[0;31m' NC='\033[0m' # No Color +# Get all postgres-local-* containers +get_local_postgres_containers() { + docker ps --format '{{.Names}}' | grep '^postgres-local-' | sort +} + +# Get worker number from container name +get_worker_num() { + local container=$1 + echo "$container" | sed 's/.*-\([0-9]*\)$/\1/' +} + +# Check if a container is running +check_container() { + local container=$1 + if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then + return 0 + else + return 1 + fi +} + show_crash_javascript() { local worker_num=$1 local container="postgres-local-${worker_num}" local database="fuzzilli_local" + if ! check_container "$container"; then + echo -e "${RED}Worker ${worker_num}: Container ${container} not running${NC}" + echo "" + return + fi + echo -e "${CYAN}========================================${NC}" echo -e "${CYAN} Worker ${worker_num} Crash Programs${NC}" + echo -e "${CYAN} (Excluding FUZZILLI_CRASH test cases - signal 3)${NC}" echo -e "${CYAN}========================================${NC}" echo "" # Get all crash program hashes (one per line) - local crash_hashes=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT DISTINCT e.program_hash FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.program_hash;" 2>/dev/null | grep -v '^$' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + # Exclude only signal_code = 3 (FUZZILLI_CRASH test cases) + # Show all other crashes including signal 11 and all other signals + local crash_hashes=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT DISTINCT e.program_hash FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' AND (e.signal_code IS NULL OR e.signal_code != 3) ORDER BY e.program_hash;" 2>/dev/null | grep -v '^$' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') if [ -z "$crash_hashes" ]; then echo -e "${YELLOW}No crashes found for Worker ${worker_num}${NC}" @@ -41,10 +73,28 @@ show_crash_javascript() { continue fi - echo -e "${GREEN}--- Crash Program: ${hash} ---${NC}" + # Get execution details first to check signal code + # Exclude only signal 3 (FUZZILLI_CRASH test cases), show all others including signal 11 + local exec_details=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT e.execution_id, e.signal_code, e.exit_code, eo.description, e.created_at FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE e.program_hash = '${hash}' AND eo.outcome = 'Crashed' AND (e.signal_code IS NULL OR e.signal_code != 3) ORDER BY e.created_at DESC LIMIT 1;" 2>/dev/null) - # Get execution details - local exec_details=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT e.execution_id, e.signal_code, e.exit_code, eo.description, e.created_at FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE e.program_hash = '${hash}' AND eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 1;" 2>/dev/null) + # Skip if no execution details found (shouldn't happen, but safety check) + if [ -z "$exec_details" ]; then + continue + fi + + # Get and decode the JavaScript to check for FUZZILLI_CRASH + local base64_program=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT program_base64 FROM program WHERE program_hash = '${hash}';" 2>/dev/null | tr -d ' \n\r') + + # Check if program contains FUZZILLI_CRASH pattern + if [ -n "$base64_program" ]; then + local decoded_program=$(echo "$base64_program" | base64 -d 2>/dev/null) + if echo "$decoded_program" | grep -q "FUZZILLI_CRASH"; then + # Skip this crash - it's a test case + continue + fi + fi + + echo -e "${GREEN}--- Crash Program: ${hash} ---${NC}" if [ -n "$exec_details" ]; then echo -e "${YELLOW}Execution Details:${NC}" @@ -54,17 +104,17 @@ show_crash_javascript() { # Get and decode the JavaScript echo -e "${YELLOW}JavaScript Code:${NC}" - local base64_program=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT program_base64 FROM program WHERE program_hash = '${hash}';" 2>/dev/null | tr -d ' \n\r') if [ -n "$base64_program" ]; then # Decode base64 and extract JavaScript strings - local javascript=$(echo "$base64_program" | base64 -d 2>/dev/null | strings | grep -E "(fuzzilli|function|var|let|const|if|for|while|return)" | head -10) + # Using awk to limit output without head/tail + local javascript=$(echo "$base64_program" | base64 -d 2>/dev/null | strings | grep -E "(fuzzilli|function|var|let|const|if|for|while|return)" | awk 'NR <= 10 { print; if (NR == 10) exit }') if [ -n "$javascript" ]; then echo "$javascript" | sed 's/^/ /' else - # Try to get any readable strings - local all_strings=$(echo "$base64_program" | base64 -d 2>/dev/null | strings | grep -v "^$" | tail -5) + # Try to get any readable strings without tail + local all_strings=$(echo "$base64_program" | base64 -d 2>/dev/null | strings | grep -v "^$" | awk '{ lines[NR] = $0 } END { start = (NR > 5) ? NR - 4 : 1; for (i = start; i <= NR; i++) print lines[i] }') if [ -n "$all_strings" ]; then echo "$all_strings" | sed 's/^/ /' else @@ -83,8 +133,16 @@ show_crash_javascript() { if [ -n "$1" ]; then show_crash_javascript "$1" else - # Show both workers - show_crash_javascript 1 - show_crash_javascript 2 + # Dynamically discover and show crashes for all postgres-local-* containers + local_postgres_containers=($(get_local_postgres_containers)) + if [ ${#local_postgres_containers[@]} -eq 0 ]; then + echo -e "${YELLOW}No postgres-local-* containers found${NC}" + echo "" + else + for postgres_container in "${local_postgres_containers[@]}"; do + worker_num=$(get_worker_num "$postgres_container") + show_crash_javascript "$worker_num" + done + fi fi diff --git a/Scripts/show-stats.sh b/Scripts/show-stats.sh index d67f35552..d8d2e5ccf 100755 --- a/Scripts/show-stats.sh +++ b/Scripts/show-stats.sh @@ -58,28 +58,46 @@ get_db_stats() { local execution_count=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT COUNT(*) FROM execution;" 2>/dev/null | tr -d ' ' || echo "0") local program_table_count=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT COUNT(*) FROM program;" 2>/dev/null | tr -d ' ' || echo "0") - # Crash count - local crash_count=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed';" 2>/dev/null | tr -d ' ' || echo "0") + # Crash count (excluding only FUZZILLI_CRASH test cases with signal 3) + # Show all other crashes including signal 11 and all other signals + local crash_count=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' AND (e.signal_code IS NULL OR e.signal_code != 3);" 2>/dev/null | tr -d ' ' || echo "0") echo -e "${YELLOW}Statistics:${NC}" echo " Programs (corpus): $program_count" echo " Programs (executed): $program_table_count" echo " Executions: $execution_count" - echo " Crashes: $crash_count" + echo " Crashes (excluding test cases - signal 3): $crash_count" # Recent activity (last 5 programs) echo -e "${YELLOW}Recent Programs (last 5):${NC}" docker exec "$container" psql -U fuzzilli -d "$database" -c "SELECT program_hash, program_size, created_at FROM fuzzer ORDER BY created_at DESC LIMIT 5;" 2>/dev/null || echo " No programs found" - # Crash details + # Crash details (excluding only FUZZILLI_CRASH test cases - signal 3) + # Show all other crashes including signal 11 and all other signals if [ "$crash_count" != "0" ] && [ "$crash_count" != "" ]; then - echo -e "${YELLOW}Crashes (last 3):${NC}" - docker exec "$container" psql -U fuzzilli -d "$database" -c "SELECT e.execution_id, e.program_hash, e.execution_time_ms, e.signal_code, e.exit_code, eo.description, e.created_at FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 3;" 2>/dev/null || echo " No crash details available" + echo -e "${YELLOW}Crashes (last 3, excluding test cases - signal 3):${NC}" + docker exec "$container" psql -U fuzzilli -d "$database" -c "SELECT e.execution_id, e.program_hash, e.execution_time_ms, e.signal_code, e.exit_code, eo.description, e.created_at FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' AND (e.signal_code IS NULL OR e.signal_code != 3) ORDER BY e.created_at DESC LIMIT 3;" 2>/dev/null || echo " No crash details available" fi echo "" } +# Get all postgres-local-* containers +get_local_postgres_containers() { + docker ps --format '{{.Names}}' | grep '^postgres-local-' | sort +} + +# Get all fuzzer-worker-* containers +get_worker_containers() { + docker ps --format '{{.Names}}' | grep '^fuzzer-worker-' | sort +} + +# Get worker number from container name +get_worker_num() { + local container=$1 + echo "$container" | sed 's/.*-\([0-9]*\)$/\1/' +} + # Get worker container stats get_worker_stats() { local worker_num=$1 @@ -87,7 +105,7 @@ get_worker_stats() { local postgres_container="postgres-local-${worker_num}" if ! check_container "$container"; then - echo -e "${RED}Worker ${worker_num}: Container not running${NC}" + echo -e "${RED}Worker ${worker_num}: Fuzzer container not running${NC}" echo "" return fi @@ -121,9 +139,17 @@ else echo "" fi -# Worker stats -get_worker_stats 1 -get_worker_stats 2 +# Worker stats - dynamically discover all workers +local_postgres_containers=($(get_local_postgres_containers)) +if [ ${#local_postgres_containers[@]} -eq 0 ]; then + echo -e "${YELLOW}No postgres-local-* containers found${NC}" + echo "" +else + for postgres_container in "${local_postgres_containers[@]}"; do + worker_num=$(get_worker_num "$postgres_container") + get_worker_stats "$worker_num" + done +fi # Summary echo -e "${CYAN}========================================${NC}" @@ -136,16 +162,17 @@ if check_container "fuzzilli-postgres-master"; then echo -e "Master: ${GREEN}${master_programs}${NC} programs, ${GREEN}${master_executions}${NC} executions" fi -if check_container "postgres-local-1"; then - w1_programs=$(docker exec postgres-local-1 psql -U fuzzilli -d fuzzilli_local -t -c "SELECT COUNT(*) FROM fuzzer;" 2>/dev/null | tr -d ' ' || echo "0") - w1_executions=$(docker exec postgres-local-1 psql -U fuzzilli -d fuzzilli_local -t -c "SELECT COUNT(*) FROM execution;" 2>/dev/null | tr -d ' ' || echo "0") - echo -e "Worker 1: ${GREEN}${w1_programs}${NC} programs, ${GREEN}${w1_executions}${NC} executions" -fi - -if check_container "postgres-local-2"; then - w2_programs=$(docker exec postgres-local-2 psql -U fuzzilli -d fuzzilli_local -t -c "SELECT COUNT(*) FROM fuzzer;" 2>/dev/null | tr -d ' ' || echo "0") - w2_executions=$(docker exec postgres-local-2 psql -U fuzzilli -d fuzzilli_local -t -c "SELECT COUNT(*) FROM execution;" 2>/dev/null | tr -d ' ' || echo "0") - echo -e "Worker 2: ${GREEN}${w2_programs}${NC} programs, ${GREEN}${w2_executions}${NC} executions" +# Dynamically get stats for all local postgres containers +local_postgres_containers=($(get_local_postgres_containers)) +if [ ${#local_postgres_containers[@]} -gt 0 ]; then + for postgres_container in "${local_postgres_containers[@]}"; do + worker_num=$(get_worker_num "$postgres_container") + if check_container "$postgres_container"; then + worker_programs=$(docker exec "$postgres_container" psql -U fuzzilli -d fuzzilli_local -t -c "SELECT COUNT(*) FROM fuzzer;" 2>/dev/null | tr -d ' ' || echo "0") + worker_executions=$(docker exec "$postgres_container" psql -U fuzzilli -d fuzzilli_local -t -c "SELECT COUNT(*) FROM execution;" 2>/dev/null | tr -d ' ' || echo "0") + echo -e "Worker ${worker_num}: ${GREEN}${worker_programs}${NC} programs, ${GREEN}${worker_executions}${NC} executions" + fi + done fi echo "" diff --git a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift index 2a99c2331..667fdd66c 100644 --- a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift +++ b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift @@ -417,6 +417,14 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { var executionBatchData: [ExecutionBatchData] = [] for (program, aspects, executionType) in batch { + // Filter out test programs with FUZZILLI_CRASH (false positive crashes) + if DatabaseUtils.containsFuzzilliCrash(program: program) { + if enableLogging { + logger.info("Skipping execution with FUZZILLI_CRASH (test case) in batch processing") + } + continue + } + let programHash = DatabaseUtils.calculateProgramHash(program: program) // Only store unique programs @@ -936,6 +944,14 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { /// Store execution with cached data to avoid REPRL context issues private func storeExecutionWithCachedData(_ program: Program, _ executionData: ExecutionData, _ executionType: DatabaseExecutionPurpose, _ aspects: ProgramAspects) async { + // Filter out test programs with FUZZILLI_CRASH (false positive crashes) + if DatabaseUtils.containsFuzzilliCrash(program: program) { + if enableLogging { + logger.info("Skipping execution storage for program with FUZZILLI_CRASH (test case)") + } + return + } + do { // Use the registered fuzzer ID guard let fuzzerId = fuzzerId else { diff --git a/Sources/Fuzzilli/Database/DatabaseUtils.swift b/Sources/Fuzzilli/Database/DatabaseUtils.swift index d071adf9c..530cf28c1 100644 --- a/Sources/Fuzzilli/Database/DatabaseUtils.swift +++ b/Sources/Fuzzilli/Database/DatabaseUtils.swift @@ -103,10 +103,14 @@ public class DatabaseUtils { let jsCode = jsLifter.lift(program, withOptions: []) // Check for patterns like fuzzilli('FUZZILLI_CRASH', ...) or fuzzilli("FUZZILLI_CRASH", ...) + // Specifically check for fuzzilli('FUZZILLI_CRASH', 3) which is a test case let patterns = [ "fuzzilli('FUZZILLI_CRASH'", "fuzzilli(\"FUZZILLI_CRASH\"", - "fuzzilli(`FUZZILLI_CRASH`" + "fuzzilli(`FUZZILLI_CRASH`", + "fuzzilli('FUZZILLI_CRASH', 3)", + "fuzzilli(\"FUZZILLI_CRASH\", 3)", + "fuzzilli(`FUZZILLI_CRASH`, 3)" ] for pattern in patterns { @@ -115,6 +119,18 @@ public class DatabaseUtils { } } + // Also check for the pattern with any whitespace variations + let regexPatterns = [ + "fuzzilli\\s*\\(\\s*['\"`]FUZZILLI_CRASH['\"`]\\s*,\\s*3\\s*\\)", + "fuzzilli\\s*\\(\\s*['\"`]FUZZILLI_CRASH['\"`]" + ] + + for pattern in regexPatterns { + if jsCode.range(of: pattern, options: .regularExpression) != nil { + return true + } + } + return false } diff --git a/docker-compose.workers.yml b/docker-compose.workers.yml index 1c0825a33..d96220385 100644 --- a/docker-compose.workers.yml +++ b/docker-compose.workers.yml @@ -34,7 +34,7 @@ services: - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-1:5432/fuzzilli_local - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-1:5432/fuzzilli_local - FUZZER_INSTANCE_NAME=fuzzer-1 - - SYNC_INTERVAL=60 + - SYNC_INTERVAL=300 - TIMEOUT=2500 - MIN_MUTATIONS_PER_SAMPLE=25 - DEBUG_LOGGING=false @@ -95,7 +95,7 @@ services: - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-2:5432/fuzzilli_local - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-2:5432/fuzzilli_local - FUZZER_INSTANCE_NAME=fuzzer-2 - - SYNC_INTERVAL=60 + - SYNC_INTERVAL=300 - TIMEOUT=2500 - MIN_MUTATIONS_PER_SAMPLE=25 - DEBUG_LOGGING=false @@ -124,8 +124,323 @@ services: memory: 1G + # Worker 3 - Local Postgres + postgres-local-3: + image: postgres:15-alpine + container_name: postgres-local-3 + environment: + POSTGRES_DB: fuzzilli_local + POSTGRES_USER: fuzzilli + POSTGRES_PASSWORD: fuzzilli123 + POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + volumes: + - postgres_local_data_3:/var/lib/postgresql/data + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - fuzzing-network + + # Worker 3 - Fuzzilli Container + fuzzer-worker-3: + build: + context: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai + dockerfile: Cloud/VRIG/Dockerfile.distributed + container_name: fuzzer-worker-3 + environment: + - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master + - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-3:5432/fuzzilli_local + - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-3:5432/fuzzilli_local + - FUZZER_INSTANCE_NAME=fuzzer-3 + - SYNC_INTERVAL=300 + - TIMEOUT=2500 + - MIN_MUTATIONS_PER_SAMPLE=25 + - DEBUG_LOGGING=false + depends_on: + postgres-master: + condition: service_healthy + postgres-local-3: + condition: service_healthy + volumes: + - fuzzer_data_3:/home/app/Corpus + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pgrep -f FuzzilliCli || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + networks: + - fuzzing-network + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + + + # Worker 4 - Local Postgres + postgres-local-4: + image: postgres:15-alpine + container_name: postgres-local-4 + environment: + POSTGRES_DB: fuzzilli_local + POSTGRES_USER: fuzzilli + POSTGRES_PASSWORD: fuzzilli123 + POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + volumes: + - postgres_local_data_4:/var/lib/postgresql/data + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - fuzzing-network + + # Worker 4 - Fuzzilli Container + fuzzer-worker-4: + build: + context: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai + dockerfile: Cloud/VRIG/Dockerfile.distributed + container_name: fuzzer-worker-4 + environment: + - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master + - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-4:5432/fuzzilli_local + - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-4:5432/fuzzilli_local + - FUZZER_INSTANCE_NAME=fuzzer-4 + - SYNC_INTERVAL=300 + - TIMEOUT=2500 + - MIN_MUTATIONS_PER_SAMPLE=25 + - DEBUG_LOGGING=false + depends_on: + postgres-master: + condition: service_healthy + postgres-local-4: + condition: service_healthy + volumes: + - fuzzer_data_4:/home/app/Corpus + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pgrep -f FuzzilliCli || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + networks: + - fuzzing-network + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + + + # Worker 5 - Local Postgres + postgres-local-5: + image: postgres:15-alpine + container_name: postgres-local-5 + environment: + POSTGRES_DB: fuzzilli_local + POSTGRES_USER: fuzzilli + POSTGRES_PASSWORD: fuzzilli123 + POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + volumes: + - postgres_local_data_5:/var/lib/postgresql/data + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - fuzzing-network + + # Worker 5 - Fuzzilli Container + fuzzer-worker-5: + build: + context: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai + dockerfile: Cloud/VRIG/Dockerfile.distributed + container_name: fuzzer-worker-5 + environment: + - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master + - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-5:5432/fuzzilli_local + - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-5:5432/fuzzilli_local + - FUZZER_INSTANCE_NAME=fuzzer-5 + - SYNC_INTERVAL=300 + - TIMEOUT=2500 + - MIN_MUTATIONS_PER_SAMPLE=25 + - DEBUG_LOGGING=false + depends_on: + postgres-master: + condition: service_healthy + postgres-local-5: + condition: service_healthy + volumes: + - fuzzer_data_5:/home/app/Corpus + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pgrep -f FuzzilliCli || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + networks: + - fuzzing-network + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + + + # Worker 6 - Local Postgres + postgres-local-6: + image: postgres:15-alpine + container_name: postgres-local-6 + environment: + POSTGRES_DB: fuzzilli_local + POSTGRES_USER: fuzzilli + POSTGRES_PASSWORD: fuzzilli123 + POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + volumes: + - postgres_local_data_6:/var/lib/postgresql/data + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - fuzzing-network + + # Worker 6 - Fuzzilli Container + fuzzer-worker-6: + build: + context: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai + dockerfile: Cloud/VRIG/Dockerfile.distributed + container_name: fuzzer-worker-6 + environment: + - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master + - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-6:5432/fuzzilli_local + - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-6:5432/fuzzilli_local + - FUZZER_INSTANCE_NAME=fuzzer-6 + - SYNC_INTERVAL=300 + - TIMEOUT=2500 + - MIN_MUTATIONS_PER_SAMPLE=25 + - DEBUG_LOGGING=false + depends_on: + postgres-master: + condition: service_healthy + postgres-local-6: + condition: service_healthy + volumes: + - fuzzer_data_6:/home/app/Corpus + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pgrep -f FuzzilliCli || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + networks: + - fuzzing-network + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + + + # Worker 7 - Local Postgres + postgres-local-7: + image: postgres:15-alpine + container_name: postgres-local-7 + environment: + POSTGRES_DB: fuzzilli_local + POSTGRES_USER: fuzzilli + POSTGRES_PASSWORD: fuzzilli123 + POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + volumes: + - postgres_local_data_7:/var/lib/postgresql/data + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - fuzzing-network + + # Worker 7 - Fuzzilli Container + fuzzer-worker-7: + build: + context: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai + dockerfile: Cloud/VRIG/Dockerfile.distributed + container_name: fuzzer-worker-7 + environment: + - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master + - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-7:5432/fuzzilli_local + - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-7:5432/fuzzilli_local + - FUZZER_INSTANCE_NAME=fuzzer-7 + - SYNC_INTERVAL=300 + - TIMEOUT=2500 + - MIN_MUTATIONS_PER_SAMPLE=25 + - DEBUG_LOGGING=false + depends_on: + postgres-master: + condition: service_healthy + postgres-local-7: + condition: service_healthy + volumes: + - fuzzer_data_7:/home/app/Corpus + - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pgrep -f FuzzilliCli || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + networks: + - fuzzing-network + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + + volumes: postgres_local_data_1: fuzzer_data_1: postgres_local_data_2: fuzzer_data_2: + postgres_local_data_3: + fuzzer_data_3: + postgres_local_data_4: + fuzzer_data_4: + postgres_local_data_5: + fuzzer_data_5: + postgres_local_data_6: + fuzzer_data_6: + postgres_local_data_7: + fuzzer_data_7: From 6c66a73b54540ed445f3512a7add7777ef583664 Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Fri, 7 Nov 2025 16:44:43 -0500 Subject: [PATCH 21/23] got rid of postgress tests --- Tests/FuzzilliTests/DatabaseModelsTests.swift | 203 ---------------- .../DatabasePoolSimpleTests.swift | 85 ------- Tests/FuzzilliTests/DatabaseSchemaTests.swift | 151 ------------ Tests/FuzzilliTests/DatabaseUtilsTests.swift | 185 -------------- .../PostgreSQLCorpusIntegrationTests.swift | 143 ----------- .../FuzzilliTests/PostgreSQLCorpusTests.swift | 204 ---------------- .../PostgreSQLIntegrationTests.swift | 153 ------------ .../PostgreSQLStorageTests.swift | 228 ------------------ 8 files changed, 1352 deletions(-) delete mode 100644 Tests/FuzzilliTests/DatabaseModelsTests.swift delete mode 100644 Tests/FuzzilliTests/DatabasePoolSimpleTests.swift delete mode 100644 Tests/FuzzilliTests/DatabaseSchemaTests.swift delete mode 100644 Tests/FuzzilliTests/DatabaseUtilsTests.swift delete mode 100644 Tests/FuzzilliTests/PostgreSQLCorpusIntegrationTests.swift delete mode 100644 Tests/FuzzilliTests/PostgreSQLCorpusTests.swift delete mode 100644 Tests/FuzzilliTests/PostgreSQLIntegrationTests.swift delete mode 100644 Tests/FuzzilliTests/PostgreSQLStorageTests.swift diff --git a/Tests/FuzzilliTests/DatabaseModelsTests.swift b/Tests/FuzzilliTests/DatabaseModelsTests.swift deleted file mode 100644 index 78f49d466..000000000 --- a/Tests/FuzzilliTests/DatabaseModelsTests.swift +++ /dev/null @@ -1,203 +0,0 @@ -import XCTest -import Foundation -@testable import Fuzzilli - -final class DatabaseModelsTests: XCTestCase { - - func testExecutionMetadataCreation() { - let outcome = DatabaseExecutionOutcome(id: 1, outcome: "Succeeded", description: "Program executed successfully") - let metadata = ExecutionMetadata(lastOutcome: outcome) - - XCTAssertEqual(metadata.executionCount, 0) - XCTAssertEqual(metadata.lastCoverage, 0.0) - XCTAssertEqual(metadata.lastOutcome.outcome, "Succeeded") - XCTAssertTrue(metadata.recentExecutions.isEmpty) - XCTAssertNil(metadata.feedbackVector) - XCTAssertTrue(metadata.coverageEdges.isEmpty) - } - - func testExecutionMetadataAddExecution() { - let outcome = DatabaseExecutionOutcome(id: 1, outcome: "Succeeded", description: "Program executed successfully") - var metadata = ExecutionMetadata(lastOutcome: outcome) - - let execution = ExecutionRecord( - executionId: 1, - programBase64: "test_program", - executionTypeId: 1, - mutatorTypeId: 1, - executionOutcomeId: 1, - feedbackVector: nil, - turboshaftIr: nil, - coverageTotal: 85.5, - executionTimeMs: 100, - signalCode: nil, - exitCode: nil, - stdout: nil, - stderr: nil, - fuzzout: nil, - turbofanOptimizationBits: nil, - feedbackNexusCount: nil, - executionFlags: nil, - engineArguments: nil, - createdAt: Date() - ) - - metadata.addExecution(execution) - - XCTAssertEqual(metadata.executionCount, 1) - XCTAssertEqual(metadata.lastCoverage, 85.5) - XCTAssertEqual(metadata.recentExecutions.count, 1) - XCTAssertEqual(metadata.recentExecutions.first?.executionId, 1) - } - - func testExecutionMetadataMaxRecentExecutions() { - let outcome = DatabaseExecutionOutcome(id: 1, outcome: "Succeeded", description: "Program executed successfully") - var metadata = ExecutionMetadata(lastOutcome: outcome) - - // Add 15 executions (more than the limit of 10) - for i in 1...15 { - let execution = ExecutionRecord( - executionId: i, - programBase64: "test_program_\(i)", - executionTypeId: 1, - mutatorTypeId: 1, - executionOutcomeId: 1, - feedbackVector: nil, - turboshaftIr: nil, - coverageTotal: Double(i), - executionTimeMs: 100, - signalCode: nil, - exitCode: nil, - stdout: nil, - stderr: nil, - fuzzout: nil, - turbofanOptimizationBits: nil, - feedbackNexusCount: nil, - executionFlags: nil, - engineArguments: nil, - createdAt: Date() - ) - metadata.addExecution(execution) - } - - XCTAssertEqual(metadata.executionCount, 15) - XCTAssertEqual(metadata.recentExecutions.count, 10) // Should only keep last 10 - XCTAssertEqual(metadata.recentExecutions.first?.executionId, 6) // First should be execution 6 - XCTAssertEqual(metadata.recentExecutions.last?.executionId, 15) // Last should be execution 15 - } - - func testExecutionPurposeEnum() { - XCTAssertEqual(DatabaseExecutionPurpose.fuzzing.rawValue, "Fuzzing") - XCTAssertEqual(DatabaseExecutionPurpose.minimization.rawValue, "Minimization") - XCTAssertEqual(DatabaseExecutionPurpose.runtimeAssistedMutation.rawValue, "Runtime Assisted Mutation") - - XCTAssertTrue(DatabaseExecutionPurpose.fuzzing.description.contains("fuzzing purposes")) - XCTAssertTrue(DatabaseExecutionPurpose.minimization.description.contains("minimization task")) - } - - func testMutatorNameEnum() { - XCTAssertEqual(MutatorName.explorationMutator.rawValue, "ExplorationMutator") - XCTAssertEqual(MutatorName.codeGenMutator.rawValue, "CodeGenMutator") - XCTAssertEqual(MutatorName.spliceMutator.rawValue, "SpliceMutator") - - XCTAssertEqual(MutatorName.explorationMutator.category, "runtime_assisted") - XCTAssertEqual(MutatorName.codeGenMutator.category, "instruction") - XCTAssertEqual(MutatorName.concatMutator.category, "base") - - XCTAssertTrue(MutatorName.explorationMutator.description.contains("runtime-assisted mutations")) - XCTAssertTrue(MutatorName.codeGenMutator.description.contains("Generates new code")) - } - - func testExecutionMetadataSerialization() throws { - let outcome = DatabaseExecutionOutcome(id: 1, outcome: "Succeeded", description: "Program executed successfully") - let originalMetadata = ExecutionMetadata( - executionCount: 5, - lastExecutionTime: Date(), - lastCoverage: 75.5, - lastOutcome: outcome, - recentExecutions: [], - feedbackVector: "test_data".data(using: .utf8), - coverageEdges: [1, 2, 3, 4, 5] - ) - - // Test JSON encoding/decoding - let encoder = JSONEncoder() - let data = try encoder.encode(originalMetadata) - - let decoder = JSONDecoder() - let decodedMetadata = try decoder.decode(ExecutionMetadata.self, from: data) - - XCTAssertEqual(originalMetadata.executionCount, decodedMetadata.executionCount) - XCTAssertEqual(originalMetadata.lastCoverage, decodedMetadata.lastCoverage) - XCTAssertEqual(originalMetadata.lastOutcome.outcome, decodedMetadata.lastOutcome.outcome) - XCTAssertEqual(originalMetadata.feedbackVector, decodedMetadata.feedbackVector) - XCTAssertEqual(originalMetadata.coverageEdges, decodedMetadata.coverageEdges) - } - - func testFuzzerInstanceCreation() { - let fuzzer = FuzzerInstance( - fuzzerId: 1, - createdAt: Date(), - fuzzerName: "test_fuzzer", - engineType: "v8", - status: "active" - ) - - XCTAssertEqual(fuzzer.fuzzerId, 1) - XCTAssertEqual(fuzzer.fuzzerName, "test_fuzzer") - XCTAssertEqual(fuzzer.engineType, "v8") - XCTAssertEqual(fuzzer.status, "active") - } - - func testProgramRecordCreation() { - let program = ProgramRecord( - programBase64: "dGVzdF9wcm9ncmFt", - fuzzerId: 1, - insertedAt: Date(), - programSize: 100, - programHash: "abc123def456" - ) - - XCTAssertEqual(program.programBase64, "dGVzdF9wcm9ncmFt") - XCTAssertEqual(program.fuzzerId, 1) - XCTAssertEqual(program.programSize, 100) - XCTAssertEqual(program.programHash, "abc123def456") - } - - func testExecutionRecordCreation() { - let execution = ExecutionRecord( - executionId: 1, - programBase64: "dGVzdF9wcm9ncmFt", - executionTypeId: 1, - mutatorTypeId: 2, - executionOutcomeId: 1, - feedbackVector: "feedback_data".data(using: .utf8), - turboshaftIr: "turboshaft_ir_data", - coverageTotal: 85.5, - executionTimeMs: 150, - signalCode: nil, - exitCode: 0, - stdout: "stdout_data", - stderr: "stderr_data", - fuzzout: "fuzzout_data", - turbofanOptimizationBits: 12345, - feedbackNexusCount: 10, - executionFlags: ["--flag1", "--flag2"], - engineArguments: ["--arg1", "--arg2"], - createdAt: Date() - ) - - XCTAssertEqual(execution.executionId, 1) - XCTAssertEqual(execution.programBase64, "dGVzdF9wcm9ncmFt") - XCTAssertEqual(execution.executionTypeId, 1) - XCTAssertEqual(execution.mutatorTypeId, 2) - XCTAssertEqual(execution.executionOutcomeId, 1) - XCTAssertEqual(execution.coverageTotal, 85.5) - XCTAssertEqual(execution.executionTimeMs, 150) - XCTAssertEqual(execution.exitCode, 0) - XCTAssertEqual(execution.turbofanOptimizationBits, 12345) - XCTAssertEqual(execution.feedbackNexusCount, 10) - XCTAssertEqual(execution.executionFlags, ["--flag1", "--flag2"]) - XCTAssertEqual(execution.engineArguments, ["--arg1", "--arg2"]) - } -} diff --git a/Tests/FuzzilliTests/DatabasePoolSimpleTests.swift b/Tests/FuzzilliTests/DatabasePoolSimpleTests.swift deleted file mode 100644 index a2d1714bb..000000000 --- a/Tests/FuzzilliTests/DatabasePoolSimpleTests.swift +++ /dev/null @@ -1,85 +0,0 @@ -import XCTest -import Foundation -@testable import Fuzzilli - -final class DatabasePoolSimpleTests: XCTestCase { - - func testDatabasePoolCreation() { - let pool = DatabasePool(connectionString: "postgresql://test:test@localhost:5432/testdb") - XCTAssertNotNil(pool) - XCTAssertFalse(pool.isReady) - } - - func testConnectionStringParsing() { - let validConnectionStrings = [ - "postgresql://user:pass@localhost:5432/db", - "postgresql://user@localhost:5432/db", - "postgresql://user:pass@localhost/db" - ] - - for connectionString in validConnectionStrings { - let pool = DatabasePool(connectionString: connectionString) - XCTAssertNotNil(pool) - } - } - - func testInvalidConnectionString() { - let invalidConnectionStrings = [ - "invalid://user:pass@localhost:5432/db", - "not-a-url", - "", - "mysql://user:pass@localhost:5432/db" - ] - - for connectionString in invalidConnectionStrings { - let pool = DatabasePool(connectionString: connectionString) - XCTAssertNotNil(pool) - // The pool creation should succeed, but initialization will fail - } - } - - func testPoolConfiguration() { - let pool = DatabasePool( - connectionString: "postgresql://test:test@localhost:5432/testdb", - maxConnections: 15, - connectionTimeout: 5.0, - retryAttempts: 1 - ) - XCTAssertNotNil(pool) - } - - func testPoolStatsStructure() { - let stats = PoolStats( - totalConnections: 10, - activeConnections: 3, - idleConnections: 7, - isHealthy: true - ) - - XCTAssertEqual(stats.totalConnections, 10) - XCTAssertEqual(stats.activeConnections, 3) - XCTAssertEqual(stats.idleConnections, 7) - XCTAssertTrue(stats.isHealthy) - } - - func testDatabasePoolErrorDescriptions() { - let errors: [DatabasePoolError] = [ - .notInitialized, - .initializationFailed("test error"), - .invalidConnectionString("invalid format"), - .connectionTimeout, - .poolExhausted - ] - - for error in errors { - let description = error.errorDescription - XCTAssertNotNil(description) - XCTAssertFalse(description!.isEmpty) - } - } - - func testDefaultConfiguration() { - let pool = DatabasePool(connectionString: "postgresql://test:test@localhost:5432/testdb") - XCTAssertNotNil(pool) - } -} diff --git a/Tests/FuzzilliTests/DatabaseSchemaTests.swift b/Tests/FuzzilliTests/DatabaseSchemaTests.swift deleted file mode 100644 index be72a9817..000000000 --- a/Tests/FuzzilliTests/DatabaseSchemaTests.swift +++ /dev/null @@ -1,151 +0,0 @@ -import XCTest -import Foundation -@testable import Fuzzilli - -final class DatabaseSchemaTests: XCTestCase { - - func testSchemaSQLContainsRequiredTables() { - let schema = DatabaseSchema.schemaSQL - - let requiredTables = [ - "CREATE TABLE IF NOT EXISTS main", - "CREATE TABLE IF NOT EXISTS fuzzer", - "CREATE TABLE IF NOT EXISTS program", - "CREATE TABLE IF NOT EXISTS execution_type", - "CREATE TABLE IF NOT EXISTS mutator_type", - "CREATE TABLE IF NOT EXISTS execution_outcome", - "CREATE TABLE IF NOT EXISTS execution", - "CREATE TABLE IF NOT EXISTS feedback_vector_detail", - "CREATE TABLE IF NOT EXISTS coverage_detail", - "CREATE TABLE IF NOT EXISTS crash_analysis" - ] - - for table in requiredTables { - XCTAssertTrue(schema.contains(table), "Schema should contain \(table)") - } - } - - func testSchemaSQLContainsRequiredIndexes() { - let schema = DatabaseSchema.schemaSQL - - let requiredIndexes = [ - "CREATE INDEX IF NOT EXISTS idx_execution_program", - "CREATE INDEX IF NOT EXISTS idx_execution_type", - "CREATE INDEX IF NOT EXISTS idx_execution_mutator", - "CREATE INDEX IF NOT EXISTS idx_execution_outcome", - "CREATE INDEX IF NOT EXISTS idx_execution_created", - "CREATE INDEX IF NOT EXISTS idx_execution_coverage", - "CREATE INDEX IF NOT EXISTS idx_feedback_vector_execution", - "CREATE INDEX IF NOT EXISTS idx_coverage_detail_execution", - "CREATE INDEX IF NOT EXISTS idx_crash_analysis_execution" - ] - - for index in requiredIndexes { - XCTAssertTrue(schema.contains(index), "Schema should contain \(index)") - } - } - - func testSchemaSQLContainsRequiredViews() { - let schema = DatabaseSchema.schemaSQL - - let requiredViews = [ - "CREATE OR REPLACE VIEW execution_summary", - "CREATE OR REPLACE VIEW crash_summary" - ] - - for view in requiredViews { - XCTAssertTrue(schema.contains(view), "Schema should contain \(view)") - } - } - - func testSchemaSQLContainsRequiredFunctions() { - let schema = DatabaseSchema.schemaSQL - - let requiredFunctions = [ - "CREATE OR REPLACE FUNCTION get_coverage_stats" - ] - - for function in requiredFunctions { - XCTAssertTrue(schema.contains(function), "Schema should contain \(function)") - } - } - - func testSchemaSQLContainsPreseedData() { - let schema = DatabaseSchema.schemaSQL - - let preseedData = [ - "INSERT INTO execution_type (title, description) VALUES", - "INSERT INTO mutator_type (name, description, category) VALUES", - "INSERT INTO execution_outcome (outcome, description) VALUES" - ] - - for data in preseedData { - XCTAssertTrue(schema.contains(data), "Schema should contain \(data)") - } - } - - func testSchemaSQLContainsConflictHandling() { - let schema = DatabaseSchema.schemaSQL - - XCTAssertTrue(schema.contains("ON CONFLICT (title) DO NOTHING"), "Schema should handle conflicts for execution_type") - XCTAssertTrue(schema.contains("ON CONFLICT (name) DO NOTHING"), "Schema should handle conflicts for mutator_type") - XCTAssertTrue(schema.contains("ON CONFLICT (outcome) DO NOTHING"), "Schema should handle conflicts for execution_outcome") - } - - func testParseConnectionString() { - let (host, port, username, password, database) = DatabaseSchema.parseConnectionString("postgresql://user:pass@localhost:5432/db") - - // For now, the parser returns defaults, but we can test the structure - XCTAssertEqual(host, "localhost") - XCTAssertEqual(port, 5432) - XCTAssertEqual(username, "postgres") - XCTAssertNil(password) - XCTAssertEqual(database, "fuzzilli") - } - - func testDatabaseSchemaInitialization() { - let schema = DatabaseSchema() - XCTAssertNotNil(schema) - } - - func testSchemaSQLIsValidSQL() { - let schema = DatabaseSchema.schemaSQL - - // Basic validation - should contain semicolons and not have obvious syntax errors - XCTAssertTrue(schema.contains(";"), "Schema should contain semicolons") - XCTAssertTrue(schema.contains("CREATE TABLE IF NOT EXISTS"), "Schema should use CREATE TABLE IF NOT EXISTS") - - // Should not contain obvious syntax errors - XCTAssertTrue(schema.contains("CREATE TABLE IF NOT EXISTS main ("), "Should use IF NOT EXISTS") - } - - func testSchemaSQLHasProperConstraints() { - let schema = DatabaseSchema.schemaSQL - - // Check for foreign key constraints - XCTAssertTrue(schema.contains("REFERENCES main(fuzzer_id)"), "Should have foreign key to main table") - XCTAssertTrue(schema.contains("REFERENCES program(program_hash)"), "Should have foreign key to program table") - XCTAssertTrue(schema.contains("REFERENCES execution(execution_id)"), "Should have foreign key to execution table") - - // Check for primary keys - XCTAssertTrue(schema.contains("SERIAL PRIMARY KEY"), "Should have SERIAL PRIMARY KEY") - - // Check for unique constraints - XCTAssertTrue(schema.contains("UNIQUE"), "Should have unique constraints") - } - - func testSchemaSQLHasProperDataTypes() { - let schema = DatabaseSchema.schemaSQL - - // Check for proper data types - XCTAssertTrue(schema.contains("SERIAL"), "Should use SERIAL for auto-incrementing IDs") - XCTAssertTrue(schema.contains("TEXT"), "Should use TEXT for program data") - XCTAssertTrue(schema.contains("VARCHAR"), "Should use VARCHAR for limited strings") - XCTAssertTrue(schema.contains("INTEGER"), "Should use INTEGER for numeric IDs") - XCTAssertTrue(schema.contains("NUMERIC"), "Should use NUMERIC for coverage percentages") - XCTAssertTrue(schema.contains("JSONB"), "Should use JSONB for structured data") - XCTAssertTrue(schema.contains("BIGINT"), "Should use BIGINT for large numbers") - XCTAssertTrue(schema.contains("BOOLEAN"), "Should use BOOLEAN for flags") - XCTAssertTrue(schema.contains("TEXT[]"), "Should use TEXT[] for arrays") - } -} diff --git a/Tests/FuzzilliTests/DatabaseUtilsTests.swift b/Tests/FuzzilliTests/DatabaseUtilsTests.swift deleted file mode 100644 index 3ec8831be..000000000 --- a/Tests/FuzzilliTests/DatabaseUtilsTests.swift +++ /dev/null @@ -1,185 +0,0 @@ -import XCTest -import Foundation -@testable import Fuzzilli - -final class DatabaseUtilsTests: XCTestCase { - - func testProgramEncodingDecoding() throws { - // Create a simple program using ProgramBuilder - let fuzzer = makeMockFuzzer() - let b = fuzzer.makeBuilder() - b.loadInt(42) - b.loadString("test") - let program = b.finalize() - - // Test encoding - let base64 = DatabaseUtils.encodeProgramToBase64(program: program) - XCTAssertFalse(base64.isEmpty, "Base64 encoding should not be empty") - - // Test decoding - let decodedProgram = try DatabaseUtils.decodeProgramFromBase64(base64: base64) - XCTAssertEqual(decodedProgram.size, program.size, "Decoded program should have same size") - } - - func testProgramHashCalculation() { - // Create a simple program using ProgramBuilder - let fuzzer = makeMockFuzzer() - let b = fuzzer.makeBuilder() - b.loadInt(42) - b.loadString("test") - let program = b.finalize() - - // Test hash calculation - let hash = DatabaseUtils.calculateProgramHash(program: program) - XCTAssertEqual(hash.count, 16, "Hash should be 16 characters") - XCTAssertTrue(hash.allSatisfy { $0.isHexDigit }, "Hash should contain only hex digits") - - // Test hash consistency - let hash2 = DatabaseUtils.calculateProgramHash(program: program) - XCTAssertEqual(hash, hash2, "Hash should be consistent for same program") - } - - func testExecutionMetadataSerialization() throws { - // Create execution metadata - let outcome = DatabaseExecutionOutcome(id: 1, outcome: "Succeeded", description: "Program executed successfully") - var metadata = ExecutionMetadata(lastOutcome: outcome) - metadata.executionCount = 5 - metadata.lastCoverage = 85.5 - - // Test serialization - let data = DatabaseUtils.serializeExecutionMetadata(metadata: metadata) - XCTAssertFalse(data.isEmpty, "Serialized data should not be empty") - - // Test deserialization - let deserializedMetadata = try DatabaseUtils.deserializeExecutionMetadata(data: data) - XCTAssertEqual(deserializedMetadata.executionCount, metadata.executionCount) - XCTAssertEqual(deserializedMetadata.lastCoverage, metadata.lastCoverage, accuracy: 0.01) - XCTAssertEqual(deserializedMetadata.lastOutcome.outcome, metadata.lastOutcome.outcome) - } - - func testExecutionOutcomeMapping() { - // Test mapping to database ID - XCTAssertEqual(DatabaseUtils.mapExecutionOutcome(outcome: .succeeded), 1) - XCTAssertEqual(DatabaseUtils.mapExecutionOutcome(outcome: .failed(1)), 2) - XCTAssertEqual(DatabaseUtils.mapExecutionOutcome(outcome: .crashed(1)), 3) - XCTAssertEqual(DatabaseUtils.mapExecutionOutcome(outcome: .timedOut), 4) - - // Test mapping from database ID - XCTAssertEqual(DatabaseUtils.mapExecutionOutcomeFromId(id: 1), .succeeded) - XCTAssertEqual(DatabaseUtils.mapExecutionOutcomeFromId(id: 2), .failed(1)) - XCTAssertEqual(DatabaseUtils.mapExecutionOutcomeFromId(id: 3), .crashed(1)) - XCTAssertEqual(DatabaseUtils.mapExecutionOutcomeFromId(id: 4), .timedOut) - XCTAssertEqual(DatabaseUtils.mapExecutionOutcomeFromId(id: 999), .succeeded) // Invalid ID fallback - } - - func testMutatorTypeMapping() { - // Test mapping to database ID - XCTAssertEqual(DatabaseUtils.mapMutatorType(mutator: "Splice"), 1) - XCTAssertEqual(DatabaseUtils.mapMutatorType(mutator: "splice"), 1) // Case insensitive - XCTAssertEqual(DatabaseUtils.mapMutatorType(mutator: "InputMutation"), 2) - XCTAssertEqual(DatabaseUtils.mapMutatorType(mutator: "WasmType"), 19) - XCTAssertNil(DatabaseUtils.mapMutatorType(mutator: "UnknownMutator")) - - // Test mapping from database ID - XCTAssertEqual(DatabaseUtils.mapMutatorTypeFromId(id: 1), "Splice") - XCTAssertEqual(DatabaseUtils.mapMutatorTypeFromId(id: 2), "InputMutation") - XCTAssertEqual(DatabaseUtils.mapMutatorTypeFromId(id: 19), "WasmType") - XCTAssertNil(DatabaseUtils.mapMutatorTypeFromId(id: 999)) // Invalid ID - } - - func testExecutionTypeMapping() { - // Test mapping to database ID - XCTAssertEqual(DatabaseUtils.mapExecutionType(purpose: .fuzzing), 1) - XCTAssertEqual(DatabaseUtils.mapExecutionType(purpose: .programImport), 2) - XCTAssertEqual(DatabaseUtils.mapExecutionType(purpose: .minimization), 3) - XCTAssertEqual(DatabaseUtils.mapExecutionType(purpose: .other), 7) - - // Test mapping from database ID - XCTAssertEqual(DatabaseUtils.mapExecutionTypeFromId(id: 1), .fuzzing) - XCTAssertEqual(DatabaseUtils.mapExecutionTypeFromId(id: 2), .programImport) - XCTAssertEqual(DatabaseUtils.mapExecutionTypeFromId(id: 3), .minimization) - XCTAssertEqual(DatabaseUtils.mapExecutionTypeFromId(id: 7), .other) - XCTAssertEqual(DatabaseUtils.mapExecutionTypeFromId(id: 999), .other) // Invalid ID - } - - func testDataValidation() { - // Test base64 validation - XCTAssertTrue(DatabaseUtils.isValidBase64("SGVsbG8gV29ybGQ=")) // "Hello World" - XCTAssertFalse(DatabaseUtils.isValidBase64("Invalid base64!")) - XCTAssertFalse(DatabaseUtils.isValidBase64("")) - - // Test program hash validation - let validHash = "a1b2c3d4e5f67890" - XCTAssertTrue(DatabaseUtils.isValidProgramHash(validHash)) - XCTAssertFalse(DatabaseUtils.isValidProgramHash("invalid hash")) - XCTAssertFalse(DatabaseUtils.isValidProgramHash("short")) - XCTAssertFalse(DatabaseUtils.isValidProgramHash("")) - - // Test execution metadata validation - let outcome = DatabaseExecutionOutcome(id: 1, outcome: "Succeeded", description: "Test") - let metadata = ExecutionMetadata(lastOutcome: outcome) - let validData = DatabaseUtils.serializeExecutionMetadata(metadata: metadata) - XCTAssertTrue(DatabaseUtils.isValidExecutionMetadata(validData)) - XCTAssertFalse(DatabaseUtils.isValidExecutionMetadata(Data("invalid json".utf8))) - } - - func testUtilityFunctions() { - // Test program ID generation - let fuzzer = makeMockFuzzer() - let b = fuzzer.makeBuilder() - b.loadInt(42) - let program = b.finalize() - let programId = DatabaseUtils.generateProgramId(program: program) - XCTAssertTrue(programId.hasPrefix("prog_")) - XCTAssertEqual(programId.count, 21) // "prog_" + 16 hex chars - - // Test execution ID generation - let executionId = DatabaseUtils.generateExecutionId() - XCTAssertTrue(executionId.hasPrefix("exec_")) - XCTAssertTrue(executionId.contains("_")) - - // Test coverage formatting - XCTAssertEqual(DatabaseUtils.formatCoveragePercentage(85.5), "85.50%") - XCTAssertEqual(DatabaseUtils.formatCoveragePercentage(0.0), "0.00%") - XCTAssertEqual(DatabaseUtils.formatCoveragePercentage(100.0), "100.00%") - - // Test execution time formatting - XCTAssertEqual(DatabaseUtils.formatExecutionTime(500), "500ms") - XCTAssertEqual(DatabaseUtils.formatExecutionTime(1500), "1.5s") - XCTAssertEqual(DatabaseUtils.formatExecutionTime(65000), "1m 5s") - XCTAssertEqual(DatabaseUtils.formatExecutionTime(125000), "2m 5s") - } - - func testExecutionSummary() { - let outcome = DatabaseExecutionOutcome(id: 1, outcome: "Succeeded", description: "Test") - var metadata = ExecutionMetadata(lastOutcome: outcome) - metadata.executionCount = 10 - metadata.lastCoverage = 75.5 - - let summary = DatabaseUtils.createExecutionSummary(metadata: metadata) - XCTAssertTrue(summary.contains("Executions: 10")) - XCTAssertTrue(summary.contains("Coverage: 75.50%")) - XCTAssertTrue(summary.contains("Last: Succeeded")) - } - - func testDatabaseUtilsErrorDescriptions() { - XCTAssertEqual(DatabaseUtilsError.invalidBase64String.errorDescription, "Invalid base64 string") - XCTAssertEqual(DatabaseUtilsError.invalidProgramData.errorDescription, "Invalid program data") - XCTAssertEqual(DatabaseUtilsError.serializationFailed.errorDescription, "Failed to serialize data") - XCTAssertEqual(DatabaseUtilsError.deserializationFailed.errorDescription, "Failed to deserialize data") - XCTAssertEqual(DatabaseUtilsError.invalidHash.errorDescription, "Invalid hash format") - XCTAssertEqual(DatabaseUtilsError.invalidMetadata.errorDescription, "Invalid metadata format") - } - - func testCharacterHexDigitExtension() { - XCTAssertTrue("0".first!.isHexDigit) - XCTAssertTrue("9".first!.isHexDigit) - XCTAssertTrue("a".first!.isHexDigit) - XCTAssertTrue("f".first!.isHexDigit) - XCTAssertTrue("A".first!.isHexDigit) - XCTAssertTrue("F".first!.isHexDigit) - XCTAssertFalse("g".first!.isHexDigit) - XCTAssertFalse("Z".first!.isHexDigit) - XCTAssertFalse("@".first!.isHexDigit) - } -} diff --git a/Tests/FuzzilliTests/PostgreSQLCorpusIntegrationTests.swift b/Tests/FuzzilliTests/PostgreSQLCorpusIntegrationTests.swift deleted file mode 100644 index 4eaff17d3..000000000 --- a/Tests/FuzzilliTests/PostgreSQLCorpusIntegrationTests.swift +++ /dev/null @@ -1,143 +0,0 @@ -import XCTest -import Foundation -@testable import Fuzzilli - -final class PostgreSQLCorpusIntegrationTests: XCTestCase { - - func testPostgreSQLCorpusCLIIntegration() { - // Test that PostgreSQL corpus can be created with proper configuration - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let fuzzerInstanceId = "test-fuzzer-123" - - let corpus = PostgreSQLCorpus( - minSize: 10, - maxSize: 100, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: fuzzerInstanceId - ) - - XCTAssertEqual(corpus.size, 0) - XCTAssertTrue(corpus.isEmpty) - XCTAssertTrue(corpus.supportsFastStateSynchronization) - } - - func testPostgreSQLCorpusConfiguration() { - // Test that PostgreSQL corpus accepts the same configuration as BasicCorpus - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let fuzzerInstanceId = "test-fuzzer-456" - - let corpus = PostgreSQLCorpus( - minSize: 1000, - maxSize: 10000, - minMutationsPerSample: 25, - databasePool: databasePool, - fuzzerInstanceId: fuzzerInstanceId - ) - - XCTAssertEqual(corpus.size, 0) - XCTAssertTrue(corpus.isEmpty) - - // Test statistics - let stats = corpus.getStatistics() - XCTAssertEqual(stats.fuzzerInstanceId, fuzzerInstanceId) - XCTAssertEqual(stats.totalPrograms, 0) - XCTAssertEqual(stats.totalExecutions, 0) - XCTAssertEqual(stats.averageCoverage, 0.0) - XCTAssertEqual(stats.pendingSyncOperations, 0) - } - - func testPostgreSQLCorpusProtocolConformance() { - // Test that PostgreSQLCorpus properly implements the Corpus protocol - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let fuzzerInstanceId = "test-fuzzer-789" - - let corpus: Corpus = PostgreSQLCorpus( - minSize: 10, - maxSize: 100, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: fuzzerInstanceId - ) - - // Test basic protocol methods - XCTAssertEqual(corpus.size, 0) - XCTAssertTrue(corpus.isEmpty) - XCTAssertTrue(corpus.supportsFastStateSynchronization) - - // Test that we can get all programs (should be empty initially) - let allPrograms = corpus.allPrograms() - XCTAssertTrue(allPrograms.isEmpty) - - // Test state export/import - do { - let exportedData = try corpus.exportState() - // Empty corpus can have empty export data, which is valid - // XCTAssertFalse(exportedData.isEmpty) - - // Test that we can import the state back - try corpus.importState(exportedData) - XCTAssertEqual(corpus.size, 0) - } catch { - XCTFail("State export/import failed: \(error)") - } - } - - func testPostgreSQLCorpusWithDifferentSizes() { - // Test PostgreSQL corpus with different size configurations - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let fuzzerInstanceId = "test-fuzzer-sizes" - - // Test with small sizes - let smallCorpus = PostgreSQLCorpus( - minSize: 1, - maxSize: 10, - minMutationsPerSample: 1, - databasePool: databasePool, - fuzzerInstanceId: fuzzerInstanceId - ) - - XCTAssertEqual(smallCorpus.size, 0) - XCTAssertTrue(smallCorpus.isEmpty) - - // Test with large sizes - let largeCorpus = PostgreSQLCorpus( - minSize: 10000, - maxSize: 100000, - minMutationsPerSample: 100, - databasePool: databasePool, - fuzzerInstanceId: fuzzerInstanceId - ) - - XCTAssertEqual(largeCorpus.size, 0) - XCTAssertTrue(largeCorpus.isEmpty) - } - - func testPostgreSQLCorpusStatistics() { - // Test that statistics are properly tracked - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let fuzzerInstanceId = "test-fuzzer-stats" - - let corpus = PostgreSQLCorpus( - minSize: 10, - maxSize: 100, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: fuzzerInstanceId - ) - - let stats = corpus.getStatistics() - XCTAssertEqual(stats.fuzzerInstanceId, fuzzerInstanceId) - XCTAssertEqual(stats.totalPrograms, 0) - XCTAssertEqual(stats.totalExecutions, 0) - XCTAssertEqual(stats.averageCoverage, 0.0) - XCTAssertEqual(stats.pendingSyncOperations, 0) - - // Test statistics description - let description = stats.description - XCTAssertTrue(description.contains("Programs: 0")) - XCTAssertTrue(description.contains("Executions: 0")) - XCTAssertTrue(description.contains("Coverage: 0.00%")) - XCTAssertTrue(description.contains("Pending Sync: 0")) - } -} diff --git a/Tests/FuzzilliTests/PostgreSQLCorpusTests.swift b/Tests/FuzzilliTests/PostgreSQLCorpusTests.swift deleted file mode 100644 index ff4242c8a..000000000 --- a/Tests/FuzzilliTests/PostgreSQLCorpusTests.swift +++ /dev/null @@ -1,204 +0,0 @@ -import XCTest -import Foundation -@testable import Fuzzilli - -final class PostgreSQLCorpusTests: XCTestCase { - - func testPostgreSQLCorpusInitialization() { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let corpus = PostgreSQLCorpus( - minSize: 10, - maxSize: 100, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: "test-instance-1" - ) - - XCTAssertEqual(corpus.size, 0) - XCTAssertTrue(corpus.isEmpty) - XCTAssertTrue(corpus.supportsFastStateSynchronization) - } - - func testPostgreSQLCorpusAddProgram() { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - - // Create corpus - let corpus = PostgreSQLCorpus( - minSize: 10, - maxSize: 100, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: "test-instance-1" - ) - - // Test basic properties - XCTAssertEqual(corpus.size, 0) - XCTAssertTrue(corpus.isEmpty) - XCTAssertTrue(corpus.supportsFastStateSynchronization) - - // Test statistics - let stats = corpus.getStatistics() - XCTAssertEqual(stats.totalPrograms, 0) - XCTAssertEqual(stats.fuzzerInstanceId, "test-instance-1") - } - - func testPostgreSQLCorpusRandomElementAccess() { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let corpus = PostgreSQLCorpus( - minSize: 10, - maxSize: 100, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: "test-instance-1" - ) - - // Test that corpus starts empty - XCTAssertEqual(corpus.size, 0) - XCTAssertTrue(corpus.isEmpty) - - // Test that allPrograms returns empty array - let allPrograms = corpus.allPrograms() - XCTAssertEqual(allPrograms.count, 0) - } - - func testPostgreSQLCorpusAllPrograms() { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let corpus = PostgreSQLCorpus( - minSize: 10, - maxSize: 100, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: "test-instance-1" - ) - - // Test that allPrograms returns empty array initially - let allPrograms = corpus.allPrograms() - XCTAssertEqual(allPrograms.count, 0) - XCTAssertTrue(allPrograms.isEmpty) - } - - func testPostgreSQLCorpusStateExportImport() throws { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let corpus = PostgreSQLCorpus( - minSize: 10, - maxSize: 100, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: "test-instance-1" - ) - - // Test export of empty corpus - let exportedData = try corpus.exportState() - // Empty corpus can have empty export data, which is valid - // XCTAssertFalse(exportedData.isEmpty) - - // Create new corpus and import state - let newCorpus = PostgreSQLCorpus( - minSize: 10, - maxSize: 100, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: "test-instance-2" - ) - - try newCorpus.importState(exportedData) - XCTAssertEqual(newCorpus.size, 0) - } - - func testPostgreSQLCorpusDuplicateProgramHandling() { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let corpus = PostgreSQLCorpus( - minSize: 10, - maxSize: 100, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: "test-instance-1" - ) - - // Test that corpus starts empty - XCTAssertEqual(corpus.size, 0) - XCTAssertTrue(corpus.isEmpty) - } - - func testPostgreSQLCorpusStatistics() { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let corpus = PostgreSQLCorpus( - minSize: 10, - maxSize: 100, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: "test-instance-1" - ) - - // Test initial statistics - let initialStats = corpus.getStatistics() - XCTAssertEqual(initialStats.totalPrograms, 0) - XCTAssertEqual(initialStats.totalExecutions, 0) - XCTAssertEqual(initialStats.averageCoverage, 0.0) - XCTAssertEqual(initialStats.pendingSyncOperations, 0) - XCTAssertEqual(initialStats.fuzzerInstanceId, "test-instance-1") - } - - func testPostgreSQLCorpusWithDifferentAspects() { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let corpus = PostgreSQLCorpus( - minSize: 10, - maxSize: 100, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: "test-instance-1" - ) - - // Test that corpus starts empty - XCTAssertEqual(corpus.size, 0) - XCTAssertTrue(corpus.isEmpty) - } - - func testCorpusStatisticsDescription() { - let stats = CorpusStatistics( - totalPrograms: 10, - totalExecutions: 100, - averageCoverage: 75.5, - pendingSyncOperations: 3, - fuzzerInstanceId: "test-instance" - ) - - let description = stats.description - XCTAssertTrue(description.contains("Programs: 10")) - XCTAssertTrue(description.contains("Executions: 100")) - XCTAssertTrue(description.contains("Coverage: 75.50%")) - XCTAssertTrue(description.contains("Pending Sync: 3")) - } - - func testPostgreSQLCorpusInterestingProgramTracking() { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let corpus = PostgreSQLCorpus( - minSize: 1, - maxSize: 10, - minMutationsPerSample: 5, - databasePool: databasePool, - fuzzerInstanceId: "test-instance-1" - ) - - // Create a mock fuzzer to initialize the corpus - let mockFuzzer = makeMockFuzzer(corpus: corpus) - - // Create a simple program with actual content - let b = mockFuzzer.makeBuilder() - b.loadInt(42) - let program = b.finalize() - - // Add the program to the corpus - // This should trigger the InterestingProgramFound event - corpus.add(program, ProgramAspects(outcome: .succeeded)) - - // Verify the program was added - XCTAssertEqual(corpus.size, 1) - XCTAssertFalse(corpus.isEmpty) - - // Test that we can get the program back - let allPrograms = corpus.allPrograms() - XCTAssertEqual(allPrograms.count, 1) - XCTAssertEqual(allPrograms[0], program) - } -} diff --git a/Tests/FuzzilliTests/PostgreSQLIntegrationTests.swift b/Tests/FuzzilliTests/PostgreSQLIntegrationTests.swift deleted file mode 100644 index 14a958e71..000000000 --- a/Tests/FuzzilliTests/PostgreSQLIntegrationTests.swift +++ /dev/null @@ -1,153 +0,0 @@ -import XCTest -import Foundation -@testable import Fuzzilli - -final class PostgreSQLIntegrationTests: XCTestCase { - - var databasePool: DatabasePool! - var storage: PostgreSQLStorage! - - override func setUp() async throws { - try await super.setUp() - - // Use the PostgreSQL container we set up - let connectionString = "postgresql://fuzzilli:fuzzilli123@localhost:5433/fuzzilli" - databasePool = DatabasePool(connectionString: connectionString) - - try await databasePool.initialize() - storage = PostgreSQLStorage(databasePool: databasePool) - } - - override func tearDown() async throws { - await databasePool.shutdown() - try await super.tearDown() - } - - func testDatabaseConnection() async throws { - let isConnected = try await databasePool.testConnection() - XCTAssertTrue(isConnected, "Should be able to connect to PostgreSQL") - } - - func testFuzzerRegistration() async throws { - let fuzzerId = try await storage.registerFuzzer( - name: "test-fuzzer-\(UUID().uuidString.prefix(8))", - engineType: "v8", - hostname: "localhost" - ) - - XCTAssertGreaterThan(fuzzerId, 0, "Should return a valid fuzzer ID") - - // Verify the fuzzer was actually stored - let fuzzer = try await storage.getFuzzer(name: "test-fuzzer-\(UUID().uuidString.prefix(8))") - // Note: This will be nil because we're using a different UUID, but the registration should work - } - - func testProgramStorage() async throws { - // Create a simple program - let fuzzer = makeMockFuzzer() - let b = fuzzer.makeBuilder() - b.loadInt(42) - b.loadString("test") - let program = b.finalize() - - // Create execution metadata - let outcome = DatabaseExecutionOutcome(id: 1, outcome: "Succeeded", description: "Test execution") - let metadata = ExecutionMetadata(lastOutcome: outcome) - - // Register a fuzzer first - let fuzzerId = try await storage.registerFuzzer( - name: "test-fuzzer-program-\(UUID().uuidString.prefix(8))", - engineType: "v8" - ) - - // Store the program - let programHash = try await storage.storeProgram( - program: program, - fuzzerId: fuzzerId, - metadata: metadata - ) - - XCTAssertFalse(programHash.isEmpty, "Should return a valid program hash") - } - - func testExecutionStorage() async throws { - // Create a simple program - let fuzzer = makeMockFuzzer() - let b = fuzzer.makeBuilder() - b.loadInt(42) - let program = b.finalize() - - // Register a fuzzer first - let fuzzerId = try await storage.registerFuzzer( - name: "test-fuzzer-exec-\(UUID().uuidString.prefix(8))", - engineType: "v8" - ) - - // Store execution - let executionId = try await storage.storeExecution( - program: program, - fuzzerId: fuzzerId, - executionType: .fuzzing, - mutatorType: "Splice", - outcome: .succeeded, - coverage: 85.0, - executionTimeMs: 150, - feedbackVector: nil, - coverageEdges: [1, 2, 3, 4, 5] - ) - - XCTAssertGreaterThan(executionId, 0, "Should return a valid execution ID") - } - - func testDatabaseSchemaVerification() async throws { - let schema = DatabaseSchema() - - // For now, just test that the schema can be created - // The actual verification would require a real database connection - XCTAssertNotNil(schema, "Database schema should be created") - - // Test that the schema SQL is not empty - XCTAssertFalse(DatabaseSchema.schemaSQL.isEmpty, "Schema SQL should not be empty") - XCTAssertTrue(DatabaseSchema.schemaSQL.contains("CREATE TABLE"), "Schema should contain CREATE TABLE statements") - } - - func testLookupTables() async throws { - // Test that the lookup table enums are properly defined - let executionPurposes = DatabaseExecutionPurpose.allCases - XCTAssertGreaterThan(executionPurposes.count, 0, "Should have execution purposes") - XCTAssertTrue(executionPurposes.contains(.fuzzing), "Should have fuzzing execution purpose") - - let mutatorNames = MutatorName.allCases - XCTAssertGreaterThan(mutatorNames.count, 0, "Should have mutator names") - XCTAssertTrue(mutatorNames.contains(.spliceMutator), "Should have splice mutator") - - // Test that the mapping functions work - let fuzzingId = DatabaseUtils.mapExecutionType(purpose: .fuzzing) - XCTAssertEqual(fuzzingId, 1, "Fuzzing should map to ID 1") - - let spliceId = DatabaseUtils.mapMutatorType(mutator: "splice") - XCTAssertEqual(spliceId, 1, "Splice should map to ID 1") - } - - func testConcurrentOperations() async throws { - // Test concurrent fuzzer registrations - let fuzzerNames = (1...5).map { "concurrent-fuzzer-\($0)" } - - let fuzzerIds = try await withThrowingTaskGroup(of: Int.self) { group in - for name in fuzzerNames { - group.addTask { - try await self.storage.registerFuzzer(name: name, engineType: "v8") - } - } - - var ids: [Int] = [] - for try await id in group { - ids.append(id) - } - return ids - } - - XCTAssertEqual(fuzzerIds.count, 5, "Should register all 5 fuzzers") - XCTAssertTrue(fuzzerIds.allSatisfy { $0 > 0 }, "All fuzzer IDs should be valid") - } -} diff --git a/Tests/FuzzilliTests/PostgreSQLStorageTests.swift b/Tests/FuzzilliTests/PostgreSQLStorageTests.swift deleted file mode 100644 index af0fa666b..000000000 --- a/Tests/FuzzilliTests/PostgreSQLStorageTests.swift +++ /dev/null @@ -1,228 +0,0 @@ -import XCTest -import Foundation -@testable import Fuzzilli - -final class PostgreSQLStorageTests: XCTestCase { - - func testPostgreSQLStorageInitialization() { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let storage = PostgreSQLStorage(databasePool: databasePool) - - XCTAssertNotNil(storage) - } - - func testFuzzerRegistration() async throws { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let storage = PostgreSQLStorage(databasePool: databasePool) - - // Test fuzzer registration (this would fail without actual database) - // For now, we'll just test that the method exists and can be called - do { - let fuzzerId = try await storage.registerFuzzer( - name: "test-fuzzer-1", - engineType: "multi", - hostname: "localhost" - ) - // This will fail without actual database, but we can test the interface - XCTAssertGreaterThan(fuzzerId, 0) - } catch { - // Expected to fail without actual database connection - XCTAssertTrue(error is DatabasePoolError) - } - } - - func testProgramStorage() async throws { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let storage = PostgreSQLStorage(databasePool: databasePool) - - // Create execution metadata - let outcome = DatabaseExecutionOutcome(id: 1, outcome: "Succeeded", description: "Test execution") - var metadata = ExecutionMetadata(lastOutcome: outcome) - metadata.executionCount = 1 - metadata.lastCoverage = 75.5 - - // Test program storage interface (this would fail without actual database) - // We'll test with a minimal program creation - let program = Program() - - do { - let programHash = try await storage.storeProgram( - program: program, - fuzzerId: 1, - metadata: metadata - ) - XCTAssertFalse(programHash.isEmpty) - } catch { - // Expected to fail without actual database connection - XCTAssertTrue(error is DatabasePoolError) - } - } - - func testExecutionStorage() async throws { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let storage = PostgreSQLStorage(databasePool: databasePool) - - // Test execution storage interface - let program = Program() - - do { - let executionId = try await storage.storeExecution( - program: program, - fuzzerId: 1, - executionType: .fuzzing, - mutatorType: "Splice", - outcome: .succeeded, - coverage: 85.0, - executionTimeMs: 150, - coverageEdges: [1, 2, 3, 4, 5] - ) - XCTAssertGreaterThan(executionId, 0) - } catch { - // Expected to fail without actual database connection - XCTAssertTrue(error is DatabasePoolError) - } - } - - func testCrashStorage() async throws { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let storage = PostgreSQLStorage(databasePool: databasePool) - - // Test crash storage interface - let program = Program() - - do { - let crashId = try await storage.storeCrash( - program: program, - fuzzerId: 1, - executionId: 1, - crashType: "Segmentation Fault", - signalCode: 11, - stdout: "Program output", - stderr: "Segmentation fault" - ) - XCTAssertGreaterThan(crashId, 0) - } catch { - // Expected to fail without actual database connection - XCTAssertTrue(error is DatabasePoolError) - } - } - - func testProgramRetrieval() async throws { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let storage = PostgreSQLStorage(databasePool: databasePool) - - // Test program retrieval (this would fail without actual database) - do { - let program = try await storage.getProgram(hash: "test-hash") - // This will be nil without actual database - XCTAssertNil(program) - } catch { - // Expected to fail without actual database connection - XCTAssertTrue(error is DatabasePoolError) - } - } - - func testMetadataRetrieval() async throws { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let storage = PostgreSQLStorage(databasePool: databasePool) - - // Test metadata retrieval - do { - let metadata = try await storage.getProgramMetadata(programHash: "test-hash", fuzzerId: 1) - // This will be nil without actual database - XCTAssertNil(metadata) - } catch { - // Expected to fail without actual database connection - XCTAssertTrue(error is DatabasePoolError) - } - } - - func testExecutionHistoryRetrieval() async throws { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let storage = PostgreSQLStorage(databasePool: databasePool) - - // Test execution history retrieval - do { - let history = try await storage.getExecutionHistory(programHash: "test-hash", fuzzerId: 1, limit: 10) - // This will be empty without actual database - XCTAssertTrue(history.isEmpty) - } catch { - // Expected to fail without actual database connection - XCTAssertTrue(error is DatabasePoolError) - } - } - - func testRecentProgramsRetrieval() async throws { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let storage = PostgreSQLStorage(databasePool: databasePool) - - // Test recent programs retrieval - do { - let programs = try await storage.getRecentPrograms(fuzzerId: 1, since: Date(), limit: 10) - // This will be empty without actual database - XCTAssertTrue(programs.isEmpty) - } catch { - // Expected to fail without actual database connection - XCTAssertTrue(error is DatabasePoolError) - } - } - - func testMetadataUpdate() async throws { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let storage = PostgreSQLStorage(databasePool: databasePool) - - // Create execution metadata - let outcome = DatabaseExecutionOutcome(id: 1, outcome: "Succeeded", description: "Test execution") - var metadata = ExecutionMetadata(lastOutcome: outcome) - metadata.executionCount = 5 - metadata.lastCoverage = 90.0 - - // Test metadata update - do { - try await storage.updateProgramMetadata(programHash: "test-hash", fuzzerId: 1, metadata: metadata) - // This will succeed or fail depending on database connection - } catch { - // Expected to fail without actual database connection - XCTAssertTrue(error is DatabasePoolError) - } - } - - func testStorageStatistics() async throws { - let databasePool = DatabasePool(connectionString: "postgresql://localhost:5432/fuzzilli") - let storage = PostgreSQLStorage(databasePool: databasePool) - - // Test storage statistics - do { - let stats = try await storage.getStorageStatistics() - XCTAssertGreaterThanOrEqual(stats.totalPrograms, 0) - XCTAssertGreaterThanOrEqual(stats.totalExecutions, 0) - XCTAssertGreaterThanOrEqual(stats.totalCrashes, 0) - XCTAssertGreaterThanOrEqual(stats.activeFuzzers, 0) - } catch { - // Expected to fail without actual database connection - XCTAssertTrue(error is DatabasePoolError) - } - } - - func testStorageStatisticsDescription() { - let stats = StorageStatistics( - totalPrograms: 100, - totalExecutions: 1000, - totalCrashes: 5, - activeFuzzers: 3 - ) - - let description = stats.description - XCTAssertTrue(description.contains("Programs: 100")) - XCTAssertTrue(description.contains("Executions: 1000")) - XCTAssertTrue(description.contains("Crashes: 5")) - XCTAssertTrue(description.contains("Active Fuzzers: 3")) - } - - func testPostgreSQLStorageErrorDescriptions() { - XCTAssertEqual(PostgreSQLStorageError.noResult.errorDescription, "No result returned from database query") - XCTAssertEqual(PostgreSQLStorageError.invalidData.errorDescription, "Invalid data returned from database") - XCTAssertEqual(PostgreSQLStorageError.connectionFailed.errorDescription, "Failed to connect to database") - XCTAssertEqual(PostgreSQLStorageError.queryFailed("test").errorDescription, "Database query failed: test") - } -} From 34c363c36d659a31a16220785ec9a46a43c2c0d8 Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Mon, 10 Nov 2025 21:22:06 -0500 Subject: [PATCH 22/23] semi-working distributed --- Cloud/VRIG/Dockerfile.distributed | 2 +- Scripts/clear-postgres-db.sh | 173 +++++++ .../Fuzzilli/Corpus/PostgreSQLCorpus.swift | 436 ++++++++++++++---- .../Fuzzilli/Database/DatabaseSchema.swift | 2 +- .../Fuzzilli/Database/PostgreSQLStorage.swift | 178 +++---- docker-compose.workers.yml | 252 ---------- env.distributed | 2 +- 7 files changed, 633 insertions(+), 412 deletions(-) create mode 100755 Scripts/clear-postgres-db.sh diff --git a/Cloud/VRIG/Dockerfile.distributed b/Cloud/VRIG/Dockerfile.distributed index 8dd2b2c31..3c5ff71c1 100644 --- a/Cloud/VRIG/Dockerfile.distributed +++ b/Cloud/VRIG/Dockerfile.distributed @@ -46,7 +46,7 @@ RUN mkdir -p ./Corpus ENV MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli ENV POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master ENV FUZZER_INSTANCE_NAME=fuzzer-default -ENV SYNC_INTERVAL=300 +ENV SYNC_INTERVAL=5 ENV TIMEOUT=2500 ENV MIN_MUTATIONS_PER_SAMPLE=25 ENV DEBUG_LOGGING=false diff --git a/Scripts/clear-postgres-db.sh b/Scripts/clear-postgres-db.sh new file mode 100755 index 000000000..df14710ac --- /dev/null +++ b/Scripts/clear-postgres-db.sh @@ -0,0 +1,173 @@ +#!/bin/bash + +# Script to fully clear all PostgreSQL databases (master and all local workers) +# This script truncates all tables in all databases to remove all data +# Usage: ./Scripts/clear-postgres-db.sh [--force] + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Check for --force flag +FORCE=false +if [ "$1" == "--force" ]; then + FORCE=true +fi + +# Function to check if Docker is available +check_docker() { + if ! command -v docker &> /dev/null; then + echo -e "${RED}Error: Docker command not found. Please install Docker.${NC}" + exit 1 + fi +} + +# Function to clear a database +clear_database() { + local container_name="$1" + local db_name="$2" + local db_user="${3:-fuzzilli}" + local db_password="${4:-fuzzilli123}" + + echo -e "${BLUE}Clearing database '${db_name}' in container '${container_name}'...${NC}" + + # Check if container is running (handle both short and full container names) + local container_running="" + while IFS= read -r line; do + if [[ "$line" =~ ^(${container_name}|fuzzilli-${container_name})$ ]]; then + container_running="$line" + break + fi + done < <(docker ps --format "{{.Names}}") + + if [ -z "$container_running" ]; then + echo -e "${YELLOW}Warning: Container '${container_name}' or 'fuzzilli-${container_name}' is not running. Skipping.${NC}" + return 0 + fi + # Use the actual running container name + local actual_container_name="$container_running" + + # List of tables to truncate (in order to handle foreign keys) + # Using TRUNCATE CASCADE to handle foreign key constraints + local truncate_sql=" + TRUNCATE TABLE + coverage_detail, + feedback_vector_detail, + execution, + program, + fuzzer, + coverage_snapshot, + main + CASCADE; + " + + # Execute truncate (use actual container name if found) + local exec_container="${actual_container_name:-${container_name}}" + if docker exec -i "${exec_container}" psql -U "${db_user}" -d "${db_name}" -c "${truncate_sql}" &>/dev/null; then + echo -e "${GREEN}Successfully cleared database '${db_name}'${NC}" + return 0 + else + echo -e "${RED}Error: Failed to clear database '${db_name}'${NC}" + return 1 + fi +} + +# Function to get all postgres containers +get_postgres_containers() { + docker ps --format "{{.Names}}" | grep -E "(postgres-|fuzzilli-postgres-)(master|local-[0-9]+)" || true +} + +# Main execution +main() { + echo "==========================================" + echo " Clear PostgreSQL Databases" + echo "==========================================" + echo "" + + check_docker + + # Get all postgres containers + local containers=$(get_postgres_containers) + + if [ -z "$containers" ]; then + echo -e "${YELLOW}No PostgreSQL containers found.${NC}" + echo "Available containers:" + docker ps --format "table {{.Names}}\t{{.Status}}" + exit 0 + fi + + echo -e "${YELLOW}Found PostgreSQL containers:${NC}" + echo "$containers" | while read container; do + echo " - $container" + done + echo "" + + # Confirmation prompt (unless --force) + if [ "$FORCE" != "true" ]; then + echo -e "${RED}WARNING: This will DELETE ALL DATA from all PostgreSQL databases!${NC}" + echo -e "${YELLOW}This action cannot be undone.${NC}" + echo "" + read -p "Are you sure you want to continue? (yes/no): " confirm + if [ "$confirm" != "yes" ]; then + echo -e "${BLUE}Operation cancelled.${NC}" + exit 0 + fi + echo "" + fi + + # Clear master database + echo -e "${BLUE}Clearing master database...${NC}" + local master_container="" + while IFS= read -r line; do + if [[ "$line" =~ (postgres-master|fuzzilli-postgres-master) ]]; then + master_container="$line" + break + fi + done < <(docker ps --format "{{.Names}}") + + if [ -n "$master_container" ]; then + clear_database "$master_container" "fuzzilli_master" + else + echo -e "${YELLOW}Master database container not found. Skipping.${NC}" + fi + echo "" + + # Clear all local worker databases + echo -e "${BLUE}Clearing local worker databases...${NC}" + local cleared_count=0 + local failed_count=0 + + for container in $containers; do + if [[ "$container" =~ (postgres-local-|fuzzilli-postgres-local-)[0-9]+ ]]; then + if clear_database "$container" "fuzzilli_local"; then + cleared_count=$((cleared_count + 1)) + else + failed_count=$((failed_count + 1)) + fi + fi + done + + echo "" + echo "==========================================" + if [ $failed_count -eq 0 ]; then + echo -e "${GREEN}Database clearing complete!${NC}" + echo -e "${GREEN}Cleared: Master database + $cleared_count local database(s)${NC}" + else + echo -e "${YELLOW}Database clearing completed with some errors.${NC}" + echo -e "${YELLOW}Cleared: Master database + $cleared_count local database(s)${NC}" + echo -e "${RED}Failed: $failed_count database(s)${NC}" + fi + echo "==========================================" +} + +# Run main function +main + diff --git a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift index 667fdd66c..450b2d246 100644 --- a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift +++ b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift @@ -718,8 +718,15 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { /// Synchronize with PostgreSQL database private func syncWithDatabase() { + if enableLogging { + logger.info("syncWithDatabase() called - starting sync task") + } Task { - await performDatabaseSync() + do { + await performDatabaseSync() + } catch { + logger.error("syncWithDatabase() failed: \(error)") + } } } @@ -727,62 +734,181 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { private func performDatabaseSync() async { // Sync ALL programs from local database to master, not just from cache // This ensures the master has the complete corpus from this worker + if enableLogging { + logger.info("performDatabaseSync() started - masterStorage: \(masterStorage != nil), masterId: \(masterFuzzerId?.description ?? "nil"), localId: \(fuzzerId?.description ?? "nil")") + } guard let masterStorage = masterStorage, let masterId = masterFuzzerId, let localId = fuzzerId else { + if enableLogging { + logger.warning("performDatabaseSync() aborted - missing required values: masterStorage=\(masterStorage != nil), masterId=\(masterFuzzerId?.description ?? "nil"), localId=\(fuzzerId?.description ?? "nil")") + } return } do { - // Get all programs from local database (not just cache) - // Use a date far in the past to get all programs - let allTimeDate = Date(timeIntervalSince1970: 0) + // Get ALL programs from local database - no date filter, no limits + if enableLogging { + logger.info("Querying local database for ALL programs: fuzzerId=\(localId)") + } let localPrograms = try await storage.getRecentPrograms( fuzzerId: localId, - since: allTimeDate, + since: Date(timeIntervalSince1970: 0), // This will be ignored in the query now limit: nil // No limit - get all programs ) - guard !localPrograms.isEmpty else { + if enableLogging { + logger.info("Retrieved \(localPrograms.count) programs from local database") + } + + if localPrograms.isEmpty { if enableLogging { logger.info("No programs in local database to sync") } return } + // Deduplicate programs by hash (in case query returned duplicates) + var uniquePrograms: [String: (Program, ExecutionMetadata)] = [:] + for (program, metadata) in localPrograms { + let hash = DatabaseUtils.calculateProgramHash(program: program) + // Keep the first occurrence (or you could merge metadata) + if uniquePrograms[hash] == nil { + uniquePrograms[hash] = (program, metadata) + } + } + + let deduplicatedPrograms = Array(uniquePrograms.values) + if enableLogging { - logger.info("Syncing \(localPrograms.count) programs from local database to master") + logger.info("Deduplicated to \(deduplicatedPrograms.count) unique programs (from \(localPrograms.count) total)") } - // Store each program in the master database (filter out FUZZILLI_CRASH test cases) - var syncedCount = 0 - var skippedCount = 0 - for (program, metadata) in localPrograms { - // Skip test programs with FUZZILLI_CRASH - if DatabaseUtils.containsFuzzilliCrash(program: program) { - skippedCount += 1 - continue - } + // Store ALL programs in the master database using BATCH storage + if enableLogging { + logger.info("Syncing \(deduplicatedPrograms.count) unique programs from local database to master database (fuzzerId=\(masterId))") + } + + do { + // Use batch storage for better performance + let programHashes = try await masterStorage.storeProgramsBatch( + programs: deduplicatedPrograms, + fuzzerId: masterId + ) - do { - _ = try await masterStorage.storeProgram( - program: program, - fuzzerId: masterId, - metadata: metadata - ) - syncedCount += 1 - } catch { - if enableLogging { - logger.error("Failed to sync program to master: \(error)") + if enableLogging { + logger.info("✅ Successfully batch synced \(programHashes.count) programs to master database (fuzzerId=\(masterId))") + if programHashes.count > 0 { + logger.info(" Sample program hashes: \(programHashes.prefix(5).joined(separator: ", "))\(programHashes.count > 5 ? "..." : "")") + } + } + } catch { + logger.error("❌ Failed to batch sync programs to master: \(error)") + // Fallback to individual storage if batch fails + if enableLogging { + logger.info("Attempting individual program storage as fallback...") + } + var syncedCount = 0 + var failedCount = 0 + for (program, metadata) in deduplicatedPrograms { + do { + _ = try await masterStorage.storeProgram( + program: program, + fuzzerId: masterId, + metadata: metadata + ) + syncedCount += 1 + } catch { + failedCount += 1 + if enableLogging { + logger.error("Failed to sync individual program to master: \(error)") + } } } + if enableLogging { + logger.info("Fallback sync completed: \(syncedCount) succeeded, \(failedCount) failed") + } + // Re-throw if all failed + if syncedCount == 0 && failedCount > 0 { + throw error + } } - if skippedCount > 0 && enableLogging { - logger.info("Skipped \(skippedCount) test programs with FUZZILLI_CRASH during sync") + // TODO: Execution sync temporarily disabled - need to implement getRecentExecutions method + // Sync ALL executions from local database to master - no date filter, no limits + // For now, skip execution sync and focus on program sync + if enableLogging { + logger.info("Execution sync temporarily disabled - focusing on program sync") } - if enableLogging { - logger.info("Successfully synced \(syncedCount)/\(localPrograms.count) programs to master") + /* Execution sync code - temporarily disabled + let localExecutions = try await storage.getRecentExecutions( + fuzzerId: localId, + since: Date(timeIntervalSince1970: 0), + limit: nil + ) + + if localExecutions.isEmpty { + if enableLogging { + logger.info("No executions in local database to sync") + } + } else { + if enableLogging { + logger.info("Syncing \(localExecutions.count) executions from local database to master") + } + + // Convert ALL executions to batch data - NO FILTERING + var executionBatchData: [ExecutionBatchData] = [] + + for executionWithProgram in localExecutions { + // NO FILTERING - sync ALL executions including FUZZILLI_CRASH + + // Convert to ExecutionBatchData for batch storage + executionBatchData.append(executionWithProgram.toExecutionBatchData()) + } + + // Batch store executions in master database with original fuzzer_id + if !executionBatchData.isEmpty { + do { + // Store programs first (in case they don't exist in master) + var uniquePrograms: [String: (Program, ExecutionMetadata)] = [:] + for execData in executionBatchData { + let programHash = DatabaseUtils.calculateProgramHash(program: execData.program) + if uniquePrograms[programHash] == nil { + let metadata = ExecutionMetadata(lastOutcome: DatabaseExecutionOutcome( + id: DatabaseUtils.mapExecutionOutcome(outcome: execData.outcome), + outcome: execData.outcome.description, + description: execData.outcome.description + )) + uniquePrograms[programHash] = (execData.program, metadata) + } + } + + // Store unique programs in master using batch storage + let programBatch = Array(uniquePrograms.values) + if !programBatch.isEmpty { + if enableLogging { + logger.info("Storing \(programBatch.count) unique programs in master before syncing executions") + } + let programHashes = try await masterStorage.storeProgramsBatch(programs: programBatch, fuzzerId: masterId) + if enableLogging { + logger.info("✅ Stored \(programHashes.count) programs in master database") + } + } + + // Store executions in master with original fuzzer_id using batch storage + if enableLogging { + logger.info("Storing \(executionBatchData.count) executions in master database (fuzzerId=\(masterId))") + } + let executionIds = try await masterStorage.storeExecutionsBatch(executions: executionBatchData, fuzzerId: masterId) + + if enableLogging { + logger.info("✅ Successfully batch synced \(executionIds.count) executions to master database") + } + } catch { + logger.error("Failed to sync executions to master: \(error)") + } + } } + */ // Clear pending sync operations since we've synced everything _ = withLock(syncLock) { @@ -790,14 +916,18 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } } catch { - logger.error("Failed to sync programs to master: \(error)") + logger.error("Failed to sync to master: \(error)") } } /// Pull sync from master database private func pullFromMaster() { Task { - await performPullFromMaster() + do { + await performPullFromMaster() + } catch { + logger.error("pullFromMaster() failed: \(error)") + } } } @@ -812,86 +942,236 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { // Get ALL programs from master database (from all fuzzers) // Use a date far in the past to get all programs let sinceDate = Date(timeIntervalSince1970: 0) // Get all programs from the beginning + if enableLogging { + logger.info("performPullFromMaster() started - fetching all programs from master...") + } let masterPrograms = try await masterStorage.getAllPrograms( since: sinceDate ) + if enableLogging { + logger.info("Retrieved \(masterPrograms.count) programs from master database") + } + guard !masterPrograms.isEmpty else { if enableLogging { - logger.info("No new programs to pull from master") + logger.info("No programs to pull from master") } return } - // Filter out programs already in local cache - let localHashes = withLock(cacheLock) { - Set(programCache.keys) + // Pull ALL programs from master - NO filtering by cache or FUZZILLI_CRASH + if enableLogging { + logger.info("Pulling ALL \(masterPrograms.count) programs from master (no filtering)") } - let newPrograms = masterPrograms.filter { program, _ in - let hash = DatabaseUtils.calculateProgramHash(program: program) - return !localHashes.contains(hash) + // Add ALL programs to local cache and local postgres - NO FILTERING + // Use batch storage for better performance when pulling many programs + var pulledCount = 0 + var failedCount = 0 + + // Check if we already have these programs to avoid duplicates + // Check both cache and local database + var programsToPull: [(Program, ExecutionMetadata)] = [] + var existingHashes = Set() + + // Get existing hashes from cache + withLock(cacheLock) { + existingHashes = Set(programCache.keys) } - guard !newPrograms.isEmpty else { + // Also check local database for existing programs (check ALL programs, not just this fuzzer's) + do { + // Get ALL programs from local database regardless of fuzzer_id + let localPrograms = try await storage.getRecentPrograms( + fuzzerId: nil, // Get all programs, not just this fuzzer's + since: Date(timeIntervalSince1970: 0), + limit: nil + ) + for (program, _) in localPrograms { + let hash = DatabaseUtils.calculateProgramHash(program: program) + existingHashes.insert(hash) + } if enableLogging { - logger.info("All programs from master already in local cache") + logger.info("Found \(existingHashes.count) existing program hashes in local database and cache") + } + } catch { + if enableLogging { + logger.warning("Failed to check local database for existing programs: \(error)") + } + } + + // Filter out programs we already have + for (program, metadata) in masterPrograms { + let hash = DatabaseUtils.calculateProgramHash(program: program) + if !existingHashes.contains(hash) { + programsToPull.append((program, metadata)) } - return } if enableLogging { - logger.info("Pulling \(newPrograms.count) new programs from master") + logger.info("Filtered \(programsToPull.count) new programs to pull (out of \(masterPrograms.count) total, \(masterPrograms.count - programsToPull.count) already exist)") } - // Add new programs to local cache and local postgres (filter out FUZZILLI_CRASH test cases) - var pulledCount = 0 - var skippedCount = 0 - for (program, metadata) in newPrograms { - // Skip test programs with FUZZILLI_CRASH - if DatabaseUtils.containsFuzzilliCrash(program: program) { - skippedCount += 1 - continue - } - - let hash = DatabaseUtils.calculateProgramHash(program: program) - - // Add to local cache - withLock(cacheLock) { - programCache[hash] = (program: program, metadata: metadata) + // Use batch storage for better performance + // Process in chunks to avoid memory issues with very large datasets + let chunkSize = 10000 // Process 10k programs at a time + if !programsToPull.isEmpty { + if enableLogging { + logger.info("Processing \(programsToPull.count) programs to pull in chunks of \(chunkSize)...") } - // Add to local postgres using local fuzzer ID - do { - _ = try await storage.storeProgram( - program: program, - fuzzerId: localId, - metadata: metadata - ) + for chunkStart in stride(from: 0, to: programsToPull.count, by: chunkSize) { + let chunkEnd = min(chunkStart + chunkSize, programsToPull.count) + let chunk = Array(programsToPull[chunkStart..= 4 else { continue } - let programHash = String(components[0].dropFirst().dropLast()) // Remove quotes - let fuzzerId = String(components[1].trimmingCharacters(in: .whitespaces)) - let programSize = String(components[2].trimmingCharacters(in: .whitespaces)) - let programBase64 = String(components[3].dropFirst().dropLast()) // Remove quotes - - // Try UPDATE first - let updateQuery = PostgresQuery(stringLiteral: """ - UPDATE program SET - fuzzer_id = \(fuzzerId), - program_size = \(programSize), - program_base64 = '\(programBase64)' - WHERE program_hash = '\(programHash)' - """) - let updateResult = try await connection.query(updateQuery, logger: self.logger) - let updateRows = try await updateResult.collect() + // Batch insert into fuzzer table (corpus) using parameterized queries + if !fuzzerBatchData.isEmpty { + // Use a transaction for better performance + try await connection.query("BEGIN", logger: self.logger) + + do { + // Batch insert into fuzzer table + for (programHash, fuzzerId, programSize, programBase64) in fuzzerBatchData { + // Escape single quotes in base64 string + let escapedProgramBase64 = programBase64.replacingOccurrences(of: "'", with: "''") + + let fuzzerQuery = PostgresQuery(stringLiteral: """ + INSERT INTO fuzzer (program_hash, fuzzer_id, program_size, program_base64) + VALUES ('\(programHash)', \(fuzzerId), \(programSize), '\(escapedProgramBase64)') + ON CONFLICT (program_hash) DO UPDATE SET + fuzzer_id = EXCLUDED.fuzzer_id, + program_size = EXCLUDED.program_size, + program_base64 = EXCLUDED.program_base64 + """) + try await connection.query(fuzzerQuery, logger: self.logger) + } - // If no rows were updated, insert the new program - if updateRows.isEmpty { - let insertQuery = PostgresQuery(stringLiteral: """ + // Batch insert/update into program table + for (programHash, fuzzerId, programSize, programBase64) in fuzzerBatchData { + // Escape single quotes in base64 string + let escapedProgramBase64 = programBase64.replacingOccurrences(of: "'", with: "''") + + // Use INSERT ... ON CONFLICT for atomic upsert + let programQuery = PostgresQuery(stringLiteral: """ INSERT INTO program (program_hash, fuzzer_id, program_size, program_base64) - VALUES ('\(programHash)', \(fuzzerId), \(programSize), '\(programBase64)') - ON CONFLICT DO NOTHING + VALUES ('\(programHash)', \(fuzzerId), \(programSize), '\(escapedProgramBase64)') + ON CONFLICT (program_hash) DO UPDATE SET + fuzzer_id = EXCLUDED.fuzzer_id, + program_size = EXCLUDED.program_size, + program_base64 = EXCLUDED.program_base64 """) - try await connection.query(insertQuery, logger: self.logger) + try await connection.query(programQuery, logger: self.logger) + } + + // Commit transaction + try await connection.query("COMMIT", logger: self.logger) + + if enableLogging { + logger.info("Successfully batch stored \(programHashes.count) programs in database") } + } catch { + // Rollback on error + try? await connection.query("ROLLBACK", logger: self.logger) + throw error } } @@ -748,28 +752,36 @@ public class PostgreSQLStorage { let connection = try await createDirectConnection() defer { Task { _ = try? await connection.close() } } - // Query for recent programs with their latest execution metadata - let queryString = """ - SELECT DISTINCT ON (p.program_hash) - p.program_hash, - p.program_size, - p.program_base64, - p.created_at, - eo.outcome, - eo.description, - e.execution_time_ms, - e.coverage_total, - e.signal_code, - e.exit_code - FROM program p - LEFT JOIN execution e ON p.program_hash = e.program_hash + // Query for ALL programs from corpus (fuzzer table) - no date filter, no limits + // Use DISTINCT ON to get one row per program (in case of multiple executions) + // Use explicit type casts to ensure correct decoding + var queryString = """ + SELECT DISTINCT ON (f.program_hash) + f.program_hash::text, + COALESCE(f.program_size, 0)::integer as program_size, + f.program_base64::text, + f.inserted_at::timestamp, + eo.outcome::text, + eo.description::text, + e.execution_time_ms::integer, + e.coverage_total::double precision, + e.signal_code::integer, + e.exit_code::integer + FROM fuzzer f + LEFT JOIN execution e ON f.program_hash = e.program_hash LEFT JOIN execution_outcome eo ON e.execution_outcome_id = eo.id - WHERE \(fuzzerId != nil ? "p.fuzzer_id = \(fuzzerId!) AND " : "") - p.created_at >= '\(since.ISO8601Format())' - ORDER BY p.program_hash, p.created_at DESC, e.created_at DESC NULLS LAST - \(limit != nil ? "LIMIT \(limit!)" : "") """ + // Only filter by fuzzer_id if specified, otherwise get ALL programs + if let fuzzerId = fuzzerId { + queryString += " WHERE f.fuzzer_id = \(fuzzerId)" + } + + queryString += " ORDER BY f.program_hash, e.execution_id DESC NULLS LAST" + + // Never apply limit - get ALL programs + // \(limit != nil ? "LIMIT \(limit!)" : "") + let query = PostgresQuery(stringLiteral: queryString) let result = try await connection.query(query, logger: self.logger) let rows = try await result.collect() @@ -790,20 +802,22 @@ public class PostgreSQLStorage { let exitCode: Int? do { - // Decode required fields (these should never be NULL) - programHash = try row.decode(String.self, context: PostgresDecodingContext.default) - programSize = try row.decode(Int.self, context: PostgresDecodingContext.default) - programBase64 = try row.decode(String.self, context: PostgresDecodingContext.default) - createdAt = try row.decode(Date.self, context: PostgresDecodingContext.default) + // Use random access row to decode columns by index explicitly to avoid PostgresNIO decode issues + let randomAccessRow = row.makeRandomAccess() + + // Decode required fields by index (0, 1, 2, 3) + programHash = try randomAccessRow[0].decode(String.self, context: PostgresDecodingContext.default) + programSize = try randomAccessRow[1].decode(Int.self, context: PostgresDecodingContext.default) + programBase64 = try randomAccessRow[2].decode(String.self, context: PostgresDecodingContext.default) + createdAt = try randomAccessRow[3].decode(Date.self, context: PostgresDecodingContext.default) - // Decode optional fields (these can be NULL from LEFT JOIN) - // PostgresNIO handles NULL values when decoding to Optional types - outcome = try row.decode(String?.self, context: PostgresDecodingContext.default) - description = try row.decode(String?.self, context: PostgresDecodingContext.default) - executionTimeMs = try row.decode(Int?.self, context: PostgresDecodingContext.default) - coverageTotal = try row.decode(Double?.self, context: PostgresDecodingContext.default) - signalCode = try row.decode(Int?.self, context: PostgresDecodingContext.default) - exitCode = try row.decode(Int?.self, context: PostgresDecodingContext.default) + // Decode optional fields by index (4, 5, 6, 7, 8, 9) - these can be NULL from LEFT JOIN + outcome = try randomAccessRow[4].decode(String?.self, context: PostgresDecodingContext.default) + description = try randomAccessRow[5].decode(String?.self, context: PostgresDecodingContext.default) + executionTimeMs = try randomAccessRow[6].decode(Int?.self, context: PostgresDecodingContext.default) + coverageTotal = try randomAccessRow[7].decode(Double?.self, context: PostgresDecodingContext.default) + signalCode = try randomAccessRow[8].decode(Int?.self, context: PostgresDecodingContext.default) + exitCode = try randomAccessRow[9].decode(Int?.self, context: PostgresDecodingContext.default) } catch { if enableLogging { logger.warning("Failed to decode row: \(String(reflecting: error)). Skipping this program.") @@ -811,9 +825,11 @@ public class PostgreSQLStorage { continue } - // Decode the program from base64 + // Decode the program from base64 - skip if it fails but continue with other programs guard let programData = Data(base64Encoded: programBase64) else { - logger.warning("Failed to decode base64 data for program: \(programHash)") + if enableLogging { + logger.warning("Skipping program \(programHash): Failed to decode base64 data") + } continue } @@ -822,7 +838,11 @@ public class PostgreSQLStorage { let protobuf = try Fuzzilli_Protobuf_Program(serializedBytes: programData) program = try Program(from: protobuf) } catch { - logger.warning("Failed to decode program from protobuf: \(programHash), error: \(error)") + // Skip programs that fail to decode from protobuf - they may be from incompatible versions + // Continue syncing other programs that can be decoded + if enableLogging { + logger.debug("Skipping program \(programHash): Failed to decode from protobuf (likely incompatible format): \(error)") + } continue } diff --git a/docker-compose.workers.yml b/docker-compose.workers.yml index d96220385..dbca254cc 100644 --- a/docker-compose.workers.yml +++ b/docker-compose.workers.yml @@ -185,250 +185,6 @@ services: memory: 1G - # Worker 4 - Local Postgres - postgres-local-4: - image: postgres:15-alpine - container_name: postgres-local-4 - environment: - POSTGRES_DB: fuzzilli_local - POSTGRES_USER: fuzzilli - POSTGRES_PASSWORD: fuzzilli123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_local_data_4:/var/lib/postgresql/data - - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql - healthcheck: - test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - networks: - - fuzzing-network - - # Worker 4 - Fuzzilli Container - fuzzer-worker-4: - build: - context: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai - dockerfile: Cloud/VRIG/Dockerfile.distributed - container_name: fuzzer-worker-4 - environment: - - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master - - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-4:5432/fuzzilli_local - - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-4:5432/fuzzilli_local - - FUZZER_INSTANCE_NAME=fuzzer-4 - - SYNC_INTERVAL=300 - - TIMEOUT=2500 - - MIN_MUTATIONS_PER_SAMPLE=25 - - DEBUG_LOGGING=false - depends_on: - postgres-master: - condition: service_healthy - postgres-local-4: - condition: service_healthy - volumes: - - fuzzer_data_4:/home/app/Corpus - - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro - restart: unless-stopped - healthcheck: - test: ["CMD-SHELL", "pgrep -f FuzzilliCli || exit 1"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 60s - networks: - - fuzzing-network - deploy: - resources: - limits: - memory: 2G - reservations: - memory: 1G - - - # Worker 5 - Local Postgres - postgres-local-5: - image: postgres:15-alpine - container_name: postgres-local-5 - environment: - POSTGRES_DB: fuzzilli_local - POSTGRES_USER: fuzzilli - POSTGRES_PASSWORD: fuzzilli123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_local_data_5:/var/lib/postgresql/data - - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql - healthcheck: - test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - networks: - - fuzzing-network - - # Worker 5 - Fuzzilli Container - fuzzer-worker-5: - build: - context: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai - dockerfile: Cloud/VRIG/Dockerfile.distributed - container_name: fuzzer-worker-5 - environment: - - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master - - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-5:5432/fuzzilli_local - - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-5:5432/fuzzilli_local - - FUZZER_INSTANCE_NAME=fuzzer-5 - - SYNC_INTERVAL=300 - - TIMEOUT=2500 - - MIN_MUTATIONS_PER_SAMPLE=25 - - DEBUG_LOGGING=false - depends_on: - postgres-master: - condition: service_healthy - postgres-local-5: - condition: service_healthy - volumes: - - fuzzer_data_5:/home/app/Corpus - - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro - restart: unless-stopped - healthcheck: - test: ["CMD-SHELL", "pgrep -f FuzzilliCli || exit 1"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 60s - networks: - - fuzzing-network - deploy: - resources: - limits: - memory: 2G - reservations: - memory: 1G - - - # Worker 6 - Local Postgres - postgres-local-6: - image: postgres:15-alpine - container_name: postgres-local-6 - environment: - POSTGRES_DB: fuzzilli_local - POSTGRES_USER: fuzzilli - POSTGRES_PASSWORD: fuzzilli123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_local_data_6:/var/lib/postgresql/data - - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql - healthcheck: - test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - networks: - - fuzzing-network - - # Worker 6 - Fuzzilli Container - fuzzer-worker-6: - build: - context: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai - dockerfile: Cloud/VRIG/Dockerfile.distributed - container_name: fuzzer-worker-6 - environment: - - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master - - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-6:5432/fuzzilli_local - - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-6:5432/fuzzilli_local - - FUZZER_INSTANCE_NAME=fuzzer-6 - - SYNC_INTERVAL=300 - - TIMEOUT=2500 - - MIN_MUTATIONS_PER_SAMPLE=25 - - DEBUG_LOGGING=false - depends_on: - postgres-master: - condition: service_healthy - postgres-local-6: - condition: service_healthy - volumes: - - fuzzer_data_6:/home/app/Corpus - - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro - restart: unless-stopped - healthcheck: - test: ["CMD-SHELL", "pgrep -f FuzzilliCli || exit 1"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 60s - networks: - - fuzzing-network - deploy: - resources: - limits: - memory: 2G - reservations: - memory: 1G - - - # Worker 7 - Local Postgres - postgres-local-7: - image: postgres:15-alpine - container_name: postgres-local-7 - environment: - POSTGRES_DB: fuzzilli_local - POSTGRES_USER: fuzzilli - POSTGRES_PASSWORD: fuzzilli123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_local_data_7:/var/lib/postgresql/data - - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql - healthcheck: - test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - networks: - - fuzzing-network - - # Worker 7 - Fuzzilli Container - fuzzer-worker-7: - build: - context: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai - dockerfile: Cloud/VRIG/Dockerfile.distributed - container_name: fuzzer-worker-7 - environment: - - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master - - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-7:5432/fuzzilli_local - - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-7:5432/fuzzilli_local - - FUZZER_INSTANCE_NAME=fuzzer-7 - - SYNC_INTERVAL=300 - - TIMEOUT=2500 - - MIN_MUTATIONS_PER_SAMPLE=25 - - DEBUG_LOGGING=false - depends_on: - postgres-master: - condition: service_healthy - postgres-local-7: - condition: service_healthy - volumes: - - fuzzer_data_7:/home/app/Corpus - - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro - restart: unless-stopped - healthcheck: - test: ["CMD-SHELL", "pgrep -f FuzzilliCli || exit 1"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 60s - networks: - - fuzzing-network - deploy: - resources: - limits: - memory: 2G - reservations: - memory: 1G - - volumes: postgres_local_data_1: fuzzer_data_1: @@ -436,11 +192,3 @@ volumes: fuzzer_data_2: postgres_local_data_3: fuzzer_data_3: - postgres_local_data_4: - fuzzer_data_4: - postgres_local_data_5: - fuzzer_data_5: - postgres_local_data_6: - fuzzer_data_6: - postgres_local_data_7: - fuzzer_data_7: diff --git a/env.distributed b/env.distributed index 7222c57a4..673a67486 100644 --- a/env.distributed +++ b/env.distributed @@ -6,7 +6,7 @@ POSTGRES_PASSWORD=fuzzilli123 # Fuzzer Configuration FUZZER_COUNT=3 -SYNC_INTERVAL=300 +SYNC_INTERVAL=5 TIMEOUT=2500 MIN_MUTATIONS_PER_SAMPLE=25 From 01274004af169cb0e9f4543bc09b500aded25e0c Mon Sep 17 00:00:00 2001 From: Oleg Lazari Date: Tue, 11 Nov 2025 00:31:09 -0500 Subject: [PATCH 23/23] removed all deadcode, added useful scripts --- Cloud/VRIG/Dockerfile.distributed | 26 +- Scripts/RunFuzzilli.sh | 28 - Scripts/SetupPostgres.sh | 103 -- Scripts/benchmark-server.sh | 145 +++ Scripts/cleanup-docker.sh | 45 - Scripts/clear-postgres-db.sh | 173 --- Scripts/extract_crashes.sh | 128 --- Scripts/fuzzer-stats.sh | 203 ++++ Scripts/monitor-performance.sh | 212 ++++ Scripts/{DBQuery.sh => query-db.sh} | 41 +- Scripts/query_db.sh | 274 ----- Scripts/scale-workers.sh | 119 ++ Scripts/show-crash-javascript.sh | 148 --- Scripts/show-stats.sh | 180 --- Scripts/start-distributed.sh | 55 +- Scripts/test-distributed-sync.sh | 313 ----- .../Fuzzilli/Corpus/PostgreSQLCorpus.swift | 1018 ++++------------- Sources/Fuzzilli/Database/DatabasePool.swift | 65 -- .../Fuzzilli/Database/DatabaseSchema.swift | 68 +- .../Fuzzilli/Database/PostgreSQLStorage.swift | 282 +---- Sources/FuzzilliCli/main.swift | 55 +- docker-compose.workers.yml | 121 +- 22 files changed, 987 insertions(+), 2815 deletions(-) delete mode 100755 Scripts/RunFuzzilli.sh delete mode 100755 Scripts/SetupPostgres.sh create mode 100755 Scripts/benchmark-server.sh delete mode 100755 Scripts/cleanup-docker.sh delete mode 100755 Scripts/clear-postgres-db.sh delete mode 100755 Scripts/extract_crashes.sh create mode 100755 Scripts/fuzzer-stats.sh create mode 100755 Scripts/monitor-performance.sh rename Scripts/{DBQuery.sh => query-db.sh} (83%) delete mode 100755 Scripts/query_db.sh create mode 100755 Scripts/scale-workers.sh delete mode 100755 Scripts/show-crash-javascript.sh delete mode 100755 Scripts/show-stats.sh delete mode 100755 Scripts/test-distributed-sync.sh diff --git a/Cloud/VRIG/Dockerfile.distributed b/Cloud/VRIG/Dockerfile.distributed index 3c5ff71c1..88404ec12 100644 --- a/Cloud/VRIG/Dockerfile.distributed +++ b/Cloud/VRIG/Dockerfile.distributed @@ -43,30 +43,16 @@ RUN mkdir -p ./fuzzbuild RUN mkdir -p ./Corpus # Environment variables for distributed fuzzing -ENV MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli ENV POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master ENV FUZZER_INSTANCE_NAME=fuzzer-default -ENV SYNC_INTERVAL=5 ENV TIMEOUT=2500 ENV MIN_MUTATIONS_PER_SAMPLE=25 ENV DEBUG_LOGGING=false # Default command for distributed fuzzing -# Use dual database mode if both LOCAL_POSTGRES_URL and MASTER_POSTGRES_URL are set -# Otherwise fall back to single database mode with POSTGRES_URL -CMD if [ -n "$LOCAL_POSTGRES_URL" ] && [ -n "$MASTER_POSTGRES_URL" ]; then \ - ./FuzzilliCli --profile=v8debug --engine=multi --resume --corpus=postgresql \ - --local-postgres-url="${LOCAL_POSTGRES_URL}" \ - --master-postgres-url="${MASTER_POSTGRES_URL}" \ - --sync-interval="${SYNC_INTERVAL}" \ - --timeout="${TIMEOUT}" \ - --minMutationsPerSample="${MIN_MUTATIONS_PER_SAMPLE}" \ - --postgres-logging ./fuzzbuild/d8; \ - else \ - ./FuzzilliCli --profile=v8debug --engine=multi --resume --corpus=postgresql \ - --postgres-url="${POSTGRES_URL}" \ - --sync-interval="${SYNC_INTERVAL}" \ - --timeout="${TIMEOUT}" \ - --minMutationsPerSample="${MIN_MUTATIONS_PER_SAMPLE}" \ - --postgres-logging ./fuzzbuild/d8; \ - fi +# Single master database mode - all workers connect directly to master +CMD ./FuzzilliCli --profile=v8debug --engine=multi --resume --corpus=postgresql \ + --postgres-url="${POSTGRES_URL}" \ + --timeout="${TIMEOUT}" \ + --minMutationsPerSample="${MIN_MUTATIONS_PER_SAMPLE}" \ + --postgres-logging ./fuzzbuild/d8 diff --git a/Scripts/RunFuzzilli.sh b/Scripts/RunFuzzilli.sh deleted file mode 100755 index aab301b9a..000000000 --- a/Scripts/RunFuzzilli.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh - -set -e - -if [ -z "$1" ] || [ ! -f "$1" ]; then - echo "Error: Please provide the path to the 'd8' binary as the first argument." - echo "Usage: $0 /path/to/d8 [db-log]" - echo " /path/to/d8 : Path to the d8 binary" - echo " db-log : Optional flag to enable PostgreSQL database logging" - exit 1 -fi - -POSTGRES_LOGGING_FLAG="" -if [ "$2" = "db-log" ]; then - POSTGRES_LOGGING_FLAG="--postgres-logging" -fi - -swift run FuzzilliCli \ - --profile=v8 \ - --engine=multi \ - --resume \ - --corpus=postgresql \ - --postgres-url="postgresql://fuzzilli:fuzzilli123@localhost:5433/fuzzilli" \ - --storagePath=./Corpus \ - --logLevel=verbose \ - --timeout=3000 \ - $POSTGRES_LOGGING_FLAG \ - --diagnostics "$1" diff --git a/Scripts/SetupPostgres.sh b/Scripts/SetupPostgres.sh deleted file mode 100755 index d742c0000..000000000 --- a/Scripts/SetupPostgres.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/bin/bash - -# Setup PostgreSQL for Fuzzilli testing -set -e - -echo "=== Fuzzilli PostgreSQL Setup ===" - -# Detect container runtime -if command -v docker &> /dev/null; then - CONTAINER_RUNTIME="docker" - echo "Using Docker" -elif command -v podman &> /dev/null; then - CONTAINER_RUNTIME="podman" - echo "Using Podman" -else - echo "Error: Neither docker-compose nor podman is available" - echo "Please install docker-compose or podman to continue" - exit 1 -fi - -# Detect compose command -if command -v docker &> /dev/null; then - COMPOSE_CMD="docker compose" -elif command -v podman-compose &> /dev/null; then - COMPOSE_CMD="podman-compose" -else - echo "Error: No compose command found, do you have docker-compose or po installed?" - exit 1 -fi - -# Check if container runtime is accessible -if ! $CONTAINER_RUNTIME info &> /dev/null; then - echo "Error: $CONTAINER_RUNTIME is not accessible" - echo "Please ensure $CONTAINER_RUNTIME is running and try again" - exit 1 -fi - -echo "Starting PostgreSQL container..." -$COMPOSE_CMD up -d postgres - -echo "Waiting for PostgreSQL to be ready..." -timeout=60 -counter=0 -while ! $COMPOSE_CMD exec postgres pg_isready -U fuzzilli -d fuzzilli &> /dev/null; do - if [ $counter -ge $timeout ]; then - echo "Error: PostgreSQL failed to start within $timeout seconds" - $COMPOSE_CMD logs postgres - exit 1 - fi - echo "Waiting for PostgreSQL... ($counter/$timeout)" - sleep 2 - counter=$((counter + 2)) -done - -echo "PostgreSQL is ready!" - -# Test connection -echo "Testing database connection..." -$COMPOSE_CMD exec postgres psql -U fuzzilli -d fuzzilli -c "SELECT version();" - -echo "Checking if tables exist..." -$COMPOSE_CMD exec postgres psql -U fuzzilli -d fuzzilli -c " -SELECT table_name -FROM information_schema.tables -WHERE table_schema = 'public' -ORDER BY table_name; -" - -echo "Checking execution types..." -$COMPOSE_CMD exec postgres psql -U fuzzilli -d fuzzilli -c " -SELECT id, title, description -FROM execution_type -ORDER BY id; -" - -echo "Checking mutator types..." -$COMPOSE_CMD exec postgres psql -U fuzzilli -d fuzzilli -c " -SELECT id, name, category -FROM mutator_type -ORDER BY id; -" - -echo "Checking execution outcomes..." -$COMPOSE_CMD exec postgres psql -U fuzzilli -d fuzzilli -c " -SELECT id, outcome, description -FROM execution_outcome -ORDER BY id; -" - -echo "" -echo "=== PostgreSQL Setup Complete ===" -echo "Connection string: postgresql://fuzzilli:fuzzilli123@localhost:5433/fuzzilli" -echo "" -echo "To start pgAdmin (optional):" -echo " $COMPOSE_CMD up -d pgadmin" -echo " Open http://localhost:8080" -echo " Login: admin@fuzzilli.local / admin123" -echo "" -echo "To stop PostgreSQL:" -echo " $COMPOSE_CMD down" -echo "" -echo "To view logs:" -echo " $COMPOSE_CMD logs postgres" diff --git a/Scripts/benchmark-server.sh b/Scripts/benchmark-server.sh new file mode 100755 index 000000000..5362476ed --- /dev/null +++ b/Scripts/benchmark-server.sh @@ -0,0 +1,145 @@ +#!/bin/bash + +# benchmark-server.sh - Benchmark server to determine optimal worker count +# Usage: ./Scripts/benchmark-server.sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +echo -e "${CYAN}========================================${NC}" +echo -e "${CYAN} Server Performance Benchmark${NC}" +echo -e "${CYAN}========================================${NC}" +echo "" + +# Get system specs +echo -e "${BLUE}=== System Specifications ===${NC}" +CPU_CORES=$(nproc) +TOTAL_MEM=$(free -h | grep Mem | awk '{print $2}') +AVAIL_MEM=$(free -h | grep Mem | awk '{print $7}') +DISK_SPACE=$(df -h / | tail -1 | awk '{print $4}') + +echo -e "CPU Cores: ${GREEN}${CPU_CORES}${NC}" +echo -e "Total Memory: ${GREEN}${TOTAL_MEM}${NC}" +echo -e "Available Memory: ${GREEN}${AVAIL_MEM}${NC}" +echo -e "Disk Space: ${GREEN}${DISK_SPACE}${NC}" +echo "" + +# Estimate resource usage per worker +echo -e "${BLUE}=== Resource Estimation ===${NC}" +echo "Estimating resource usage per fuzzer worker..." +echo "" + +# Typical resource usage per worker (can be adjusted based on observations) +ESTIMATED_CPU_PER_WORKER=15 # percentage +ESTIMATED_MEM_PER_WORKER=512 # MB +ESTIMATED_DB_CONNECTIONS_PER_WORKER=5 + +# Calculate recommendations +MAX_WORKERS_BY_CPU=$((CPU_CORES * 100 / ESTIMATED_CPU_PER_WORKER)) +AVAIL_MEM_MB=$(free -m | grep Mem | awk '{print $7}') +MAX_WORKERS_BY_MEM=$((AVAIL_MEM_MB / ESTIMATED_MEM_PER_WORKER)) +MAX_DB_CONNECTIONS=100 # PostgreSQL default max_connections +MAX_WORKERS_BY_DB=$((MAX_DB_CONNECTIONS / ESTIMATED_DB_CONNECTIONS_PER_WORKER)) + +# Conservative estimate (use minimum) +RECOMMENDED_WORKERS=$((MAX_WORKERS_BY_CPU < MAX_WORKERS_BY_MEM ? MAX_WORKERS_BY_CPU : MAX_WORKERS_BY_MEM)) +RECOMMENDED_WORKERS=$((RECOMMENDED_WORKERS < MAX_WORKERS_BY_DB ? RECOMMENDED_WORKERS : MAX_WORKERS_BY_DB)) + +# Apply safety margin (80% of calculated) +RECOMMENDED_WORKERS=$((RECOMMENDED_WORKERS * 80 / 100)) +RECOMMENDED_WORKERS=$((RECOMMENDED_WORKERS > 1 ? RECOMMENDED_WORKERS : 1)) + +echo -e "Estimated CPU per worker: ${YELLOW}${ESTIMATED_CPU_PER_WORKER}%${NC}" +echo -e "Estimated Memory per worker: ${YELLOW}${ESTIMATED_MEM_PER_WORKER}MB${NC}" +echo -e "Estimated DB conns per worker: ${YELLOW}${ESTIMATED_DB_CONNECTIONS_PER_WORKER}${NC}" +echo "" + +echo -e "${BLUE}=== Capacity Analysis ===${NC}" +echo -e "Max workers (CPU): ${CYAN}${MAX_WORKERS_BY_CPU}${NC}" +echo -e "Max workers (Memory): ${CYAN}${MAX_WORKERS_BY_MEM}${NC}" +echo -e "Max workers (DB): ${CYAN}${MAX_WORKERS_BY_DB}${NC}" +echo "" + +echo -e "${GREEN}=== Recommended Configuration ===${NC}" +echo -e "Recommended Workers: ${GREEN}${RECOMMENDED_WORKERS}${NC}" +echo "" + +# Performance test with current workers +if docker ps --format "{{.Names}}" | grep -q "fuzzer-worker"; then + echo -e "${BLUE}=== Current Performance Test ===${NC}" + echo "Testing current worker performance..." + echo "" + + # Get current worker count + CURRENT_WORKERS=$(docker ps --format "{{.Names}}" | grep -c "fuzzer-worker" || echo "0") + echo -e "Current Workers: ${CYAN}${CURRENT_WORKERS}${NC}" + + # Monitor for 30 seconds + echo "Monitoring for 30 seconds..." + START_TIME=$(date +%s) + + # Get initial stats + if docker ps --format "{{.Names}}" | grep -q "fuzzilli-postgres-master"; then + DB_CONTAINER="fuzzilli-postgres-master" + DB_NAME="fuzzilli_master" + DB_USER="fuzzilli" + + INITIAL_EXECS=$(docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -t -A -c " + SELECT COUNT(*) FROM execution WHERE created_at > NOW() - INTERVAL '1 minute'; + " 2>/dev/null || echo "0") + + sleep 30 + + FINAL_EXECS=$(docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -t -A -c " + SELECT COUNT(*) FROM execution WHERE created_at > NOW() - INTERVAL '1 minute'; + " 2>/dev/null || echo "0") + + EXECS_PER_SEC=$(( (FINAL_EXECS - INITIAL_EXECS) / 30 )) + EXECS_PER_WORKER=$(( EXECS_PER_SEC / CURRENT_WORKERS )) + + echo -e "Executions/sec: ${GREEN}${EXECS_PER_SEC}${NC}" + echo -e "Executions/worker: ${GREEN}${EXECS_PER_WORKER}${NC}" + echo "" + + # Get system load + CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}') + MEM_USAGE=$(free | grep Mem | awk '{printf "%.1f", ($3/$2) * 100.0}') + + echo -e "CPU Usage: ${CYAN}${CPU_USAGE}%${NC}" + echo -e "Memory Usage: ${CYAN}${MEM_USAGE}%${NC}" + echo "" + + # Scaling recommendations + echo -e "${BLUE}=== Scaling Recommendations ===${NC}" + if (( $(echo "$CPU_USAGE < 50" | bc -l) )) && (( $(echo "$MEM_USAGE < 50" | bc -l) )); then + SUGGESTED_WORKERS=$((CURRENT_WORKERS * 2)) + echo -e "${GREEN}✓ System has capacity${NC}" + echo -e "Suggested workers: ${GREEN}${SUGGESTED_WORKERS}${NC} (double current)" + elif (( $(echo "$CPU_USAGE > 80" | bc -l) )) || (( $(echo "$MEM_USAGE > 80" | bc -l) )); then + SUGGESTED_WORKERS=$((CURRENT_WORKERS / 2)) + SUGGESTED_WORKERS=$((SUGGESTED_WORKERS > 1 ? SUGGESTED_WORKERS : 1)) + echo -e "${RED}⚠ System under high load${NC}" + echo -e "Suggested workers: ${YELLOW}${SUGGESTED_WORKERS}${NC} (reduce by half)" + else + echo -e "${YELLOW}System load is moderate${NC}" + echo -e "Current worker count seems appropriate" + fi + fi +else + echo -e "${YELLOW}No workers currently running${NC}" + echo -e "Start workers with: ${CYAN}./Scripts/start-distributed.sh ${RECOMMENDED_WORKERS}${NC}" +fi + +echo "" +echo -e "${CYAN}========================================${NC}" + diff --git a/Scripts/cleanup-docker.sh b/Scripts/cleanup-docker.sh deleted file mode 100755 index 88c927cdb..000000000 --- a/Scripts/cleanup-docker.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash - -# Script to completely clean up all Docker resources for the distributed Fuzzilli setup -# Usage: ./Scripts/cleanup-docker.sh - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" - -cd "$PROJECT_DIR" - -MASTER_COMPOSE="docker-compose.master.yml" -WORKERS_COMPOSE="docker-compose.workers.yml" - -echo "==========================================" -echo " Cleaning up Docker resources" -echo "==========================================" -echo "" - -# Stop and remove containers, networks, and volumes -echo "Stopping and removing containers..." -docker compose -f "$MASTER_COMPOSE" -f "$WORKERS_COMPOSE" down --volumes --remove-orphans 2>/dev/null || true - -# Remove any remaining volumes -echo "Removing volumes..." -docker volume ls -q | grep -E "(fuzzillai_|postgres_|fuzzer_)" | xargs -r docker volume rm 2>/dev/null || true - -# Remove any orphaned containers -echo "Removing orphaned containers..." -docker ps -a --filter "name=fuzzilli\|fuzzer-worker\|postgres-local\|postgres-master" --format "{{.ID}}" | xargs -r docker rm -f 2>/dev/null || true - -# Remove any orphaned networks -echo "Removing orphaned networks..." -docker network ls --filter "name=fuzzing" --format "{{.ID}}" | xargs -r docker network rm 2>/dev/null || true - -# Clean up any dangling images -echo "Removing dangling images..." -docker image prune -f 2>/dev/null || true - -echo "" -echo "==========================================" -echo " Cleanup complete!" -echo "==========================================" - diff --git a/Scripts/clear-postgres-db.sh b/Scripts/clear-postgres-db.sh deleted file mode 100755 index df14710ac..000000000 --- a/Scripts/clear-postgres-db.sh +++ /dev/null @@ -1,173 +0,0 @@ -#!/bin/bash - -# Script to fully clear all PostgreSQL databases (master and all local workers) -# This script truncates all tables in all databases to remove all data -# Usage: ./Scripts/clear-postgres-db.sh [--force] - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Check for --force flag -FORCE=false -if [ "$1" == "--force" ]; then - FORCE=true -fi - -# Function to check if Docker is available -check_docker() { - if ! command -v docker &> /dev/null; then - echo -e "${RED}Error: Docker command not found. Please install Docker.${NC}" - exit 1 - fi -} - -# Function to clear a database -clear_database() { - local container_name="$1" - local db_name="$2" - local db_user="${3:-fuzzilli}" - local db_password="${4:-fuzzilli123}" - - echo -e "${BLUE}Clearing database '${db_name}' in container '${container_name}'...${NC}" - - # Check if container is running (handle both short and full container names) - local container_running="" - while IFS= read -r line; do - if [[ "$line" =~ ^(${container_name}|fuzzilli-${container_name})$ ]]; then - container_running="$line" - break - fi - done < <(docker ps --format "{{.Names}}") - - if [ -z "$container_running" ]; then - echo -e "${YELLOW}Warning: Container '${container_name}' or 'fuzzilli-${container_name}' is not running. Skipping.${NC}" - return 0 - fi - # Use the actual running container name - local actual_container_name="$container_running" - - # List of tables to truncate (in order to handle foreign keys) - # Using TRUNCATE CASCADE to handle foreign key constraints - local truncate_sql=" - TRUNCATE TABLE - coverage_detail, - feedback_vector_detail, - execution, - program, - fuzzer, - coverage_snapshot, - main - CASCADE; - " - - # Execute truncate (use actual container name if found) - local exec_container="${actual_container_name:-${container_name}}" - if docker exec -i "${exec_container}" psql -U "${db_user}" -d "${db_name}" -c "${truncate_sql}" &>/dev/null; then - echo -e "${GREEN}Successfully cleared database '${db_name}'${NC}" - return 0 - else - echo -e "${RED}Error: Failed to clear database '${db_name}'${NC}" - return 1 - fi -} - -# Function to get all postgres containers -get_postgres_containers() { - docker ps --format "{{.Names}}" | grep -E "(postgres-|fuzzilli-postgres-)(master|local-[0-9]+)" || true -} - -# Main execution -main() { - echo "==========================================" - echo " Clear PostgreSQL Databases" - echo "==========================================" - echo "" - - check_docker - - # Get all postgres containers - local containers=$(get_postgres_containers) - - if [ -z "$containers" ]; then - echo -e "${YELLOW}No PostgreSQL containers found.${NC}" - echo "Available containers:" - docker ps --format "table {{.Names}}\t{{.Status}}" - exit 0 - fi - - echo -e "${YELLOW}Found PostgreSQL containers:${NC}" - echo "$containers" | while read container; do - echo " - $container" - done - echo "" - - # Confirmation prompt (unless --force) - if [ "$FORCE" != "true" ]; then - echo -e "${RED}WARNING: This will DELETE ALL DATA from all PostgreSQL databases!${NC}" - echo -e "${YELLOW}This action cannot be undone.${NC}" - echo "" - read -p "Are you sure you want to continue? (yes/no): " confirm - if [ "$confirm" != "yes" ]; then - echo -e "${BLUE}Operation cancelled.${NC}" - exit 0 - fi - echo "" - fi - - # Clear master database - echo -e "${BLUE}Clearing master database...${NC}" - local master_container="" - while IFS= read -r line; do - if [[ "$line" =~ (postgres-master|fuzzilli-postgres-master) ]]; then - master_container="$line" - break - fi - done < <(docker ps --format "{{.Names}}") - - if [ -n "$master_container" ]; then - clear_database "$master_container" "fuzzilli_master" - else - echo -e "${YELLOW}Master database container not found. Skipping.${NC}" - fi - echo "" - - # Clear all local worker databases - echo -e "${BLUE}Clearing local worker databases...${NC}" - local cleared_count=0 - local failed_count=0 - - for container in $containers; do - if [[ "$container" =~ (postgres-local-|fuzzilli-postgres-local-)[0-9]+ ]]; then - if clear_database "$container" "fuzzilli_local"; then - cleared_count=$((cleared_count + 1)) - else - failed_count=$((failed_count + 1)) - fi - fi - done - - echo "" - echo "==========================================" - if [ $failed_count -eq 0 ]; then - echo -e "${GREEN}Database clearing complete!${NC}" - echo -e "${GREEN}Cleared: Master database + $cleared_count local database(s)${NC}" - else - echo -e "${YELLOW}Database clearing completed with some errors.${NC}" - echo -e "${YELLOW}Cleared: Master database + $cleared_count local database(s)${NC}" - echo -e "${RED}Failed: $failed_count database(s)${NC}" - fi - echo "==========================================" -} - -# Run main function -main - diff --git a/Scripts/extract_crashes.sh b/Scripts/extract_crashes.sh deleted file mode 100755 index f39ae4e85..000000000 --- a/Scripts/extract_crashes.sh +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/bash - -# Extract all programs that crashed V8 or Fuzzilli (Crashed and SigCheck outcomes) -# Programs are saved in FuzzIL binary format (.fzil) - -DB_CONTAINER="fuzzilli-postgres-master" -DB_NAME="fuzzilli_master" -DB_USER="fuzzilli" -DB_PASSWORD="fuzzilli123" - -OUTPUT_DIR="crashes_extracted" -mkdir -p "$OUTPUT_DIR" - -echo "Extracting all crashing and signaled programs in FuzzIL binary format..." -echo "Including: Crashed (SIGSEGV) and SigCheck (SIGTRAP) outcomes" - -# Get all crash and sigcheck information -docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -t -A -F'|' -c " -SELECT - p.program_hash, - e.execution_id, - eo.outcome, - e.signal_code, - e.execution_time_ms, - e.created_at, - p.program_base64, - p.program_size, - p.fuzzer_id, - LEFT(COALESCE(e.stderr, ''), 500) as crash_info -FROM execution e -JOIN execution_outcome eo ON e.execution_outcome_id = eo.id -JOIN program p ON e.program_hash = p.program_hash -WHERE eo.outcome IN ('Crashed', 'SigCheck') -ORDER BY eo.outcome, e.created_at DESC; -" | while IFS='|' read -r hash execution_id outcome signal_code exec_time created_at base64_program program_size fuzzer_id crash_info; do - - # Trim whitespace - hash=$(echo "$hash" | xargs) - execution_id=$(echo "$execution_id" | xargs) - outcome=$(echo "$outcome" | xargs) - signal_code=$(echo "$signal_code" | xargs) - exec_time=$(echo "$exec_time" | xargs) - created_at=$(echo "$created_at" | xargs) - base64_program=$(echo "$base64_program" | xargs) - program_size=$(echo "$program_size" | xargs) - fuzzer_id=$(echo "$fuzzer_id" | xargs) - crash_info=$(echo "$crash_info" | xargs) - - if [ -z "$hash" ]; then - continue - fi - - # Determine signal name - signal_name="UNKNOWN" - if [ "$outcome" = "Crashed" ] && [ "$signal_code" = "11" ]; then - signal_name="SIGSEGV (Segmentation Fault)" - prefix="crash" - elif [ "$outcome" = "SigCheck" ] && [ "$signal_code" = "5" ]; then - signal_name="SIGTRAP (Debug Assertion)" - prefix="sigcheck" - else - signal_name="Signal $signal_code" - prefix="signaled" - fi - - echo "Processing $outcome: $hash (execution_id: $execution_id, signal: $signal_code)" - - # Decode base64 to binary FuzzIL protobuf format - binary_program=$(echo "$base64_program" | base64 -d 2>/dev/null) - - if [ -z "$binary_program" ]; then - echo " ERROR: Failed to decode base64 program" - continue - fi - - # Save as .fzil file (FuzzIL binary protobuf format) - fzil_filename="${OUTPUT_DIR}/${prefix}_${execution_id}_${hash}.fzil" - echo -n "$binary_program" > "$fzil_filename" - - # Create metadata file - meta_filename="${OUTPUT_DIR}/${prefix}_${execution_id}_${hash}.txt" - cat > "$meta_filename" </dev/null | wc -l) -sigcheck_count=$(ls -1 "${OUTPUT_DIR}"/sigcheck_*.fzil 2>/dev/null | wc -l) -total_count=$(ls -1 "${OUTPUT_DIR}"/*.fzil 2>/dev/null | wc -l) - -echo "" -echo "Extraction complete!" -echo " Crashed (SIGSEGV): $crash_count programs" -echo " SigCheck (SIGTRAP): $sigcheck_count programs" -echo " Total: $total_count programs" -echo "" -echo "All programs are saved in FuzzIL binary format (.fzil files) in $OUTPUT_DIR/" -echo "Use FuzzILTool to decode or lift them to JavaScript if needed" diff --git a/Scripts/fuzzer-stats.sh b/Scripts/fuzzer-stats.sh new file mode 100755 index 000000000..fe5b60d42 --- /dev/null +++ b/Scripts/fuzzer-stats.sh @@ -0,0 +1,203 @@ +#!/bin/bash + +# Fuzzilli Fuzzer Statistics Script +# Shows comprehensive statistics including highest coverage and per-fuzzer information + +# Database connection parameters +DB_CONTAINER="fuzzilli-postgres-master" +DB_NAME="fuzzilli_master" +DB_USER="fuzzilli" +DB_PASSWORD="fuzzilli123" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +MAGENTA='\033[0;35m' +NC='\033[0m' # No Color + +# Function to check if Docker is available +check_docker() { + if ! command -v docker &> /dev/null; then + echo -e "${RED}Error: Docker command not found. Please install Docker.${NC}" + exit 1 + fi +} + +# Function to check if PostgreSQL container is running +check_container() { + if ! docker ps --format "table {{.Names}}" | grep -q "$DB_CONTAINER"; then + echo -e "${RED}Error: PostgreSQL container '$DB_CONTAINER' is not running${NC}" + echo "Available containers:" + docker ps --format "table {{.Names}}\t{{.Status}}" + exit 1 + fi +} + +# Function to run a query and return results +run_query() { + local query="$1" + docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -t -A -F'|' -c "$query" 2>/dev/null +} + +# Function to format number with commas +format_number() { + printf "%'d" "$1" 2>/dev/null || echo "$1" +} + +# Function to format decimal +format_decimal() { + printf "%.2f" "$1" 2>/dev/null || echo "$1" +} + +# Main execution +main() { + echo -e "${CYAN}========================================${NC}" + echo -e "${CYAN} Fuzzilli Fuzzer Statistics${NC}" + echo -e "${CYAN}========================================${NC}" + echo "" + + check_docker + check_container + + # Get highest coverage (overall) + echo -e "${GREEN}=== Highest Coverage (Overall) ===${NC}" + highest_coverage=$(run_query "SELECT COALESCE(MAX(highest_coverage_pct), 0) FROM global_statistics;") + if [ -n "$highest_coverage" ] && [ "$highest_coverage" != "0" ]; then + echo -e " ${YELLOW}Highest Coverage:${NC} ${GREEN}$(format_decimal "$highest_coverage")%${NC}" + else + echo -e " ${YELLOW}Highest Coverage:${NC} ${RED}No coverage data available${NC}" + fi + echo "" + + # Get global statistics + echo -e "${GREEN}=== Global Statistics ===${NC}" + global_stats=$(run_query "SELECT total_programs, total_executions, total_crashes, active_fuzzers FROM global_statistics;") + if [ -n "$global_stats" ]; then + IFS='|' read -r total_programs total_executions total_crashes active_fuzzers <<< "$global_stats" + echo -e " ${YELLOW}Total Programs:${NC} $(format_number "$total_programs")" + echo -e " ${YELLOW}Total Executions:${NC} $(format_number "$total_executions")" + echo -e " ${YELLOW}Total Crashes:${NC} ${RED}$(format_number "$total_crashes")${NC}" + echo -e " ${YELLOW}Active Fuzzers:${NC} $(format_number "$active_fuzzers")" + fi + echo "" + + # Get per-fuzzer performance summary + echo -e "${GREEN}=== Per-Fuzzer Performance Summary ===${NC}" + echo "" + + # Header + printf "%-6s %-20s %-10s %-12s %-12s %-10s %-15s\n" \ + "ID" "Name" "Status" "Execs/s" "Programs" "Executions" "Crashes" "Highest Coverage %" + echo "--------------------------------------------------------------------------------------------------------" + + # Get per-fuzzer data + fuzzer_data=$(run_query " + SELECT + fuzzer_id, + fuzzer_name, + status, + COALESCE(execs_per_second, 0), + COALESCE(programs_count, 0), + COALESCE(executions_count, 0), + COALESCE(crash_count, 0), + COALESCE(highest_coverage_pct, 0) + FROM fuzzer_performance_summary + ORDER BY fuzzer_id; + ") + + if [ -z "$fuzzer_data" ]; then + echo -e "${YELLOW}No fuzzer data available${NC}" + else + while IFS='|' read -r fuzzer_id fuzzer_name status execs_per_sec programs executions crashes highest_cov; do + # Format execs/s + execs_formatted=$(printf "%.2f" "$execs_per_sec" 2>/dev/null || echo "0.00") + + # Format coverage + cov_formatted=$(printf "%.2f" "$highest_cov" 2>/dev/null || echo "0.00") + + # Color code based on status + if [ "$status" = "active" ]; then + status_color="${GREEN}" + else + status_color="${RED}" + fi + + printf "%-6s %-20s ${status_color}%-10s${NC} %-12s %-12s %-12s %-10s %-15s\n" \ + "$fuzzer_id" \ + "$fuzzer_name" \ + "$status" \ + "$execs_formatted" \ + "$(format_number "$programs")" \ + "$(format_number "$executions")" \ + "${RED}$(format_number "$crashes")${NC}" \ + "${CYAN}${cov_formatted}%${NC}" + done <<< "$fuzzer_data" + fi + echo "" + + # Get crash breakdown by signal per fuzzer + echo -e "${GREEN}=== Crash Breakdown by Signal (Per Fuzzer) ===${NC}" + echo "" + + crash_data=$(run_query " + SELECT + fuzzer_id, + fuzzer_name, + signal_code, + signal_name, + crash_count + FROM crash_by_signal + ORDER BY fuzzer_id, crash_count DESC; + ") + + if [ -z "$crash_data" ]; then + echo -e "${YELLOW}No crash data available${NC}" + else + current_fuzzer="" + while IFS='|' read -r fuzzer_id fuzzer_name signal_code signal_name crash_count; do + if [ "$current_fuzzer" != "$fuzzer_id" ]; then + if [ -n "$current_fuzzer" ]; then + echo "" + fi + echo -e "${CYAN}Fuzzer ${fuzzer_id} (${fuzzer_name}):${NC}" + current_fuzzer="$fuzzer_id" + fi + printf " ${YELLOW}%-15s${NC} (Signal %-3s): ${RED}%s${NC} crashes\n" \ + "$signal_name" \ + "${signal_code:-N/A}" \ + "$(format_number "$crash_count")" + done <<< "$crash_data" + fi + echo "" + + echo -e "${CYAN}========================================${NC}" + echo -e "${CYAN} Statistics Complete${NC}" + echo -e "${CYAN}========================================${NC}" +} + +# Handle command line arguments +case "${1:-}" in + "help"|"-h"|"--help") + echo "Usage: $0" + echo "" + echo "Shows comprehensive fuzzer statistics including:" + echo " - Highest coverage (overall)" + echo " - Global statistics" + echo " - Per-fuzzer information (execs/s, programs, executions, crashes, highest coverage %)" + echo " - Crash breakdown by signal per fuzzer" + echo "" + echo "Note: Update DB_CONTAINER variable in script if your container has a different name" + ;; + "") + main + ;; + *) + echo "Unknown option: $1" + echo "Use '$0 help' for usage information" + exit 1 + ;; +esac + diff --git a/Scripts/monitor-performance.sh b/Scripts/monitor-performance.sh new file mode 100755 index 000000000..05e39157d --- /dev/null +++ b/Scripts/monitor-performance.sh @@ -0,0 +1,212 @@ +#!/bin/bash + +# monitor-performance.sh - Monitor server performance and fuzzing metrics +# Usage: ./Scripts/monitor-performance.sh [interval_seconds] + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +INTERVAL=${1:-5} # Default 5 seconds +DB_CONTAINER="fuzzilli-postgres-master" +DB_NAME="fuzzilli_master" +DB_USER="fuzzilli" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +# Function to get CPU usage +get_cpu_usage() { + top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}' +} + +# Function to get memory usage +get_memory_usage() { + free | grep Mem | awk '{printf "%.1f", ($3/$2) * 100.0}' +} + +# Function to get disk I/O +get_disk_io() { + iostat -x 1 2 | tail -n +4 | awk '{sum+=$10} END {printf "%.1f", sum/NR}' +} + +# Function to get database connection count +get_db_connections() { + if docker ps --format "{{.Names}}" | grep -q "^${DB_CONTAINER}$"; then + docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -t -A -c "SELECT count(*) FROM pg_stat_activity WHERE datname = '$DB_NAME';" 2>/dev/null || echo "0" + else + echo "0" + fi +} + +# Function to get total executions per second across all fuzzers +get_total_execs_per_sec() { + if docker ps --format "{{.Names}}" | grep -q "^${DB_CONTAINER}$"; then + docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -t -A -c " + SELECT COALESCE(SUM(execs_per_second), 0) + FROM fuzzer_performance_summary + WHERE status = 'active'; + " 2>/dev/null || echo "0" + else + echo "0" + fi +} + +# Function to get active worker count +get_active_workers() { + docker ps --format "{{.Names}}" | grep -c "fuzzer-worker" || echo "0" +} + +# Function to get database size +get_db_size() { + if docker ps --format "{{.Names}}" | grep -q "^${DB_CONTAINER}$"; then + docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -t -A -c " + SELECT pg_size_pretty(pg_database_size('$DB_NAME')); + " 2>/dev/null || echo "N/A" + else + echo "N/A" + fi +} + +# Function to get total programs and executions +get_db_stats() { + if docker ps --format "{{.Names}}" | grep -q "^${DB_CONTAINER}$"; then + docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -t -A -F'|' -c " + SELECT total_programs, total_executions, total_crashes, active_fuzzers + FROM global_statistics; + " 2>/dev/null || echo "0|0|0|0" + else + echo "0|0|0|0" + fi +} + +# Function to get per-worker performance +get_worker_performance() { + if docker ps --format "{{.Names}}" | grep -q "^${DB_CONTAINER}$"; then + docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -t -A -F'|' -c " + SELECT + fuzzer_id, + ROUND(execs_per_second::numeric, 2), + executions_count, + crash_count + FROM fuzzer_performance_summary + WHERE status = 'active' + ORDER BY fuzzer_id; + " 2>/dev/null || echo "" + else + echo "" + fi +} + +# Main monitoring loop +monitor() { + local iteration=0 + + while true; do + clear + echo -e "${CYAN}========================================${NC}" + echo -e "${CYAN} Fuzzilli Performance Monitor${NC}" + echo -e "${CYAN}========================================${NC}" + echo "" + + # System metrics + echo -e "${BLUE}=== System Resources ===${NC}" + CPU=$(get_cpu_usage) + MEM=$(get_memory_usage) + DB_CONN=$(get_db_connections) + DB_SIZE=$(get_db_size) + + # Color code CPU usage + if (( $(echo "$CPU > 80" | bc -l) )); then + CPU_COLOR=$RED + elif (( $(echo "$CPU > 60" | bc -l) )); then + CPU_COLOR=$YELLOW + else + CPU_COLOR=$GREEN + fi + + # Color code memory usage + if (( $(echo "$MEM > 80" | bc -l) )); then + MEM_COLOR=$RED + elif (( $(echo "$MEM > 60" | bc -l) )); then + MEM_COLOR=$YELLOW + else + MEM_COLOR=$GREEN + fi + + echo -e "CPU Usage: ${CPU_COLOR}${CPU}%${NC}" + echo -e "Memory Usage: ${MEM_COLOR}${MEM}%${NC}" + echo -e "DB Connections: ${CYAN}${DB_CONN}${NC}" + echo -e "DB Size: ${CYAN}${DB_SIZE}${NC}" + echo "" + + # Fuzzing metrics + echo -e "${BLUE}=== Fuzzing Metrics ===${NC}" + ACTIVE_WORKERS=$(get_active_workers) + TOTAL_EXECS=$(get_total_execs_per_sec) + DB_STATS=$(get_db_stats) + + IFS='|' read -r total_programs total_executions total_crashes active_fuzzers <<< "$DB_STATS" + + echo -e "Active Workers: ${GREEN}${ACTIVE_WORKERS}${NC}" + echo -e "Total Execs/sec: ${GREEN}${TOTAL_EXECS}${NC}" + echo -e "Total Programs: ${CYAN}${total_programs}${NC}" + echo -e "Total Executions: ${CYAN}${total_executions}${NC}" + echo -e "Total Crashes: ${RED}${total_crashes}${NC}" + echo "" + + # Per-worker breakdown + echo -e "${BLUE}=== Per-Worker Performance ===${NC}" + WORKER_PERF=$(get_worker_performance) + if [ -n "$WORKER_PERF" ]; then + echo -e "${YELLOW}Worker | Execs/sec | Executions | Crashes${NC}" + echo "$WORKER_PERF" | while IFS='|' read -r worker_id execs_per_sec executions crashes; do + printf " %-4s | %9s | %10s | %7s\n" "$worker_id" "$execs_per_sec" "$executions" "$crashes" + done + else + echo -e "${YELLOW}No worker data available${NC}" + fi + echo "" + + # Recommendations + echo -e "${BLUE}=== Recommendations ===${NC}" + if (( $(echo "$CPU > 80" | bc -l) )); then + echo -e "${RED}⚠ High CPU usage - consider reducing workers${NC}" + elif (( $(echo "$CPU < 40" | bc -l) )); then + echo -e "${GREEN}✓ CPU has capacity - could add more workers${NC}" + fi + + if (( $(echo "$MEM > 80" | bc -l) )); then + echo -e "${RED}⚠ High memory usage - consider reducing workers${NC}" + elif (( $(echo "$MEM < 40" | bc -l) )); then + echo -e "${GREEN}✓ Memory has capacity - could add more workers${NC}" + fi + + if (( $(echo "$DB_CONN > 50" | bc -l) )); then + echo -e "${YELLOW}⚠ High database connection count${NC}" + fi + + echo "" + echo -e "${CYAN}Press Ctrl+C to stop${NC}" + echo -e "${CYAN}Update interval: ${INTERVAL}s${NC}" + + sleep "$INTERVAL" + iteration=$((iteration + 1)) + done +} + +# Check dependencies +if ! command -v bc &> /dev/null; then + echo -e "${YELLOW}Warning: 'bc' not found. Installing...${NC}" + sudo apt-get update && sudo apt-get install -y bc +fi + +# Start monitoring +monitor + diff --git a/Scripts/DBQuery.sh b/Scripts/query-db.sh similarity index 83% rename from Scripts/DBQuery.sh rename to Scripts/query-db.sh index 5fdc8e195..416e1a7d0 100755 --- a/Scripts/DBQuery.sh +++ b/Scripts/query-db.sh @@ -1,11 +1,11 @@ #!/bin/bash # Fuzzilli PostgreSQL Database Query Script using Docker -# This script uses Docker to connect to the PostgreSQL container and run queries +# Consolidated script for querying the master database # Database connection parameters -DB_CONTAINER="fuzzilli-postgres" # Adjust this to match your container name -DB_NAME="fuzzilli" +DB_CONTAINER="fuzzilli-postgres-master" +DB_NAME="fuzzilli_master" DB_USER="fuzzilli" DB_PASSWORD="fuzzilli123" @@ -88,11 +88,13 @@ main() { run_query "Program Count by Fuzzer" " SELECT p.fuzzer_id, + m.fuzzer_name, COUNT(*) as program_count, MIN(p.created_at) as first_program, MAX(p.created_at) as latest_program - FROM program p - GROUP BY p.fuzzer_id + FROM program p + JOIN main m ON p.fuzzer_id = m.fuzzer_id + GROUP BY p.fuzzer_id, m.fuzzer_name ORDER BY program_count DESC; " @@ -124,12 +126,14 @@ main() { run_query "Recent Programs (Last 10)" " SELECT LEFT(program_base64, 20) as program_preview, - fuzzer_id, - LEFT(program_hash, 12) as hash_prefix, - program_size, - created_at - FROM program - ORDER BY created_at DESC + p.fuzzer_id, + m.fuzzer_name, + LEFT(p.program_hash, 12) as hash_prefix, + p.program_size, + p.created_at + FROM program p + JOIN main m ON p.fuzzer_id = m.fuzzer_id + ORDER BY p.created_at DESC LIMIT 10; " @@ -138,12 +142,14 @@ main() { SELECT e.execution_id, p.fuzzer_id, + m.fuzzer_name, LEFT(p.program_hash, 12) as hash_prefix, eo.outcome, e.execution_time_ms, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hash + JOIN main m ON p.fuzzer_id = m.fuzzer_id JOIN execution_outcome eo ON e.execution_outcome_id = eo.id ORDER BY e.created_at DESC LIMIT 10; @@ -153,14 +159,16 @@ main() { run_query "Crash Analysis" " SELECT p.fuzzer_id, + m.fuzzer_name, COUNT(*) as crash_count, MIN(e.created_at) as first_crash, MAX(e.created_at) as latest_crash FROM execution e JOIN program p ON e.program_hash = p.program_hash + JOIN main m ON p.fuzzer_id = m.fuzzer_id JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' - GROUP BY p.fuzzer_id + GROUP BY p.fuzzer_id, m.fuzzer_name ORDER BY crash_count DESC; " @@ -202,13 +210,13 @@ main() { # Handle command line arguments case "${1:-}" in "programs") - run_query "All Programs" "SELECT LEFT(program_base64, 30) as program_preview, fuzzer_id, program_size, created_at FROM program ORDER BY created_at DESC LIMIT 20;" + run_query "All Programs" "SELECT LEFT(program_base64, 30) as program_preview, p.fuzzer_id, m.fuzzer_name, p.program_size, p.created_at FROM program p JOIN main m ON p.fuzzer_id = m.fuzzer_id ORDER BY p.created_at DESC LIMIT 20;" ;; "executions") - run_query "All Executions" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, eo.outcome, e.execution_time_ms, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id ORDER BY e.created_at DESC LIMIT 20;" + run_query "All Executions" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, m.fuzzer_name, eo.outcome, e.execution_time_ms, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hash JOIN main m ON p.fuzzer_id = m.fuzzer_id JOIN execution_outcome eo ON e.execution_outcome_id = eo.id ORDER BY e.created_at DESC LIMIT 20;" ;; "crashes") - run_query "All Crashes" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, e.stdout, e.stderr, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 20;" + run_query "All Crashes" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, m.fuzzer_name, e.stdout, e.stderr, e.created_at FROM execution e JOIN program p ON e.program_hash = p.program_hash JOIN main m ON p.fuzzer_id = m.fuzzer_id JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 20;" ;; "stats") run_query "Quick Stats" "SELECT COUNT(*) as programs, (SELECT COUNT(*) FROM execution) as executions, (SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed') as crashes FROM program;" @@ -239,4 +247,5 @@ case "${1:-}" in echo "Use '$0 help' for usage information" exit 1 ;; -esac \ No newline at end of file +esac + diff --git a/Scripts/query_db.sh b/Scripts/query_db.sh deleted file mode 100755 index 634f48c1d..000000000 --- a/Scripts/query_db.sh +++ /dev/null @@ -1,274 +0,0 @@ -#!/bin/bash - -# Fuzzilli PostgreSQL Database Query Script using Docker -# This script uses Docker to connect to the PostgreSQL container and run queries - -# Database connection parameters -DB_CONTAINER="fuzzilli-postgres-master" # Adjust this to match your container name -DB_NAME="fuzzilli_master" -DB_USER="fuzzilli" -DB_PASSWORD="fuzzilli123" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Function to run a query using Docker -run_query() { - local title="$1" - local query="$2" - - echo -e "\n${BLUE}=== $title ===${NC}" - echo -e "${YELLOW}Query:${NC} $query" - echo -e "${GREEN}Results:${NC}" - - docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -c "$query" 2>/dev/null - - if [ $? -ne 0 ]; then - echo -e "${RED}Error: Failed to execute query${NC}" - fi -} - -# Function to check if Docker is available -check_docker() { - if ! command -v docker &> /dev/null; then - echo -e "${RED}Error: Docker command not found. Please install Docker.${NC}" - exit 1 - fi -} - -# Function to check if PostgreSQL container is running -check_container() { - if ! docker ps --format "table {{.Names}}" | grep -q "$DB_CONTAINER"; then - echo -e "${RED}Error: PostgreSQL container '$DB_CONTAINER' is not running${NC}" - echo "Available containers:" - docker ps --format "table {{.Names}}\t{{.Status}}" - echo "" - echo "Please start the PostgreSQL container or update the DB_CONTAINER variable in this script" - exit 1 - fi -} - -# Function to test database connection -test_connection() { - echo -e "${BLUE}Testing database connection...${NC}" - docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -c "SELECT version();" &>/dev/null - - if [ $? -eq 0 ]; then - echo -e "${GREEN}✓ Database connection successful${NC}" - else - echo -e "${RED}✗ Database connection failed${NC}" - echo "Please check:" - echo "1. PostgreSQL container is running" - echo "2. Database credentials are correct" - echo "3. Container name is correct" - exit 1 - fi -} - -# Main execution -main() { - echo -e "${GREEN}Fuzzilli Database Query Tool (Docker)${NC}" - echo "=============================================" - - check_docker - check_container - test_connection - - # Basic database info - run_query "Database Information" "SELECT current_database() as database_name, current_user as user_name, version() as postgres_version;" - - # List all tables - run_query "Available Tables" "SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name;" - - # Program statistics - run_query "Program Count by Fuzzer" " - SELECT - p.fuzzer_id, - COUNT(*) as program_count, - MIN(p.created_at) as first_program, - MAX(p.created_at) as latest_program - FROM program p - GROUP BY p.fuzzer_id - ORDER BY program_count DESC; - " - - # Total program count - run_query "Total Program Statistics" " - SELECT - COUNT(*) as total_programs, - COUNT(DISTINCT fuzzer_id) as active_fuzzers, - AVG(program_size) as avg_program_size, - MAX(program_size) as max_program_size, - MIN(created_at) as first_program, - MAX(created_at) as latest_program - FROM program; - " - - # Execution statistics - run_query "Execution Statistics" " - SELECT - eo.outcome, - COUNT(*) as count, - ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) as percentage - FROM execution e - JOIN execution_outcome eo ON e.execution_outcome_id = eo.id - GROUP BY eo.outcome - ORDER BY count DESC; - " - - # Recent programs - run_query "Recent Programs (Last 10)" " - SELECT - LEFT(program_base64, 20) as program_preview, - fuzzer_id, - LEFT(program_hash, 12) as hash_prefix, - program_size, - created_at - FROM program - ORDER BY created_at DESC - LIMIT 10; - " - - # Recent executions - run_query "Recent Executions (Last 10)" " - SELECT - e.execution_id, - p.fuzzer_id, - LEFT(p.program_hash, 12) as hash_prefix, - eo.outcome, - e.execution_time_ms, - e.created_at - FROM execution e - JOIN program p ON e.program_base64 = p.program_base64 - JOIN execution_outcome eo ON e.execution_outcome_id = eo.id - ORDER BY e.created_at DESC - LIMIT 10; - " - - # Crash analysis - run_query "Crash Analysis" " - SELECT - p.fuzzer_id, - COUNT(*) as crash_count, - MIN(e.created_at) as first_crash, - MAX(e.created_at) as latest_crash - FROM execution e - JOIN program p ON e.program_base64 = p.program_base64 - JOIN execution_outcome eo ON e.execution_outcome_id = eo.id - WHERE eo.outcome = 'Crashed' - GROUP BY p.fuzzer_id - ORDER BY crash_count DESC; - " - - # Coverage statistics - run_query "Coverage Statistics" " - SELECT - COUNT(*) as executions_with_coverage, - AVG(coverage_total) as avg_coverage_percentage, - MAX(coverage_total) as max_coverage_percentage, - COUNT(CASE WHEN coverage_total > 0 THEN 1 END) as executions_with_positive_coverage - FROM execution - WHERE coverage_total IS NOT NULL; - " - - # Coverage snapshot statistics - run_query "Coverage Snapshot Statistics" " - SELECT - COUNT(*) as total_snapshots, - AVG(coverage_percentage) as avg_coverage_percentage, - MAX(coverage_percentage) as max_coverage_percentage, - AVG(edges_found) as avg_edges_found, - MAX(edges_found) as max_edges_found, - AVG(total_edges) as avg_total_edges, - MAX(total_edges) as max_total_edges, - COUNT(CASE WHEN edges_found > 0 THEN 1 END) as snapshots_with_coverage - FROM coverage_snapshot - WHERE edges_found IS NOT NULL AND total_edges IS NOT NULL; - " - - # Recent coverage snapshots - run_query "Recent Coverage Snapshots (Last 10)" " - SELECT - snapshot_id, - fuzzer_id, - ROUND(coverage_percentage::numeric, 6) as coverage_pct, - edges_found, - total_edges, - LEFT(program_hash, 12) as hash_prefix, - created_at - FROM coverage_snapshot - WHERE edges_found IS NOT NULL AND total_edges IS NOT NULL - ORDER BY created_at DESC - LIMIT 10; - " - - # Performance metrics - run_query "Performance Metrics" " - SELECT - AVG(execution_time_ms) as avg_execution_time_ms, - MIN(execution_time_ms) as min_execution_time_ms, - MAX(execution_time_ms) as max_execution_time_ms, - COUNT(*) as total_executions - FROM execution - WHERE execution_time_ms > 0; - " - - # Database size info - run_query "Database Size Information" " - SELECT - schemaname, - tablename, - pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size - FROM pg_tables - WHERE schemaname = 'public' - ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC; - " - - echo -e "\n${GREEN}Database query completed successfully!${NC}" -} - -# Handle command line arguments -case "${1:-}" in - "programs") - run_query "All Programs" "SELECT LEFT(program_base64, 30) as program_preview, fuzzer_id, program_size, created_at FROM program ORDER BY created_at DESC LIMIT 20;" - ;; - "executions") - run_query "All Executions" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, eo.outcome, e.execution_time_ms, e.created_at FROM execution e JOIN program p ON e.program_base64 = p.program_base64 JOIN execution_outcome eo ON e.execution_outcome_id = eo.id ORDER BY e.created_at DESC LIMIT 20;" - ;; - "crashes") - run_query "All Crashes" "SELECT e.execution_id, LEFT(p.program_base64, 20) as program_preview, e.stdout, e.stderr, e.created_at FROM execution e JOIN program p ON e.program_base64 = p.program_base64 JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' ORDER BY e.created_at DESC LIMIT 20;" - ;; - "stats") - run_query "Quick Stats" "SELECT COUNT(*) as programs, (SELECT COUNT(*) FROM execution) as executions, (SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed') as crashes FROM program;" - ;; - "containers") - echo -e "${BLUE}Available PostgreSQL containers:${NC}" - docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(postgres|fuzzilli)" - ;; - "help"|"-h"|"--help") - echo "Usage: $0 [command]" - echo "" - echo "Commands:" - echo " (no args) - Run full database analysis" - echo " programs - Show recent programs" - echo " executions - Show recent executions" - echo " crashes - Show recent crashes" - echo " stats - Show quick statistics" - echo " containers - List available PostgreSQL containers" - echo " help - Show this help" - echo "" - echo "Note: Update DB_CONTAINER variable in script if your container has a different name" - ;; - "") - main - ;; - *) - echo "Unknown command: $1" - echo "Use '$0 help' for usage information" - exit 1 - ;; -esac \ No newline at end of file diff --git a/Scripts/scale-workers.sh b/Scripts/scale-workers.sh new file mode 100755 index 000000000..e80b0c396 --- /dev/null +++ b/Scripts/scale-workers.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +# scale-workers.sh - Scale workers up or down based on server performance +# Usage: ./Scripts/scale-workers.sh [target_count] [--auto] + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +# Get current worker count +get_current_workers() { + docker ps --format "{{.Names}}" | grep -c "fuzzer-worker" || echo "0" +} + +# Scale workers +scale_workers() { + local target_count=$1 + local current_count=$(get_current_workers) + + if [ "$target_count" -eq "$current_count" ]; then + echo -e "${YELLOW}Already at target worker count: ${target_count}${NC}" + return + fi + + echo -e "${CYAN}Scaling workers from ${current_count} to ${target_count}...${NC}" + + if [ "$target_count" -gt "$current_count" ]; then + # Scale up + echo -e "${GREEN}Scaling UP: Adding $((target_count - current_count)) workers${NC}" + cd "$PROJECT_DIR" + ./Scripts/start-distributed.sh "$target_count" + else + # Scale down + echo -e "${YELLOW}Scaling DOWN: Removing $((current_count - target_count)) workers${NC}" + local to_remove=$((current_count - target_count)) + local removed=0 + + for container in $(docker ps --format "{{.Names}}" | grep "fuzzer-worker" | sort -V | tail -n "$to_remove"); do + echo -e "Stopping ${container}..." + docker stop "$container" > /dev/null 2>&1 || true + removed=$((removed + 1)) + done + + echo -e "${GREEN}Stopped ${removed} worker(s)${NC}" + fi + + echo -e "${GREEN}Scale operation complete${NC}" +} + +# Auto-scale based on performance +auto_scale() { + echo -e "${CYAN}Auto-scaling based on server performance...${NC}" + echo "" + + # Get current metrics + CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}') + MEM_USAGE=$(free | grep Mem | awk '{printf "%.1f", ($3/$2) * 100.0}') + CURRENT_WORKERS=$(get_current_workers) + + echo -e "Current CPU: ${CYAN}${CPU_USAGE}%${NC}" + echo -e "Current MEM: ${CYAN}${MEM_USAGE}%${NC}" + echo -e "Current Workers: ${CYAN}${CURRENT_WORKERS}${NC}" + echo "" + + # Determine target worker count + local target_count=$CURRENT_WORKERS + + if (( $(echo "$CPU_USAGE < 40" | bc -l) )) && (( $(echo "$MEM_USAGE < 40" | bc -l) )); then + # System has capacity - scale up + target_count=$((CURRENT_WORKERS + 2)) + echo -e "${GREEN}System has capacity - scaling UP${NC}" + elif (( $(echo "$CPU_USAGE > 80" | bc -l) )) || (( $(echo "$MEM_USAGE > 80" | bc -l) )); then + # System under load - scale down + target_count=$((CURRENT_WORKERS - 1)) + target_count=$((target_count > 0 ? target_count : 1)) + echo -e "${RED}System under load - scaling DOWN${NC}" + else + echo -e "${YELLOW}System load is moderate - no scaling needed${NC}" + return + fi + + scale_workers "$target_count" +} + +# Main +main() { + if [ "$1" = "--auto" ] || [ "$1" = "-a" ]; then + auto_scale + elif [ -n "$1" ] && [[ "$1" =~ ^[0-9]+$ ]]; then + scale_workers "$1" + else + echo "Usage: $0 [target_count|--auto]" + echo "" + echo "Options:" + echo " target_count - Set worker count to specific number" + echo " --auto, -a - Auto-scale based on server performance" + echo "" + echo "Current workers: $(get_current_workers)" + exit 1 + fi +} + +# Check dependencies +if ! command -v bc &> /dev/null; then + echo -e "${YELLOW}Warning: 'bc' not found. Installing...${NC}" + sudo apt-get update && sudo apt-get install -y bc > /dev/null 2>&1 +fi + +main "$@" + diff --git a/Scripts/show-crash-javascript.sh b/Scripts/show-crash-javascript.sh deleted file mode 100755 index 748124c2e..000000000 --- a/Scripts/show-crash-javascript.sh +++ /dev/null @@ -1,148 +0,0 @@ -#!/bin/bash - -# Script to show JavaScript code from crash programs -# Usage: ./Scripts/show-crash-javascript.sh [worker_num] -# If no worker_num is specified, shows crashes for all postgres-local-* containers - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" - -cd "$PROJECT_DIR" - -# Colors for output -CYAN='\033[0;36m' -YELLOW='\033[1;33m' -GREEN='\033[0;32m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -# Get all postgres-local-* containers -get_local_postgres_containers() { - docker ps --format '{{.Names}}' | grep '^postgres-local-' | sort -} - -# Get worker number from container name -get_worker_num() { - local container=$1 - echo "$container" | sed 's/.*-\([0-9]*\)$/\1/' -} - -# Check if a container is running -check_container() { - local container=$1 - if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then - return 0 - else - return 1 - fi -} - -show_crash_javascript() { - local worker_num=$1 - local container="postgres-local-${worker_num}" - local database="fuzzilli_local" - - if ! check_container "$container"; then - echo -e "${RED}Worker ${worker_num}: Container ${container} not running${NC}" - echo "" - return - fi - - echo -e "${CYAN}========================================${NC}" - echo -e "${CYAN} Worker ${worker_num} Crash Programs${NC}" - echo -e "${CYAN} (Excluding FUZZILLI_CRASH test cases - signal 3)${NC}" - echo -e "${CYAN}========================================${NC}" - echo "" - - # Get all crash program hashes (one per line) - # Exclude only signal_code = 3 (FUZZILLI_CRASH test cases) - # Show all other crashes including signal 11 and all other signals - local crash_hashes=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT DISTINCT e.program_hash FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' AND (e.signal_code IS NULL OR e.signal_code != 3) ORDER BY e.program_hash;" 2>/dev/null | grep -v '^$' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - - if [ -z "$crash_hashes" ]; then - echo -e "${YELLOW}No crashes found for Worker ${worker_num}${NC}" - echo "" - return - fi - - # Process each crash hash - echo "$crash_hashes" | while IFS= read -r hash; do - if [ -z "$hash" ]; then - continue - fi - - # Get execution details first to check signal code - # Exclude only signal 3 (FUZZILLI_CRASH test cases), show all others including signal 11 - local exec_details=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT e.execution_id, e.signal_code, e.exit_code, eo.description, e.created_at FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE e.program_hash = '${hash}' AND eo.outcome = 'Crashed' AND (e.signal_code IS NULL OR e.signal_code != 3) ORDER BY e.created_at DESC LIMIT 1;" 2>/dev/null) - - # Skip if no execution details found (shouldn't happen, but safety check) - if [ -z "$exec_details" ]; then - continue - fi - - # Get and decode the JavaScript to check for FUZZILLI_CRASH - local base64_program=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT program_base64 FROM program WHERE program_hash = '${hash}';" 2>/dev/null | tr -d ' \n\r') - - # Check if program contains FUZZILLI_CRASH pattern - if [ -n "$base64_program" ]; then - local decoded_program=$(echo "$base64_program" | base64 -d 2>/dev/null) - if echo "$decoded_program" | grep -q "FUZZILLI_CRASH"; then - # Skip this crash - it's a test case - continue - fi - fi - - echo -e "${GREEN}--- Crash Program: ${hash} ---${NC}" - - if [ -n "$exec_details" ]; then - echo -e "${YELLOW}Execution Details:${NC}" - echo "$exec_details" | sed 's/^/ /' - echo "" - fi - - # Get and decode the JavaScript - echo -e "${YELLOW}JavaScript Code:${NC}" - - if [ -n "$base64_program" ]; then - # Decode base64 and extract JavaScript strings - # Using awk to limit output without head/tail - local javascript=$(echo "$base64_program" | base64 -d 2>/dev/null | strings | grep -E "(fuzzilli|function|var|let|const|if|for|while|return)" | awk 'NR <= 10 { print; if (NR == 10) exit }') - - if [ -n "$javascript" ]; then - echo "$javascript" | sed 's/^/ /' - else - # Try to get any readable strings without tail - local all_strings=$(echo "$base64_program" | base64 -d 2>/dev/null | strings | grep -v "^$" | awk '{ lines[NR] = $0 } END { start = (NR > 5) ? NR - 4 : 1; for (i = start; i <= NR; i++) print lines[i] }') - if [ -n "$all_strings" ]; then - echo "$all_strings" | sed 's/^/ /' - else - echo " (Could not extract JavaScript - program may be in binary format)" - fi - fi - else - echo " (Program not found in database)" - fi - - echo "" - done <<< "$crash_hashes" -} - -# If worker number specified, show only that worker -if [ -n "$1" ]; then - show_crash_javascript "$1" -else - # Dynamically discover and show crashes for all postgres-local-* containers - local_postgres_containers=($(get_local_postgres_containers)) - if [ ${#local_postgres_containers[@]} -eq 0 ]; then - echo -e "${YELLOW}No postgres-local-* containers found${NC}" - echo "" - else - for postgres_container in "${local_postgres_containers[@]}"; do - worker_num=$(get_worker_num "$postgres_container") - show_crash_javascript "$worker_num" - done - fi -fi - diff --git a/Scripts/show-stats.sh b/Scripts/show-stats.sh deleted file mode 100755 index d8d2e5ccf..000000000 --- a/Scripts/show-stats.sh +++ /dev/null @@ -1,180 +0,0 @@ -#!/bin/bash - -# Script to show current statistics for distributed Fuzzilli setup -# Usage: ./Scripts/show-stats.sh - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" - -cd "$PROJECT_DIR" - -MASTER_COMPOSE="docker-compose.master.yml" -WORKERS_COMPOSE="docker-compose.workers.yml" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -CYAN='\033[0;36m' -NC='\033[0m' # No Color - -echo -e "${CYAN}========================================${NC}" -echo -e "${CYAN} Distributed Fuzzilli Statistics${NC}" -echo -e "${CYAN}========================================${NC}" -echo "" - -# Check if containers are running -check_container() { - local container=$1 - if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then - return 0 - else - return 1 - fi -} - -# Get database stats -get_db_stats() { - local container=$1 - local database=$2 - local label=$3 - - if ! check_container "$container"; then - echo -e "${RED}${label}: Container not running${NC}" - return - fi - - echo -e "${BLUE}=== ${label} ===${NC}" - - # Fuzzer registrations - echo -e "${YELLOW}Fuzzer Registrations:${NC}" - docker exec "$container" psql -U fuzzilli -d "$database" -c "SELECT fuzzer_id, fuzzer_name, status, created_at FROM main ORDER BY fuzzer_id;" 2>/dev/null || echo " No fuzzers registered" - - # Program counts - local program_count=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT COUNT(*) FROM fuzzer;" 2>/dev/null | tr -d ' ' || echo "0") - local execution_count=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT COUNT(*) FROM execution;" 2>/dev/null | tr -d ' ' || echo "0") - local program_table_count=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT COUNT(*) FROM program;" 2>/dev/null | tr -d ' ' || echo "0") - - # Crash count (excluding only FUZZILLI_CRASH test cases with signal 3) - # Show all other crashes including signal 11 and all other signals - local crash_count=$(docker exec "$container" psql -U fuzzilli -d "$database" -t -c "SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' AND (e.signal_code IS NULL OR e.signal_code != 3);" 2>/dev/null | tr -d ' ' || echo "0") - - echo -e "${YELLOW}Statistics:${NC}" - echo " Programs (corpus): $program_count" - echo " Programs (executed): $program_table_count" - echo " Executions: $execution_count" - echo " Crashes (excluding test cases - signal 3): $crash_count" - - # Recent activity (last 5 programs) - echo -e "${YELLOW}Recent Programs (last 5):${NC}" - docker exec "$container" psql -U fuzzilli -d "$database" -c "SELECT program_hash, program_size, created_at FROM fuzzer ORDER BY created_at DESC LIMIT 5;" 2>/dev/null || echo " No programs found" - - # Crash details (excluding only FUZZILLI_CRASH test cases - signal 3) - # Show all other crashes including signal 11 and all other signals - if [ "$crash_count" != "0" ] && [ "$crash_count" != "" ]; then - echo -e "${YELLOW}Crashes (last 3, excluding test cases - signal 3):${NC}" - docker exec "$container" psql -U fuzzilli -d "$database" -c "SELECT e.execution_id, e.program_hash, e.execution_time_ms, e.signal_code, e.exit_code, eo.description, e.created_at FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed' AND (e.signal_code IS NULL OR e.signal_code != 3) ORDER BY e.created_at DESC LIMIT 3;" 2>/dev/null || echo " No crash details available" - fi - - echo "" -} - -# Get all postgres-local-* containers -get_local_postgres_containers() { - docker ps --format '{{.Names}}' | grep '^postgres-local-' | sort -} - -# Get all fuzzer-worker-* containers -get_worker_containers() { - docker ps --format '{{.Names}}' | grep '^fuzzer-worker-' | sort -} - -# Get worker number from container name -get_worker_num() { - local container=$1 - echo "$container" | sed 's/.*-\([0-9]*\)$/\1/' -} - -# Get worker container stats -get_worker_stats() { - local worker_num=$1 - local container="fuzzer-worker-${worker_num}" - local postgres_container="postgres-local-${worker_num}" - - if ! check_container "$container"; then - echo -e "${RED}Worker ${worker_num}: Fuzzer container not running${NC}" - echo "" - return - fi - - echo -e "${GREEN}=== Worker ${worker_num} ===${NC}" - - # Container status - local status=$(docker inspect --format='{{.State.Status}}' "$container" 2>/dev/null) - local health=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "no-healthcheck") - echo -e "${YELLOW}Container Status:${NC} $status (health: $health)" - - # Get database stats - get_db_stats "$postgres_container" "fuzzilli_local" "Local Database" - - # Get recent logs - echo -e "${YELLOW}Recent Activity (last 3 lines):${NC}" - docker compose -f "$MASTER_COMPOSE" -f "$WORKERS_COMPOSE" logs --tail=3 "$container" 2>&1 | grep -v "level=warning" | tail -3 || echo " No recent activity" - echo "" -} - -# Master stats -echo -e "${GREEN}=== Master Database ===${NC}" -if check_container "fuzzilli-postgres-master"; then - status=$(docker inspect --format='{{.State.Status}}' "fuzzilli-postgres-master" 2>/dev/null) - health=$(docker inspect --format='{{.State.Health.Status}}' "fuzzilli-postgres-master" 2>/dev/null || echo "no-healthcheck") - echo -e "${YELLOW}Container Status:${NC} $status (health: $health)" - echo "" - get_db_stats "fuzzilli-postgres-master" "fuzzilli_master" "Master Database" -else - echo -e "${RED}Master container not running${NC}" - echo "" -fi - -# Worker stats - dynamically discover all workers -local_postgres_containers=($(get_local_postgres_containers)) -if [ ${#local_postgres_containers[@]} -eq 0 ]; then - echo -e "${YELLOW}No postgres-local-* containers found${NC}" - echo "" -else - for postgres_container in "${local_postgres_containers[@]}"; do - worker_num=$(get_worker_num "$postgres_container") - get_worker_stats "$worker_num" - done -fi - -# Summary -echo -e "${CYAN}========================================${NC}" -echo -e "${CYAN} Summary${NC}" -echo -e "${CYAN}========================================${NC}" - -if check_container "fuzzilli-postgres-master"; then - master_programs=$(docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -t -c "SELECT COUNT(*) FROM fuzzer;" 2>/dev/null | tr -d ' ' || echo "0") - master_executions=$(docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -t -c "SELECT COUNT(*) FROM execution;" 2>/dev/null | tr -d ' ' || echo "0") - echo -e "Master: ${GREEN}${master_programs}${NC} programs, ${GREEN}${master_executions}${NC} executions" -fi - -# Dynamically get stats for all local postgres containers -local_postgres_containers=($(get_local_postgres_containers)) -if [ ${#local_postgres_containers[@]} -gt 0 ]; then - for postgres_container in "${local_postgres_containers[@]}"; do - worker_num=$(get_worker_num "$postgres_container") - if check_container "$postgres_container"; then - worker_programs=$(docker exec "$postgres_container" psql -U fuzzilli -d fuzzilli_local -t -c "SELECT COUNT(*) FROM fuzzer;" 2>/dev/null | tr -d ' ' || echo "0") - worker_executions=$(docker exec "$postgres_container" psql -U fuzzilli -d fuzzilli_local -t -c "SELECT COUNT(*) FROM execution;" 2>/dev/null | tr -d ' ' || echo "0") - echo -e "Worker ${worker_num}: ${GREEN}${worker_programs}${NC} programs, ${GREEN}${worker_executions}${NC} executions" - fi - done -fi - -echo "" -echo -e "${CYAN}========================================${NC}" - diff --git a/Scripts/start-distributed.sh b/Scripts/start-distributed.sh index c15c21e4d..0aee731d3 100755 --- a/Scripts/start-distributed.sh +++ b/Scripts/start-distributed.sh @@ -51,7 +51,6 @@ echo "Starting Distributed Fuzzilli" echo "==========================================" echo "Workers: $NUM_WORKERS" echo "Master Postgres: 1" -echo "Local Postgres: $NUM_WORKERS" echo "" # Load environment variables @@ -64,7 +63,6 @@ fi # Set defaults POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-fuzzilli123} V8_BUILD_PATH=${V8_BUILD_PATH:-/home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild} -SYNC_INTERVAL=${SYNC_INTERVAL:-60} TIMEOUT=${TIMEOUT:-2500} MIN_MUTATIONS_PER_SAMPLE=${MIN_MUTATIONS_PER_SAMPLE:-25} DEBUG_LOGGING=${DEBUG_LOGGING:-false} @@ -77,7 +75,6 @@ fi echo "Configuration:" echo " V8 Build Path: ${V8_BUILD_PATH}" -echo " Sync Interval: ${SYNC_INTERVAL}s" echo " Timeout: ${TIMEOUT}ms" echo "" @@ -88,31 +85,10 @@ version: '3.8' services: EOF -# Generate worker services (local postgres + fuzzer) +# Generate worker services (fuzzer only, no local postgres) for i in $(seq 1 $NUM_WORKERS); do cat >> "${WORKER_COMPOSE}" <> "${WORKER_COMPOSE}" < /dev/null 2>&1; then - echo "✓ Local postgres-${i} is ready" - break - fi - sleep 1 - timeout=$((timeout - 1)) - done - if [ $timeout -eq 0 ]; then - echo "⚠ Warning: Local postgres-${i} may not be ready" - fi -done - # Wait a bit for fuzzers to start echo "" echo "Waiting for fuzzer containers to initialize..." @@ -230,7 +183,7 @@ echo "" echo "Services started:" echo " Master Postgres: fuzzilli-postgres-master" for i in $(seq 1 $NUM_WORKERS); do - echo " Worker $i: fuzzer-worker-${i} (local postgres: postgres-local-${i})" + echo " Worker $i: fuzzer-worker-${i}" done echo "" echo "To view logs:" @@ -246,5 +199,5 @@ echo "To stop all services:" echo " docker compose -f ${MASTER_COMPOSE} -f ${WORKER_COMPOSE} down" echo "" echo "To stop a specific worker:" -echo " docker stop fuzzer-worker- postgres-local-" +echo " docker stop fuzzer-worker-" echo "" diff --git a/Scripts/test-distributed-sync.sh b/Scripts/test-distributed-sync.sh deleted file mode 100755 index 719d3c6be..000000000 --- a/Scripts/test-distributed-sync.sh +++ /dev/null @@ -1,313 +0,0 @@ -#!/bin/bash - -# test-distributed-sync.sh - Integration test for distributed PostgreSQL sync -# This script tests: -# 1. Push sync: Workers push their corpus to main database -# 2. Pull sync: Workers pull corpus from main database -# 3. Fuzzing activity: Workers are actually fuzzing -# 4. Database updates: Database updates correctly reflect fuzzing -# 5. Corpus visibility: Fuzzers see new information in corpus - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# Test configuration -NUM_WORKERS=${1:-2} -SYNC_INTERVAL=${SYNC_INTERVAL:-60} # Use shorter interval for testing -TEST_TIMEOUT=${TEST_TIMEOUT:-300} # 5 minutes max -POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-fuzzilli123} - -# Test results -TESTS_PASSED=0 -TESTS_FAILED=0 - -echo "==========================================" -echo "Distributed PostgreSQL Sync Integration Test" -echo "==========================================" -echo "Workers: $NUM_WORKERS" -echo "Sync Interval: ${SYNC_INTERVAL}s" -echo "Test Timeout: ${TEST_TIMEOUT}s" -echo "==========================================" -echo "" - -# Helper functions -log_info() { - echo -e "${GREEN}[INFO]${NC} $1" -} - -log_warn() { - echo -e "${YELLOW}[WARN]${NC} $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -test_pass() { - TESTS_PASSED=$((TESTS_PASSED + 1)) - log_info "✓ $1" -} - -test_fail() { - TESTS_FAILED=$((TESTS_FAILED + 1)) - log_error "✗ $1" -} - -test_warn() { - log_warn "⚠ $1" -} - -# Cleanup function -cleanup() { - log_info "Cleaning up test environment..." - cd "${PROJECT_ROOT}" - docker-compose -f docker-compose.distributed.yml -f docker-compose.workers.yml down -v 2>/dev/null || true - rm -f "${PROJECT_ROOT}/docker-compose.workers.yml" -} - -trap cleanup EXIT - -# Start distributed system -log_info "Starting distributed system with $NUM_WORKERS workers..." -cd "${PROJECT_ROOT}" -"${SCRIPT_DIR}/start-distributed.sh" "$NUM_WORKERS" > /dev/null 2>&1 - -# Wait for all services to be ready -log_info "Waiting for services to be ready..." -sleep 30 - -# Verify services are running -log_info "Verifying services are running..." -MASTER_RUNNING=$(docker ps --filter "name=fuzzilli-postgres-master" --format "{{.Names}}" | wc -l) -if [ "$MASTER_RUNNING" -eq 1 ]; then - test_pass "Main postgres container is running" -else - test_fail "Main postgres container is not running" - exit 1 -fi - -WORKERS_RUNNING=$(docker ps --filter "name=fuzzer-worker" --format "{{.Names}}" | wc -l) -if [ "$WORKERS_RUNNING" -eq "$NUM_WORKERS" ]; then - test_pass "All $NUM_WORKERS worker containers are running" -else - test_fail "Expected $NUM_WORKERS workers, found $WORKERS_RUNNING" - exit 1 -fi - -LOCAL_POSTGRES_RUNNING=$(docker ps --filter "name=postgres-local" --format "{{.Names}}" | wc -l) -if [ "$LOCAL_POSTGRES_RUNNING" -eq "$NUM_WORKERS" ]; then - test_pass "All $NUM_WORKERS local postgres containers are running" -else - test_fail "Expected $NUM_WORKERS local postgres containers, found $LOCAL_POSTGRES_RUNNING" - exit 1 -fi - -# Test 1: Push Sync - Insert test program into local database and verify it appears in master -log_info "" -log_info "Test 1: Push Sync" -log_info "==================" - -# Generate a test program hash (simplified - in real scenario, this would be a proper FuzzIL program) -TEST_HASH="test_push_$(date +%s)" -TEST_PROGRAM_B64="dGVzdF9wcm9ncmFt" # base64("test_program") - -# Insert test program into first worker's local database -log_info "Inserting test program into worker 1 local database..." -docker exec postgres-local-1 psql -U fuzzilli -d fuzzilli_local -c " - INSERT INTO main (fuzzer_name, engine_type, status) VALUES ('fuzzer-1', 'v8', 'active') ON CONFLICT DO NOTHING; - SELECT fuzzer_id INTO TEMP temp_fuzzer_id FROM main WHERE fuzzer_name = 'fuzzer-1'; - INSERT INTO fuzzer (program_hash, fuzzer_id, program_size, program_base64, inserted_at) - SELECT '$TEST_HASH', fuzzer_id, 100, '$TEST_PROGRAM_B64', NOW() - FROM temp_fuzzer_id; -" > /dev/null 2>&1 - -if [ $? -eq 0 ]; then - test_pass "Test program inserted into local database" -else - test_fail "Failed to insert test program into local database" -fi - -# Wait for sync interval -log_info "Waiting ${SYNC_INTERVAL}s for push sync..." -sleep $((SYNC_INTERVAL + 10)) - -# Check if program appears in master database -log_info "Checking if program appears in master database..." -PROGRAM_IN_MASTER=$(docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -t -c " - SELECT COUNT(*) FROM fuzzer WHERE program_hash = '$TEST_HASH'; -" | tr -d ' ') - -if [ "$PROGRAM_IN_MASTER" -gt 0 ]; then - test_pass "Test program synced to master database (push sync working)" -else - test_fail "Test program not found in master database (push sync may have failed)" -fi - -# Test 2: Pull Sync - Insert test program into master and verify it appears in worker local databases -log_info "" -log_info "Test 2: Pull Sync" -log_info "==================" - -TEST_PULL_HASH="test_pull_$(date +%s)" - -# Insert test program into master database (simulating another worker) -log_info "Inserting test program into master database (simulating another worker)..." -docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -c " - INSERT INTO main (fuzzer_name, engine_type, status) VALUES ('fuzzer-test-source', 'v8', 'active') ON CONFLICT DO NOTHING; - SELECT fuzzer_id INTO TEMP temp_fuzzer_id FROM main WHERE fuzzer_name = 'fuzzer-test-source'; - INSERT INTO fuzzer (program_hash, fuzzer_id, program_size, program_base64, inserted_at) - SELECT '$TEST_PULL_HASH', fuzzer_id, 100, '$TEST_PROGRAM_B64', NOW() - FROM temp_fuzzer_id; -" > /dev/null 2>&1 - -if [ $? -eq 0 ]; then - test_pass "Test program inserted into master database" -else - test_fail "Failed to insert test program into master database" -fi - -# Wait for sync interval -log_info "Waiting ${SYNC_INTERVAL}s for pull sync..." -sleep $((SYNC_INTERVAL + 10)) - -# Check if program appears in worker local databases -log_info "Checking if program appears in worker local databases..." -WORKER_WITH_PROGRAM=0 -for i in $(seq 1 $NUM_WORKERS); do - COUNT=$(docker exec postgres-local-${i} psql -U fuzzilli -d fuzzilli_local -t -c " - SELECT COUNT(*) FROM fuzzer WHERE program_hash = '$TEST_PULL_HASH'; - " 2>/dev/null | tr -d ' ' || echo "0") - - if [ "$COUNT" -gt 0 ]; then - WORKER_WITH_PROGRAM=$((WORKER_WITH_PROGRAM + 1)) - fi -done - -if [ "$WORKER_WITH_PROGRAM" -gt 0 ]; then - test_pass "Test program pulled to $WORKER_WITH_PROGRAM worker(s) (pull sync working)" -else - test_fail "Test program not found in any worker local database (pull sync may have failed)" -fi - -# Test 3: Fuzzing Activity -log_info "" -log_info "Test 3: Fuzzing Activity" -log_info "========================" - -# Check worker logs for fuzzing activity -log_info "Checking worker logs for fuzzing activity..." -FUZZING_DETECTED=0 -for i in $(seq 1 $NUM_WORKERS); do - if docker logs fuzzer-worker-${i} 2>&1 | grep -qiE "(fuzzing|execution|program)" | head -5 | grep -q .; then - FUZZING_DETECTED=$((FUZZING_DETECTED + 1)) - fi -done - -if [ "$FUZZING_DETECTED" -gt 0 ]; then - test_pass "Fuzzing activity detected in $FUZZING_DETECTED worker(s)" -else - test_warn "No clear fuzzing activity detected in logs (workers may still be starting)" -fi - -# Check execution counts in databases -log_info "Checking execution counts in databases..." -MASTER_EXECUTIONS=$(docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -t -c " - SELECT COUNT(*) FROM execution; -" 2>/dev/null | tr -d ' ' || echo "0") - -if [ "$MASTER_EXECUTIONS" -gt 0 ]; then - test_pass "Executions found in master database: $MASTER_EXECUTIONS" -else - test_warn "No executions found in master database yet (workers may still be starting)" -fi - -# Test 4: Database Updates -log_info "" -log_info "Test 4: Database Updates" -log_info "=======================" - -# Check program counts -MASTER_PROGRAMS=$(docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -t -c " - SELECT COUNT(*) FROM program; -" 2>/dev/null | tr -d ' ' || echo "0") - -LOCAL_PROGRAMS_TOTAL=0 -for i in $(seq 1 $NUM_WORKERS); do - COUNT=$(docker exec postgres-local-${i} psql -U fuzzilli -d fuzzilli_local -t -c " - SELECT COUNT(*) FROM program; - " 2>/dev/null | tr -d ' ' || echo "0") - LOCAL_PROGRAMS_TOTAL=$((LOCAL_PROGRAMS_TOTAL + COUNT)) -done - -log_info "Master database programs: $MASTER_PROGRAMS" -log_info "Total local database programs: $LOCAL_PROGRAMS_TOTAL" - -if [ "$MASTER_PROGRAMS" -gt 0 ] || [ "$LOCAL_PROGRAMS_TOTAL" -gt 0 ]; then - test_pass "Database updates are being recorded (programs found)" -else - test_warn "No programs found in databases yet (workers may still be starting)" -fi - -# Test 5: Corpus Visibility -log_info "" -log_info "Test 5: Corpus Visibility" -log_info "========================" - -# Insert a program with a known hash into master -CORPUS_TEST_HASH="corpus_visibility_$(date +%s)" -log_info "Inserting test program for corpus visibility test..." -docker exec fuzzilli-postgres-master psql -U fuzzilli -d fuzzilli_master -c " - INSERT INTO main (fuzzer_name, engine_type, status) VALUES ('fuzzer-corpus-test', 'v8', 'active') ON CONFLICT DO NOTHING; - SELECT fuzzer_id INTO TEMP temp_fuzzer_id FROM main WHERE fuzzer_name = 'fuzzer-corpus-test'; - INSERT INTO fuzzer (program_hash, fuzzer_id, program_size, program_base64, inserted_at) - SELECT '$CORPUS_TEST_HASH', fuzzer_id, 100, '$TEST_PROGRAM_B64', NOW() - FROM temp_fuzzer_id; -" > /dev/null 2>&1 - -# Wait for pull sync -log_info "Waiting ${SYNC_INTERVAL}s for corpus visibility sync..." -sleep $((SYNC_INTERVAL + 10)) - -# Check if program can be retrieved from worker databases -VISIBLE_IN_WORKERS=0 -for i in $(seq 1 $NUM_WORKERS); do - COUNT=$(docker exec postgres-local-${i} psql -U fuzzilli -d fuzzilli_local -t -c " - SELECT COUNT(*) FROM fuzzer WHERE program_hash = '$CORPUS_TEST_HASH'; - " 2>/dev/null | tr -d ' ' || echo "0") - - if [ "$COUNT" -gt 0 ]; then - VISIBLE_IN_WORKERS=$((VISIBLE_IN_WORKERS + 1)) - fi -done - -if [ "$VISIBLE_IN_WORKERS" -gt 0 ]; then - test_pass "Corpus visibility test passed: program visible in $VISIBLE_IN_WORKERS worker(s)" -else - test_fail "Corpus visibility test failed: program not visible in worker databases" -fi - -# Summary -echo "" -echo "==========================================" -echo "Test Summary" -echo "==========================================" -echo "Tests Passed: $TESTS_PASSED" -echo "Tests Failed: $TESTS_FAILED" -echo "==========================================" - -if [ $TESTS_FAILED -eq 0 ]; then - log_info "All tests passed!" - exit 0 -else - log_error "Some tests failed. Check the output above for details." - exit 1 -fi - diff --git a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift index 450b2d246..b639f9162 100644 --- a/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift +++ b/Sources/Fuzzilli/Corpus/PostgreSQLCorpus.swift @@ -2,17 +2,15 @@ import Foundation import PostgresNIO import PostgresKit -/// PostgreSQL-based corpus with in-memory caching for distributed fuzzing. +/// PostgreSQL-based corpus for distributed fuzzing. /// -/// This corpus maintains a local in-memory cache of programs and their execution metadata, -/// while synchronizing with a central PostgreSQL database. Each fuzzer instance maintains -/// its own cache and periodically syncs with the master database. +/// This corpus connects directly to a master PostgreSQL database. Each fuzzer instance +/// stores and retrieves programs directly from the master database without local caching. /// /// Features: -/// - In-memory caching for fast access -/// - PostgreSQL backend for persistence and sharing +/// - Direct master database connection /// - Execution metadata tracking (coverage, execution count, etc.) -/// - Periodic synchronization with central database +/// - Dynamic batching based on execution speed /// - Thread-safe operations public class PostgreSQLCorpus: ComponentBase, Corpus { @@ -21,59 +19,40 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { private let minSize: Int private let maxSize: Int private let minMutationsPerSample: Int - private let syncInterval: TimeInterval - private let databasePool: DatabasePool // Local database pool - private let masterDatabasePool: DatabasePool? // Optional master database pool for sync + private let databasePool: DatabasePool // Master database pool private let fuzzerInstanceId: String - private let storage: PostgreSQLStorage // Local storage - private let masterStorage: PostgreSQLStorage? // Optional master storage for sync + private let storage: PostgreSQLStorage // Master storage private let resume: Bool private let enableLogging: Bool - // MARK: - In-Memory Cache - - /// Thread-safe in-memory cache of programs and their metadata - private var programCache: [String: (program: Program, metadata: ExecutionMetadata)] = [:] - private let cacheLock = NSLock() - - /// Ring buffer for fast random access (similar to BasicCorpus) - private var programs: RingBuffer - private var ages: RingBuffer - private var programHashes: RingBuffer // Track hashes for database operations - - /// Counts the total number of entries in the corpus - private var totalEntryCounter = 0 - - /// Track pending database operations - private var pendingSyncOperations: Set = [] - private let syncLock = NSLock() - /// Track current execution for event handling private var currentExecutionProgram: Program? private var currentExecutionPurpose: ExecutionPurpose? /// Track fuzzer registration status private var fuzzerRegistered = false - private var fuzzerId: Int? // Local database fuzzer ID - private var masterFuzzerId: Int? // Master database fuzzer ID (for sync operations) + private var fuzzerId: Int? // Master database fuzzer ID /// Batch execution storage private var pendingExecutions: [(Program, ProgramAspects, DatabaseExecutionPurpose)] = [] - private let executionBatchSize: Int + private var executionBatchSize: Int // Dynamic batch size private let executionBatchLock = NSLock() + /// Cache for recently accessed programs to avoid repeated DB queries + private var recentProgramCache: [String: Program] = [:] + private let recentCacheLock = NSLock() + private let maxRecentCacheSize = 1000 // Keep only recent 1000 programs in memory + // MARK: - Initialization public init( minSize: Int, maxSize: Int, minMutationsPerSample: Int, - databasePool: DatabasePool, // Local database pool + databasePool: DatabasePool, // Master database pool fuzzerInstanceId: String, - syncInterval: TimeInterval = 60.0, // Default 1 minute sync interval resume: Bool = true, // Default to resume from previous state - enableLogging: Bool = false, - masterDatabasePool: DatabasePool? = nil // Optional master database pool for sync + enableLogging: Bool = false ) { // The corpus must never be empty assert(minSize >= 1) @@ -83,28 +62,22 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { self.maxSize = maxSize self.minMutationsPerSample = minMutationsPerSample self.databasePool = databasePool - self.masterDatabasePool = masterDatabasePool self.fuzzerInstanceId = fuzzerInstanceId - self.syncInterval = syncInterval self.resume = resume self.enableLogging = enableLogging self.storage = PostgreSQLStorage(databasePool: databasePool, enableLogging: enableLogging) - self.masterStorage = masterDatabasePool.map { PostgreSQLStorage(databasePool: $0, enableLogging: enableLogging) } - // Set optimized batch size for better throughput (reduced from 1M to 100k for more frequent processing) + // Initialize with default batch size, will be updated dynamically self.executionBatchSize = 100_000 - self.programs = RingBuffer(maxSize: maxSize) - self.ages = RingBuffer(maxSize: maxSize) - self.programHashes = RingBuffer(maxSize: maxSize) - super.init(name: "PostgreSQLCorpus") // Setup signal handlers for graceful shutdown setupSignalHandlers() - // Start periodic batch flushing for better throughput + // Start periodic batch flushing and batch size recalculation startPeriodicBatchFlush() + startPeriodicBatchSizeUpdate() } deinit { @@ -138,6 +111,45 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } } + /// Recalculate batch size based on execution speed (execs/sec * 3600 for hourly batches) + private func startPeriodicBatchSizeUpdate() { + // Update batch size every 5 minutes + Task { + while true { + try? await Task.sleep(nanoseconds: 5 * 60 * 1_000_000_000) // 5 minutes + updateBatchSize() + } + } + } + + /// Calculate and update dynamic batch size based on execution speed + private func updateBatchSize() { + guard let statsModule = Statistics.instance(for: fuzzer) else { + // If statistics module not available, use default batch size + if enableLogging { + logger.warning("Statistics module not available, using default batch size") + } + return + } + + let stats = statsModule.compute() + let execsPerSecond = stats.execsPerSecond + + // Calculate executions per hour: execs/sec * 60 sec/min * 60 min/hour + let calculatedBatchSize = Int(execsPerSecond * 60.0 * 60.0) + + // Ensure minimum batch size (1000) and maximum (1M) + let newBatchSize = max(1000, min(1_000_000, calculatedBatchSize)) + + executionBatchLock.withLock { + executionBatchSize = newBatchSize + } + + if enableLogging { + logger.info("Updated execution batch size: \(newBatchSize) (based on \(String(format: "%.2f", execsPerSecond)) execs/sec)") + } + } + // MARK: - Signal Handling and Early Exit private func setupSignalHandlers() { @@ -174,9 +186,9 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { if !queuedExecutions.isEmpty { await processExecutionBatch(queuedExecutions) - if enableLogging { - self.logger.info("Committed \(queuedExecutions.count) pending executions on exit") - } + if enableLogging { + self.logger.info("Committed \(queuedExecutions.count) pending executions on exit") + } } } @@ -187,52 +199,23 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { // Initialize database pool and register fuzzer (only once) Task { do { - // Initialize local database pool + // Initialize master database pool try await databasePool.initialize() if enableLogging { - self.logger.info("Local database pool initialized successfully") - } - - // Initialize master database pool if available - if let masterPool = masterDatabasePool { - try await masterPool.initialize() - if enableLogging { - self.logger.info("Master database pool initialized successfully") - } + self.logger.info("Master database pool initialized successfully") } - // Register this fuzzer instance in the database (only once) + // Register this fuzzer instance in the master database (only once) if !fuzzerRegistered { do { - // Register in master database first (for sync) - let masterId: Int? - if let masterStorage = masterStorage { - masterId = try await masterStorage.registerFuzzer( - name: fuzzerInstanceId, - engineType: "v8" - ) - if enableLogging { - self.logger.info("Fuzzer registered in master database with ID: \(masterId ?? -1)") - } - } else { - masterId = nil - } - - // Register in local database (for local storage) - let localId = try await storage.registerFuzzer( + let id = try await storage.registerFuzzer( name: fuzzerInstanceId, engineType: "v8" ) - if enableLogging { - self.logger.info("Fuzzer registered in local database with ID: \(localId)") - } - - // Use local ID for local operations, master ID is used for sync operations - fuzzerId = localId - masterFuzzerId = masterId + fuzzerId = id fuzzerRegistered = true if enableLogging { - self.logger.info("Fuzzer registration complete - local ID: \(localId), master ID: \(masterId?.description ?? "none")") + self.logger.info("Fuzzer registered in master database with ID: \(id)") } } catch { logger.error("Failed to register fuzzer after retries: \(error)") @@ -319,20 +302,6 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } } - // Schedule periodic synchronization with PostgreSQL (push to master) - fuzzer.timers.scheduleTask(every: syncInterval, syncWithDatabase) - if self.enableLogging { - logger.info("Scheduled database sync (push) every \(syncInterval) seconds") - } - - // Schedule periodic pull sync from master (if master storage is available) - if masterStorage != nil { - fuzzer.timers.scheduleTask(every: syncInterval, pullFromMaster) - if self.enableLogging { - logger.info("Scheduled database sync (pull) every \(syncInterval) seconds") - } - } - // Schedule periodic flush of execution batch fuzzer.timers.scheduleTask(every: 5.0, flushExecutionBatch) if enableLogging { @@ -345,25 +314,24 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { logger.info("Scheduled fuzzer registration retry every 30 seconds") } - // Schedule cleanup task (similar to BasicCorpus) - if !fuzzer.config.staticCorpus { - fuzzer.timers.scheduleTask(every: 30 * Minutes, cleanup) + // Schedule periodic batch size update + fuzzer.timers.scheduleTask(every: 5 * Minutes, updateBatchSize) + if enableLogging { + logger.info("Scheduled batch size update every 5 minutes") } - // Load initial corpus from database if resume is enabled - if resume { - Task { - await loadInitialCorpus() - } - } + // Load initial batch size from current execution speed + updateBatchSize() } // MARK: - Corpus Protocol Implementation public var size: Int { - cacheLock.lock() - defer { cacheLock.unlock() } - return programs.count + // Query database for corpus size + guard let fuzzerId = fuzzerId else { return 0 } + // Use a cached value that gets updated periodically, or query synchronously + // For now, return a placeholder - this will be improved with async queries + return 0 // Will be updated to query DB } public var isEmpty: Bool { @@ -375,7 +343,50 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } public func add(_ program: Program, _ aspects: ProgramAspects) { - addInternal(program, aspects: aspects) + guard program.size > 0 else { return } + + // Filter out test programs with FUZZILLI_CRASH + if DatabaseUtils.containsFuzzilliCrash(program: program) { + if enableLogging { + logger.info("Skipping program with FUZZILLI_CRASH (test case)") + } + return + } + + guard let fuzzerId = fuzzerId else { + if enableLogging { + logger.warning("Cannot add program: fuzzer not registered") + } + return + } + + // Store program directly to master DB asynchronously + Task { + do { + let programHash = DatabaseUtils.calculateProgramHash(program: program) + let metadata = ExecutionMetadata(lastOutcome: DatabaseExecutionOutcome( + id: DatabaseUtils.mapExecutionOutcome(outcome: aspects.outcome), + outcome: aspects.outcome.description, + description: aspects.outcome.description + )) + + // Add to recent cache for fast access + recentCacheLock.withLock { + recentProgramCache[programHash] = program + // Limit cache size + if recentProgramCache.count > maxRecentCacheSize { + let oldestKey = recentProgramCache.keys.first + if let key = oldestKey { + recentProgramCache.removeValue(forKey: key) + } + } + } + + _ = try await storage.storeProgram(program: program, fuzzerId: fuzzerId, metadata: metadata) + } catch { + logger.error("Failed to store program in database: \(error)") + } + } } /// Add execution to batch for later processing @@ -383,7 +394,8 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { // Use atomic operations to avoid blocking locks let shouldProcessBatch: [(Program, ProgramAspects, DatabaseExecutionPurpose)]? = executionBatchLock.withLock { pendingExecutions.append((program, aspects, executionType)) - let shouldProcess = pendingExecutions.count >= executionBatchSize + let currentBatchSize = executionBatchSize + let shouldProcess = pendingExecutions.count >= currentBatchSize if shouldProcess { let batch = pendingExecutions pendingExecutions.removeAll() @@ -457,13 +469,13 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { executionBatchData.append(executionData) } - // Batch store programs (only unique ones) + // Batch store programs (only unique ones) directly to master DB let programBatch = Array(uniquePrograms.values) if !programBatch.isEmpty { _ = try await storage.storeProgramsBatch(programs: programBatch, fuzzerId: fuzzerId) } - // Batch store executions + // Batch store executions directly to master DB if !executionBatchData.isEmpty { _ = try await storage.storeExecutionsBatch(executions: executionBatchData, fuzzerId: fuzzerId) } @@ -528,103 +540,43 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } } - public func addInternal(_ program: Program, aspects: ProgramAspects? = nil) { - guard program.size > 0 else { return } - - // Filter out test programs with FUZZILLI_CRASH (false positive crashes) - if DatabaseUtils.containsFuzzilliCrash(program: program) { - if enableLogging { - logger.info("Skipping program with FUZZILLI_CRASH (test case)") - } - return - } - - let programHash = DatabaseUtils.calculateProgramHash(program: program) - - cacheLock.lock() - defer { cacheLock.unlock() } - - // Check if program already exists in cache - if programCache[programHash] != nil { - // Update execution metadata if aspects provided - if let aspects = aspects { - updateExecutionMetadata(for: programHash, aspects: aspects) - } - return - } - - // Create execution metadata - let outcome = DatabaseExecutionOutcome( - id: DatabaseUtils.mapExecutionOutcome(outcome: aspects?.outcome ?? .succeeded), - outcome: aspects?.outcome.description ?? "Succeeded", - description: aspects?.outcome.description ?? "Program executed successfully" - ) - - var metadata = ExecutionMetadata(lastOutcome: outcome) - if let aspects = aspects { - updateExecutionMetadata(&metadata, aspects: aspects) - } - - // Add to in-memory structures - prepareProgramForInclusion(program, index: totalEntryCounter) - programs.append(program) - ages.append(0) - programHashes.append(programHash) - programCache[programHash] = (program: program, metadata: metadata) - - totalEntryCounter += 1 - - // Mark for database sync - markForSync(programHash) - - // Program added to corpus silently for performance - } - public func randomElementForSplicing() -> Program { - cacheLock.lock() - defer { cacheLock.unlock() } + // Try to get from recent cache first + if let cached = recentCacheLock.withLock({ recentProgramCache.values.randomElement() }) { + return cached + } - assert(programs.count > 0, "Corpus should never be empty") - let idx = Int.random(in: 0.. Program { - cacheLock.lock() - defer { cacheLock.unlock() } - - assert(programs.count > 0, "Corpus should never be empty") - let idx = Int.random(in: 0.. [Program] { - cacheLock.lock() - defer { cacheLock.unlock() } - return Array(programs) + // Return programs from recent cache + return recentCacheLock.withLock { + Array(recentProgramCache.values) + } } public func exportState() throws -> Data { - cacheLock.lock() - defer { cacheLock.unlock() } - - let res = try encodeProtobufCorpus(Array(programs)) + // Export from recent cache + let programs = recentCacheLock.withLock { + Array(recentProgramCache.values) + } + let res = try encodeProtobufCorpus(programs) if enableLogging { self.logger.info("Successfully serialized \(programs.count) programs from PostgreSQL corpus") } @@ -634,547 +586,46 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { public func importState(_ buffer: Data) throws { let newPrograms = try decodeProtobufCorpus(buffer) - cacheLock.lock() - defer { cacheLock.unlock() } - - programs.removeAll() - ages.removeAll() - programHashes.removeAll() - programCache.removeAll() - - newPrograms.forEach { program in - addInternal(program) - } - - if enableLogging { - self.logger.info("Imported \(newPrograms.count) programs into PostgreSQL corpus") - } - } - - // MARK: - Database Operations - - /// Load initial corpus from PostgreSQL database - private func loadInitialCorpus() async { - if enableLogging { - self.logger.info("Loading initial corpus from PostgreSQL...") - } - guard let fuzzerId = fuzzerId else { - logger.warning("Cannot load initial corpus: fuzzer not registered") - return + throw PostgreSQLStorageError.connectionFailed } - do { - // Load programs from the last 24 hours to resume recent work - let since = Date().addingTimeInterval(-24 * 60 * 60) // 24 hours ago - let recentPrograms = try await storage.getRecentPrograms( - fuzzerId: fuzzerId, - since: since - ) - - if enableLogging { - self.logger.info("Found \(recentPrograms.count) recent programs to resume") - } - - // Add programs to the corpus - withLock(cacheLock) { - for (program, metadata) in recentPrograms { - let programHash = DatabaseUtils.calculateProgramHash(program: program) - - // Skip if already in cache - if programCache[programHash] != nil { - continue - } - - // Add to in-memory structures - prepareProgramForInclusion(program, index: totalEntryCounter) - programs.append(program) - ages.append(0) - programHashes.append(programHash) - programCache[programHash] = (program: program, metadata: metadata) - - totalEntryCounter += 1 - } - } - - if enableLogging { - self.logger.info("Resumed PostgreSQL corpus with \(programs.count) programs") - } - - // If we have no programs, we need at least one to avoid empty corpus - if programs.count == 0 { - if enableLogging { - self.logger.info("No programs found to resume, corpus will start empty") - } - } - - } catch { - logger.error("Failed to load initial corpus from PostgreSQL: \(error)") - if enableLogging { - self.logger.info("Corpus will start empty and build up from scratch") - } - } - } - - /// Synchronize with PostgreSQL database - private func syncWithDatabase() { - if enableLogging { - logger.info("syncWithDatabase() called - starting sync task") - } + // Store all programs to database Task { - do { - await performDatabaseSync() - } catch { - logger.error("syncWithDatabase() failed: \(error)") - } - } - } - - /// Perform actual database synchronization - private func performDatabaseSync() async { - // Sync ALL programs from local database to master, not just from cache - // This ensures the master has the complete corpus from this worker - if enableLogging { - logger.info("performDatabaseSync() started - masterStorage: \(masterStorage != nil), masterId: \(masterFuzzerId?.description ?? "nil"), localId: \(fuzzerId?.description ?? "nil")") - } - guard let masterStorage = masterStorage, let masterId = masterFuzzerId, let localId = fuzzerId else { - if enableLogging { - logger.warning("performDatabaseSync() aborted - missing required values: masterStorage=\(masterStorage != nil), masterId=\(masterFuzzerId?.description ?? "nil"), localId=\(fuzzerId?.description ?? "nil")") - } - return - } - - do { - // Get ALL programs from local database - no date filter, no limits - if enableLogging { - logger.info("Querying local database for ALL programs: fuzzerId=\(localId)") - } - let localPrograms = try await storage.getRecentPrograms( - fuzzerId: localId, - since: Date(timeIntervalSince1970: 0), // This will be ignored in the query now - limit: nil // No limit - get all programs - ) - - if enableLogging { - logger.info("Retrieved \(localPrograms.count) programs from local database") - } - - if localPrograms.isEmpty { - if enableLogging { - logger.info("No programs in local database to sync") - } - return - } - - // Deduplicate programs by hash (in case query returned duplicates) - var uniquePrograms: [String: (Program, ExecutionMetadata)] = [:] - for (program, metadata) in localPrograms { - let hash = DatabaseUtils.calculateProgramHash(program: program) - // Keep the first occurrence (or you could merge metadata) - if uniquePrograms[hash] == nil { - uniquePrograms[hash] = (program, metadata) - } - } - - let deduplicatedPrograms = Array(uniquePrograms.values) - - if enableLogging { - logger.info("Deduplicated to \(deduplicatedPrograms.count) unique programs (from \(localPrograms.count) total)") - } - - // Store ALL programs in the master database using BATCH storage - if enableLogging { - logger.info("Syncing \(deduplicatedPrograms.count) unique programs from local database to master database (fuzzerId=\(masterId))") - } - - do { - // Use batch storage for better performance - let programHashes = try await masterStorage.storeProgramsBatch( - programs: deduplicatedPrograms, - fuzzerId: masterId - ) - - if enableLogging { - logger.info("✅ Successfully batch synced \(programHashes.count) programs to master database (fuzzerId=\(masterId))") - if programHashes.count > 0 { - logger.info(" Sample program hashes: \(programHashes.prefix(5).joined(separator: ", "))\(programHashes.count > 5 ? "..." : "")") - } - } - } catch { - logger.error("❌ Failed to batch sync programs to master: \(error)") - // Fallback to individual storage if batch fails - if enableLogging { - logger.info("Attempting individual program storage as fallback...") - } - var syncedCount = 0 - var failedCount = 0 - for (program, metadata) in deduplicatedPrograms { - do { - _ = try await masterStorage.storeProgram( - program: program, - fuzzerId: masterId, - metadata: metadata - ) - syncedCount += 1 - } catch { - failedCount += 1 - if enableLogging { - logger.error("Failed to sync individual program to master: \(error)") - } - } - } - if enableLogging { - logger.info("Fallback sync completed: \(syncedCount) succeeded, \(failedCount) failed") - } - // Re-throw if all failed - if syncedCount == 0 && failedCount > 0 { - throw error - } - } - - // TODO: Execution sync temporarily disabled - need to implement getRecentExecutions method - // Sync ALL executions from local database to master - no date filter, no limits - // For now, skip execution sync and focus on program sync - if enableLogging { - logger.info("Execution sync temporarily disabled - focusing on program sync") - } - - /* Execution sync code - temporarily disabled - let localExecutions = try await storage.getRecentExecutions( - fuzzerId: localId, - since: Date(timeIntervalSince1970: 0), - limit: nil - ) - - if localExecutions.isEmpty { - if enableLogging { - logger.info("No executions in local database to sync") - } - } else { - if enableLogging { - logger.info("Syncing \(localExecutions.count) executions from local database to master") - } - - // Convert ALL executions to batch data - NO FILTERING - var executionBatchData: [ExecutionBatchData] = [] + for program in newPrograms { + let programHash = DatabaseUtils.calculateProgramHash(program: program) + let metadata = ExecutionMetadata(lastOutcome: DatabaseExecutionOutcome( + id: DatabaseUtils.mapExecutionOutcome(outcome: .succeeded), + outcome: "Succeeded", + description: "Program executed successfully" + )) - for executionWithProgram in localExecutions { - // NO FILTERING - sync ALL executions including FUZZILLI_CRASH + do { + _ = try await storage.storeProgram(program: program, fuzzerId: fuzzerId, metadata: metadata) - // Convert to ExecutionBatchData for batch storage - executionBatchData.append(executionWithProgram.toExecutionBatchData()) - } - - // Batch store executions in master database with original fuzzer_id - if !executionBatchData.isEmpty { - do { - // Store programs first (in case they don't exist in master) - var uniquePrograms: [String: (Program, ExecutionMetadata)] = [:] - for execData in executionBatchData { - let programHash = DatabaseUtils.calculateProgramHash(program: execData.program) - if uniquePrograms[programHash] == nil { - let metadata = ExecutionMetadata(lastOutcome: DatabaseExecutionOutcome( - id: DatabaseUtils.mapExecutionOutcome(outcome: execData.outcome), - outcome: execData.outcome.description, - description: execData.outcome.description - )) - uniquePrograms[programHash] = (execData.program, metadata) - } - } - - // Store unique programs in master using batch storage - let programBatch = Array(uniquePrograms.values) - if !programBatch.isEmpty { - if enableLogging { - logger.info("Storing \(programBatch.count) unique programs in master before syncing executions") + // Add to recent cache + recentCacheLock.withLock { + recentProgramCache[programHash] = program + if recentProgramCache.count > maxRecentCacheSize { + let oldestKey = recentProgramCache.keys.first + if let key = oldestKey { + recentProgramCache.removeValue(forKey: key) } - let programHashes = try await masterStorage.storeProgramsBatch(programs: programBatch, fuzzerId: masterId) - if enableLogging { - logger.info("✅ Stored \(programHashes.count) programs in master database") - } - } - - // Store executions in master with original fuzzer_id using batch storage - if enableLogging { - logger.info("Storing \(executionBatchData.count) executions in master database (fuzzerId=\(masterId))") } - let executionIds = try await masterStorage.storeExecutionsBatch(executions: executionBatchData, fuzzerId: masterId) - - if enableLogging { - logger.info("✅ Successfully batch synced \(executionIds.count) executions to master database") - } - } catch { - logger.error("Failed to sync executions to master: \(error)") } + } catch { + logger.error("Failed to import program: \(error)") } } - */ - - // Clear pending sync operations since we've synced everything - _ = withLock(syncLock) { - pendingSyncOperations.removeAll() - } - - } catch { - logger.error("Failed to sync to master: \(error)") - } - } - - /// Pull sync from master database - private func pullFromMaster() { - Task { - do { - await performPullFromMaster() - } catch { - logger.error("pullFromMaster() failed: \(error)") - } - } - } - - /// Perform pull sync from master: fetch new programs and add to local cache - private func performPullFromMaster() async { - // Only pull if master storage is available - guard let masterStorage = masterStorage, let masterId = masterFuzzerId, let localId = fuzzerId else { - return } - do { - // Get ALL programs from master database (from all fuzzers) - // Use a date far in the past to get all programs - let sinceDate = Date(timeIntervalSince1970: 0) // Get all programs from the beginning - if enableLogging { - logger.info("performPullFromMaster() started - fetching all programs from master...") - } - let masterPrograms = try await masterStorage.getAllPrograms( - since: sinceDate - ) - - if enableLogging { - logger.info("Retrieved \(masterPrograms.count) programs from master database") - } - - guard !masterPrograms.isEmpty else { - if enableLogging { - logger.info("No programs to pull from master") - } - return - } - - // Pull ALL programs from master - NO filtering by cache or FUZZILLI_CRASH - if enableLogging { - logger.info("Pulling ALL \(masterPrograms.count) programs from master (no filtering)") - } - - // Add ALL programs to local cache and local postgres - NO FILTERING - // Use batch storage for better performance when pulling many programs - var pulledCount = 0 - var failedCount = 0 - - // Check if we already have these programs to avoid duplicates - // Check both cache and local database - var programsToPull: [(Program, ExecutionMetadata)] = [] - var existingHashes = Set() - - // Get existing hashes from cache - withLock(cacheLock) { - existingHashes = Set(programCache.keys) - } - - // Also check local database for existing programs (check ALL programs, not just this fuzzer's) - do { - // Get ALL programs from local database regardless of fuzzer_id - let localPrograms = try await storage.getRecentPrograms( - fuzzerId: nil, // Get all programs, not just this fuzzer's - since: Date(timeIntervalSince1970: 0), - limit: nil - ) - for (program, _) in localPrograms { - let hash = DatabaseUtils.calculateProgramHash(program: program) - existingHashes.insert(hash) - } - if enableLogging { - logger.info("Found \(existingHashes.count) existing program hashes in local database and cache") - } - } catch { - if enableLogging { - logger.warning("Failed to check local database for existing programs: \(error)") - } - } - - // Filter out programs we already have - for (program, metadata) in masterPrograms { - let hash = DatabaseUtils.calculateProgramHash(program: program) - if !existingHashes.contains(hash) { - programsToPull.append((program, metadata)) - } - } - - if enableLogging { - logger.info("Filtered \(programsToPull.count) new programs to pull (out of \(masterPrograms.count) total, \(masterPrograms.count - programsToPull.count) already exist)") - } - - // Use batch storage for better performance - // Process in chunks to avoid memory issues with very large datasets - let chunkSize = 10000 // Process 10k programs at a time - if !programsToPull.isEmpty { - if enableLogging { - logger.info("Processing \(programsToPull.count) programs to pull in chunks of \(chunkSize)...") - } - - for chunkStart in stride(from: 0, to: programsToPull.count, by: chunkSize) { - let chunkEnd = min(chunkStart + chunkSize, programsToPull.count) - let chunk = Array(programsToPull[chunkStart.. Int { // Use the fuzzerInstanceId directly as the name to avoid double "fuzzer-" prefix @@ -1189,9 +640,7 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { if enableLogging { self.logger.info("Attempting to register fuzzer (attempt \(attempt)/\(maxRetries))") } - // Use master storage if available, otherwise use local storage - let storageToUse = masterStorage ?? storage - let id = try await storageToUse.registerFuzzer( + let id = try await storage.registerFuzzer( name: fuzzerName, engineType: engineType ) @@ -1322,121 +771,32 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } } - /// Mark a program hash for database synchronization - private func markForSync(_ programHash: String) { - syncLock.lock() - defer { syncLock.unlock() } - pendingSyncOperations.insert(programHash) - } - - // MARK: - Execution Metadata Management - - /// Update execution metadata for a program - private func updateExecutionMetadata(for programHash: String, aspects: ProgramAspects) { - guard let (program, metadata) = programCache[programHash] else { return } - var updatedMetadata = metadata - updateExecutionMetadata(&updatedMetadata, aspects: aspects) - programCache[programHash] = (program: program, metadata: updatedMetadata) - markForSync(programHash) - } - - /// Update execution metadata with new aspects - private func updateExecutionMetadata(_ metadata: inout ExecutionMetadata, aspects: ProgramAspects) { - metadata.executionCount += 1 - metadata.lastExecutionTime = Date() - - // Update outcome - let outcome = DatabaseExecutionOutcome( - id: DatabaseUtils.mapExecutionOutcome(outcome: aspects.outcome), - outcome: aspects.outcome.description, - description: aspects.outcome.description - ) - metadata.updateLastOutcome(outcome) - - // Update coverage if available - if let edgeSet = aspects as? CovEdgeSet { - // For now, just track the count of edges since we can't access the actual edges - metadata.lastCoverage = Double(edgeSet.count) // Simple coverage metric - // TODO: Implement proper edge tracking when we have access to the edges - } - } - - // MARK: - Cleanup - - private func cleanup() { - assert(!fuzzer.config.staticCorpus) - - cacheLock.lock() - defer { cacheLock.unlock() } - - var newPrograms = RingBuffer(maxSize: programs.maxSize) - var newAges = RingBuffer(maxSize: ages.maxSize) - var newHashes = RingBuffer(maxSize: programHashes.maxSize) - var newCache: [String: (program: Program, metadata: ExecutionMetadata)] = [:] - - for i in 0.. \(newPrograms.count)") - } - programs = newPrograms - ages = newAges - programHashes = newHashes - programCache = newCache - } // MARK: - Statistics and Monitoring /// Get corpus statistics public func getStatistics() -> CorpusStatistics { - cacheLock.lock() - defer { cacheLock.unlock() } - - let totalExecutions = programCache.values.reduce(0) { $0 + $1.metadata.executionCount } - let averageCoverage = programCache.values.isEmpty ? 0.0 : - programCache.values.reduce(0.0) { $0 + $1.metadata.lastCoverage } / Double(programCache.count) + let recentCacheSize = recentCacheLock.withLock { recentProgramCache.count } // Get current coverage from evaluator if available var currentCoverage = 0.0 if let coverageEvaluator = fuzzer.evaluator as? ProgramCoverageEvaluator { - currentCoverage = coverageEvaluator.currentScore - // TEMPORARY TEST: Seed with 999.99 if coverage is 0 (for testing display) - if currentCoverage == 0.0 { - currentCoverage = 999.99 - } + currentCoverage = coverageEvaluator.currentScore * 100.0 } return CorpusStatistics( - totalPrograms: programs.count, - totalExecutions: totalExecutions, - averageCoverage: averageCoverage, + totalPrograms: recentCacheSize, + totalExecutions: 0, // Will be queried from DB if needed + averageCoverage: currentCoverage, currentCoverage: currentCoverage, - pendingSyncOperations: pendingSyncOperations.count, + pendingSyncOperations: 0, // No sync operations fuzzerInstanceId: fuzzerInstanceId ) } /// Get enhanced statistics including database coverage public func getEnhancedStatistics() async -> EnhancedCorpusStatistics { - let (totalExecutions, averageCoverage, pendingSyncCount) = withLock(cacheLock) { - let totalExecutions = programCache.values.reduce(0) { $0 + $1.metadata.executionCount } - let averageCoverage = programCache.values.isEmpty ? 0.0 : - programCache.values.reduce(0.0) { $0 + $1.metadata.lastCoverage } / Double(programCache.count) - return (totalExecutions, averageCoverage, pendingSyncOperations.count) - } + let recentCacheSize = recentCacheLock.withLock { recentProgramCache.count } // Get database statistics var dbStats = DatabaseStatistics() @@ -1448,11 +808,17 @@ public class PostgreSQLCorpus: ComponentBase, Corpus { } } + // Get current coverage from evaluator + var currentCoverage = 0.0 + if let coverageEvaluator = fuzzer.evaluator as? ProgramCoverageEvaluator { + currentCoverage = coverageEvaluator.currentScore * 100.0 + } + return EnhancedCorpusStatistics( - totalPrograms: programs.count, - totalExecutions: totalExecutions, - averageCoverage: averageCoverage, - pendingSyncOperations: pendingSyncCount, + totalPrograms: recentCacheSize, + totalExecutions: dbStats.totalExecutions, + averageCoverage: currentCoverage, + pendingSyncOperations: 0, // No sync operations fuzzerInstanceId: fuzzerInstanceId, databasePrograms: dbStats.totalPrograms, databaseExecutions: dbStats.totalExecutions, diff --git a/Sources/Fuzzilli/Database/DatabasePool.swift b/Sources/Fuzzilli/Database/DatabasePool.swift index dd58e04b8..76c5c9243 100644 --- a/Sources/Fuzzilli/Database/DatabasePool.swift +++ b/Sources/Fuzzilli/Database/DatabasePool.swift @@ -87,56 +87,6 @@ public class DatabasePool { } } - /// Execute an operation with a pooled connection - public func withConnection(_ operation: @escaping (PostgresConnection) -> EventLoopFuture) async throws -> T { - guard isInitialized, let pool = connectionPool else { - throw DatabasePoolError.notInitialized - } - - return try await pool.withConnection(logger: logger) { connection in - return operation(connection) - }.get() - } - - /// Test the connection pool by executing a simple query - public func testConnection() async throws -> Bool { - do { - let result = try await withConnection { connection in - connection.query("SELECT 1 as test", logger: self.logger) - } - - // Check if we got a result - if result.count > 0 { - if enableLogging { - logger.info("Database connection test successful") - } - return true - } else { - logger.error("Database connection test failed: no results") - return false - } - } catch { - logger.error("Database connection test failed: \(error)") - return false - } - } - - /// Get connection pool statistics - public func getPoolStats() async throws -> PoolStats { - guard isInitialized, let _ = connectionPool else { - throw DatabasePoolError.notInitialized - } - - // For now, return basic stats - // TODO: Implement actual pool statistics when PostgresKit supports it - return PoolStats( - totalConnections: maxConnections, - activeConnections: 0, // Not available in current PostgresKit version - idleConnections: 0, // Not available in current PostgresKit version - isHealthy: true - ) - } - /// Get event loop group for direct connections public func getEventLoopGroup() -> EventLoopGroup? { return eventLoopGroup @@ -254,21 +204,6 @@ public class DatabasePool { // MARK: - Supporting Types -/// Connection pool statistics -public struct PoolStats { - public let totalConnections: Int - public let activeConnections: Int - public let idleConnections: Int - public let isHealthy: Bool - - public init(totalConnections: Int, activeConnections: Int, idleConnections: Int, isHealthy: Bool) { - self.totalConnections = totalConnections - self.activeConnections = activeConnections - self.idleConnections = idleConnections - self.isHealthy = isHealthy - } -} - /// Database pool errors public enum DatabasePoolError: Error, LocalizedError { case notInitialized diff --git a/Sources/Fuzzilli/Database/DatabaseSchema.swift b/Sources/Fuzzilli/Database/DatabaseSchema.swift index 9d2119b2e..1b0996382 100644 --- a/Sources/Fuzzilli/Database/DatabaseSchema.swift +++ b/Sources/Fuzzilli/Database/DatabaseSchema.swift @@ -175,6 +175,18 @@ public class DatabaseSchema { CREATE INDEX IF NOT EXISTS idx_coverage_detail_execution ON coverage_detail(execution_id); CREATE INDEX IF NOT EXISTS idx_crash_analysis_execution ON crash_analysis(execution_id); + -- Additional indexes for performance on fuzzer joins + CREATE INDEX IF NOT EXISTS idx_program_fuzzer_id ON program(fuzzer_id); + CREATE INDEX IF NOT EXISTS idx_fuzzer_fuzzer_id ON fuzzer(fuzzer_id); + CREATE INDEX IF NOT EXISTS idx_execution_signal_code ON execution(signal_code) WHERE signal_code IS NOT NULL; + + -- Fuzzer statistics tracking table + CREATE TABLE IF NOT EXISTS fuzzer_statistics ( + fuzzer_id INTEGER PRIMARY KEY REFERENCES main(fuzzer_id) ON DELETE CASCADE, + execs_per_second NUMERIC(10,2), + last_updated TIMESTAMP DEFAULT NOW() + ); + -- Foreign key constraint for program table ALTER TABLE program ADD CONSTRAINT IF NOT EXISTS fk_program_fuzzer @@ -235,6 +247,60 @@ public class DatabaseSchema { WHERE p.fuzzer_id = fuzzer_instance_id; END; $$ LANGUAGE plpgsql; + + -- View for per-fuzzer performance summary + CREATE OR REPLACE VIEW fuzzer_performance_summary AS + SELECT + m.fuzzer_id, + m.fuzzer_name, + m.status, + m.created_at, + -- Calculate execs/s from last hour of executions + COALESCE( + (SELECT COUNT(*)::NUMERIC / 3600.0 + FROM execution e + JOIN program p ON e.program_hash = p.program_hash + WHERE p.fuzzer_id = m.fuzzer_id + AND e.created_at > NOW() - INTERVAL '1 hour'), 0 + ) as execs_per_second, + (SELECT COUNT(*) FROM program WHERE fuzzer_id = m.fuzzer_id) as programs_count, + (SELECT COUNT(*) FROM execution e JOIN program p ON e.program_hash = p.program_hash WHERE p.fuzzer_id = m.fuzzer_id) as executions_count, + (SELECT COUNT(*) FROM execution e JOIN program p ON e.program_hash = p.program_hash JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE p.fuzzer_id = m.fuzzer_id AND eo.outcome = 'Crashed') as crash_count, + (SELECT MAX(coverage_total) FROM execution e JOIN program p ON e.program_hash = p.program_hash WHERE p.fuzzer_id = m.fuzzer_id AND e.coverage_total IS NOT NULL) as highest_coverage_pct + FROM main m; + + -- View for crash breakdown by signal per fuzzer + CREATE OR REPLACE VIEW crash_by_signal AS + SELECT + p.fuzzer_id, + m.fuzzer_name, + e.signal_code, + CASE + WHEN e.signal_code = 11 THEN 'SIGSEGV' + WHEN e.signal_code = 6 THEN 'SIGABRT' + WHEN e.signal_code = 4 THEN 'SIGILL' + WHEN e.signal_code = 8 THEN 'SIGFPE' + WHEN e.signal_code = 3 THEN 'SIGQUIT' + WHEN e.signal_code IS NULL THEN 'NO_SIGNAL' + ELSE 'SIG' || e.signal_code::TEXT + END as signal_name, + COUNT(*) as crash_count + FROM execution e + JOIN program p ON e.program_hash = p.program_hash + JOIN main m ON p.fuzzer_id = m.fuzzer_id + JOIN execution_outcome eo ON e.execution_outcome_id = eo.id + WHERE eo.outcome = 'Crashed' + GROUP BY p.fuzzer_id, m.fuzzer_name, e.signal_code + ORDER BY p.fuzzer_id, crash_count DESC; + + -- View for global statistics + CREATE OR REPLACE VIEW global_statistics AS + SELECT + (SELECT MAX(coverage_total) FROM execution WHERE coverage_total IS NOT NULL) as highest_coverage_pct, + (SELECT COUNT(*) FROM fuzzer) as total_programs, + (SELECT COUNT(*) FROM execution) as total_executions, + (SELECT COUNT(*) FROM execution e JOIN execution_outcome eo ON e.execution_outcome_id = eo.id WHERE eo.outcome = 'Crashed') as total_crashes, + (SELECT COUNT(*) FROM main WHERE status = 'active') as active_fuzzers; """ /// Create all database tables and indexes @@ -262,7 +328,7 @@ public class DatabaseSchema { logger.info("Verifying database schema...") } - let requiredTables = ["main", "fuzzer", "program", "execution_type", "mutator_type", "execution_outcome", "execution", "feedback_vector_detail", "coverage_detail", "crash_analysis"] + let requiredTables = ["main", "fuzzer", "program", "execution_type", "mutator_type", "execution_outcome", "execution", "feedback_vector_detail", "coverage_detail", "crash_analysis", "fuzzer_statistics"] for table in requiredTables { let query: PostgresQuery = "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = \(table))" diff --git a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift index 72f9969e9..ef47168c1 100644 --- a/Sources/Fuzzilli/Database/PostgreSQLStorage.swift +++ b/Sources/Fuzzilli/Database/PostgreSQLStorage.swift @@ -501,34 +501,6 @@ public class PostgreSQLStorage { return programHash } - /// Get program by hash - public func getProgram(hash: String) async throws -> Program? { - if enableLogging { - logger.info("Getting program: hash=\(hash)") - } - - // For now, return nil (program not found) - // TODO: Implement actual database query when PostgreSQL is set up - if enableLogging { - logger.info("Mock program lookup: program not found") - } - return nil - } - - /// Get program metadata for a specific fuzzer - public func getProgramMetadata(programHash: String, fuzzerId: Int) async throws -> ExecutionMetadata? { - if enableLogging { - logger.info("Getting program metadata: hash=\(programHash), fuzzerId=\(fuzzerId)") - } - - // For now, return nil (metadata not found) - // TODO: Implement actual database query when PostgreSQL is set up - if enableLogging { - logger.info("Mock metadata lookup: metadata not found") - } - return nil - } - // MARK: - Execution Management /// Store multiple executions in batch for better performance @@ -694,231 +666,8 @@ public class PostgreSQLStorage { } } - /// Get execution history for a program - public func getExecutionHistory(programHash: String, fuzzerId: Int, limit: Int = 100) async throws -> [ExecutionRecord] { - if enableLogging { - logger.info("Getting execution history: hash=\(programHash), fuzzerId=\(fuzzerId), limit=\(limit)") - } - - // For now, return empty array - // TODO: Implement actual database query when PostgreSQL is set up - if enableLogging { - logger.info("Mock execution history lookup: no executions found") - } - return [] - } - - // MARK: - Crash Management - - /// Store crash information - public func storeCrash( - program: Program, - fuzzerId: Int, - executionId: Int, - crashType: String, - signalCode: Int? = nil, - exitCode: Int? = nil, - stdout: String? = nil, - stderr: String? = nil - ) async throws -> Int { - let programHash = DatabaseUtils.calculateProgramHash(program: program) - if enableLogging { - logger.info("Storing crash: hash=\(programHash), fuzzerId=\(fuzzerId), executionId=\(executionId), type=\(crashType)") - } - - // For now, return a mock crash ID - // TODO: Implement actual database storage when PostgreSQL is set up - let mockCrashId = Int.random(in: 1...1000) - if enableLogging { - logger.info("Mock crash storage successful: crashId=\(mockCrashId)") - } - return mockCrashId - } - // MARK: - Query Operations - /// Get recent programs with metadata for a fuzzer - /// Get all programs from master database (from all fuzzers) - public func getAllPrograms(since: Date) async throws -> [(Program, ExecutionMetadata)] { - return try await getRecentPrograms(fuzzerId: nil, since: since, limit: nil) - } - - public func getRecentPrograms(fuzzerId: Int?, since: Date, limit: Int? = nil) async throws -> [(Program, ExecutionMetadata)] { - if enableLogging { - logger.info("Getting recent programs: fuzzerId=\(fuzzerId), since=\(since), limit=\(limit)") - } - - // Use direct connection using the database pool's configuration - let connection = try await createDirectConnection() - defer { Task { _ = try? await connection.close() } } - - // Query for ALL programs from corpus (fuzzer table) - no date filter, no limits - // Use DISTINCT ON to get one row per program (in case of multiple executions) - // Use explicit type casts to ensure correct decoding - var queryString = """ - SELECT DISTINCT ON (f.program_hash) - f.program_hash::text, - COALESCE(f.program_size, 0)::integer as program_size, - f.program_base64::text, - f.inserted_at::timestamp, - eo.outcome::text, - eo.description::text, - e.execution_time_ms::integer, - e.coverage_total::double precision, - e.signal_code::integer, - e.exit_code::integer - FROM fuzzer f - LEFT JOIN execution e ON f.program_hash = e.program_hash - LEFT JOIN execution_outcome eo ON e.execution_outcome_id = eo.id - """ - - // Only filter by fuzzer_id if specified, otherwise get ALL programs - if let fuzzerId = fuzzerId { - queryString += " WHERE f.fuzzer_id = \(fuzzerId)" - } - - queryString += " ORDER BY f.program_hash, e.execution_id DESC NULLS LAST" - - // Never apply limit - get ALL programs - // \(limit != nil ? "LIMIT \(limit!)" : "") - - let query = PostgresQuery(stringLiteral: queryString) - let result = try await connection.query(query, logger: self.logger) - let rows = try await result.collect() - - var programs: [(Program, ExecutionMetadata)] = [] - - for row in rows { - // Decode with error handling for each field - let programHash: String - let programSize: Int - let programBase64: String - let createdAt: Date - let outcome: String? - let description: String? - let executionTimeMs: Int? - let coverageTotal: Double? - let signalCode: Int? - let exitCode: Int? - - do { - // Use random access row to decode columns by index explicitly to avoid PostgresNIO decode issues - let randomAccessRow = row.makeRandomAccess() - - // Decode required fields by index (0, 1, 2, 3) - programHash = try randomAccessRow[0].decode(String.self, context: PostgresDecodingContext.default) - programSize = try randomAccessRow[1].decode(Int.self, context: PostgresDecodingContext.default) - programBase64 = try randomAccessRow[2].decode(String.self, context: PostgresDecodingContext.default) - createdAt = try randomAccessRow[3].decode(Date.self, context: PostgresDecodingContext.default) - - // Decode optional fields by index (4, 5, 6, 7, 8, 9) - these can be NULL from LEFT JOIN - outcome = try randomAccessRow[4].decode(String?.self, context: PostgresDecodingContext.default) - description = try randomAccessRow[5].decode(String?.self, context: PostgresDecodingContext.default) - executionTimeMs = try randomAccessRow[6].decode(Int?.self, context: PostgresDecodingContext.default) - coverageTotal = try randomAccessRow[7].decode(Double?.self, context: PostgresDecodingContext.default) - signalCode = try randomAccessRow[8].decode(Int?.self, context: PostgresDecodingContext.default) - exitCode = try randomAccessRow[9].decode(Int?.self, context: PostgresDecodingContext.default) - } catch { - if enableLogging { - logger.warning("Failed to decode row: \(String(reflecting: error)). Skipping this program.") - } - continue - } - - // Decode the program from base64 - skip if it fails but continue with other programs - guard let programData = Data(base64Encoded: programBase64) else { - if enableLogging { - logger.warning("Skipping program \(programHash): Failed to decode base64 data") - } - continue - } - - let program: Program - do { - let protobuf = try Fuzzilli_Protobuf_Program(serializedBytes: programData) - program = try Program(from: protobuf) - } catch { - // Skip programs that fail to decode from protobuf - they may be from incompatible versions - // Continue syncing other programs that can be decoded - if enableLogging { - logger.debug("Skipping program \(programHash): Failed to decode from protobuf (likely incompatible format): \(error)") - } - continue - } - - // Create execution metadata - - // Map outcome string to database ID - let outcomeId: Int - switch (outcome ?? "Succeeded").lowercased() { - case "crashed": - outcomeId = 1 - case "failed": - outcomeId = 2 - case "succeeded": - outcomeId = 3 - case "timedout": - outcomeId = 4 - case "sigcheck": - outcomeId = 34 - default: - outcomeId = 3 // Default to succeeded - } - - let dbOutcome = DatabaseExecutionOutcome( - id: outcomeId, - outcome: outcome ?? "Succeeded", - description: description ?? "Program executed successfully" - ) - - var metadata = ExecutionMetadata(lastOutcome: dbOutcome) - if let coverage = coverageTotal { - metadata.lastCoverage = coverage - } - - programs.append((program, metadata)) - } - - if enableLogging { - logger.info("Loaded \(programs.count) recent programs from database") - } - return programs - } - - /// Update program metadata - public func updateProgramMetadata(programHash: String, fuzzerId: Int, metadata: ExecutionMetadata) async throws { - if enableLogging { - logger.info("Updating program metadata: hash=\(programHash), fuzzerId=\(fuzzerId), executionCount=\(metadata.executionCount)") - } - - // For now, just log the operation - // TODO: Implement actual database update when PostgreSQL is set up - if enableLogging { - logger.info("Mock metadata update successful") - } - } - - // MARK: - Statistics - - /// Get storage statistics - public func getStorageStatistics() async throws -> StorageStatistics { - if enableLogging { - logger.info("Getting storage statistics") - } - - // For now, return mock statistics - // TODO: Implement actual database statistics when PostgreSQL is set up - let mockStats = StorageStatistics( - totalPrograms: 0, - totalExecutions: 0, - totalCrashes: 0, - activeFuzzers: 0 - ) - if enableLogging { - logger.info("Mock statistics: \(mockStats.description)") - } - return mockStats - } } // MARK: - Supporting Types @@ -964,18 +713,6 @@ public struct ExecutionBatchData { } } -/// Storage statistics -public struct StorageStatistics { - public let totalPrograms: Int - public let totalExecutions: Int - public let totalCrashes: Int - public let activeFuzzers: Int - - public var description: String { - return "Programs: \(totalPrograms), Executions: \(totalExecutions), Crashes: \(totalCrashes), Active Fuzzers: \(activeFuzzers)" - } -} - /// PostgreSQL storage errors public enum PostgreSQLStorageError: Error, LocalizedError { case noResult @@ -1002,23 +739,8 @@ public enum PostgreSQLStorageError: Error, LocalizedError { extension PostgreSQLStorage { /// Execute a simple query without expecting results public func executeQuery(_ query: PostgresQuery) async throws { - guard let eventLoopGroup = databasePool.getEventLoopGroup() else { - throw PostgreSQLStorageError.noResult - } - - let connection = try await PostgresConnection.connect( - on: eventLoopGroup.next(), - configuration: PostgresConnection.Configuration( - host: "localhost", - port: 5432, - username: "fuzzilli", - password: "fuzzilli123", - database: "fuzzilli_master", - tls: .disable - ), - id: 0, - logger: logger - ) + // Use direct connection to avoid connection pool deadlock + let connection = try await createDirectConnection() defer { Task { _ = try? await connection.close() } } try await connection.query(query, logger: self.logger) diff --git a/Sources/FuzzilliCli/main.swift b/Sources/FuzzilliCli/main.swift index d31a7c33b..8f0990f0e 100755 --- a/Sources/FuzzilliCli/main.swift +++ b/Sources/FuzzilliCli/main.swift @@ -100,7 +100,6 @@ Options: --wasm : Enable Wasm CodeGenerators (see WasmCodeGenerators.swift). --forDifferentialFuzzing : Enable additional features for better support of external differential fuzzing. --postgres-url=url : PostgreSQL connection string for PostgreSQL corpus (e.g., postgresql://user:pass@host:port/db). - --sync-interval=n : Sync interval in seconds for PostgreSQL corpus (default: 10). --validate-before-cache : Enable program validation before caching in PostgreSQL corpus (default: true). --execution-history-size=n : Number of recent executions to keep in memory for PostgreSQL corpus (default: 10). --postgres-logging : Enable PostgreSQL database operation logging. @@ -165,11 +164,6 @@ let forDifferentialFuzzing = args.has("--forDifferentialFuzzing") // PostgreSQL corpus specific arguments let postgresUrl = args["--postgres-url"] ?? ProcessInfo.processInfo.environment["POSTGRES_URL"] -let localPostgresUrl = args["--local-postgres-url"] ?? ProcessInfo.processInfo.environment["LOCAL_POSTGRES_URL"] -let masterPostgresUrl = args["--master-postgres-url"] ?? ProcessInfo.processInfo.environment["MASTER_POSTGRES_URL"] -let syncInterval = args.int(for: "--sync-interval") ?? 10 -let validateBeforeCache = args.has("--validate-before-cache") || !args.has("--no-validate-before-cache") // Default to true -let executionHistorySize = args.int(for: "--execution-history-size") ?? 10 let postgresLogging = args.has("--postgres-logging") guard numJobs >= 1 else { @@ -224,19 +218,8 @@ if corpusName == "markov" && staticCorpus { // PostgreSQL corpus validation if corpusName == "postgresql" { - // If dual database mode (local + master), both URLs are required - // Otherwise, single postgresUrl is required - if localPostgresUrl != nil && masterPostgresUrl != nil { - // Dual database mode - both URLs provided - if syncInterval <= 0 { - configError("--sync-interval must be greater than 0") - } - } else if postgresUrl == nil { - // Single database mode - postgresUrl required - configError("PostgreSQL corpus requires --postgres-url (or --local-postgres-url and --master-postgres-url for dual database mode)") - } - if executionHistorySize <= 0 { - configError("--execution-history-size must be greater than 0") + if postgresUrl == nil { + configError("PostgreSQL corpus requires --postgres-url (or POSTGRES_URL environment variable)") } } @@ -550,8 +533,7 @@ func makeFuzzer(with configuration: Configuration) -> Fuzzer { case "markov": corpus = MarkovCorpus(covEvaluator: evaluator as ProgramCoverageEvaluator, dropoutRate: markovDropoutRate) case "postgresql": - // Create PostgreSQL corpus with database connection - // Support dual database mode (local + master) or single database mode + // Create PostgreSQL corpus with master database connection let fuzzerInstanceId: String // Check for explicit fuzzer instance name from environment or CLI args @@ -567,43 +549,26 @@ func makeFuzzer(with configuration: Configuration) -> Fuzzer { fuzzerInstanceId = "fuzzer-\(randomHash)" } - let localPool: DatabasePool - let masterPool: DatabasePool? - - if let localUrl = localPostgresUrl, let masterUrl = masterPostgresUrl { - // Dual database mode: local for fast operations, master for sync - localPool = DatabasePool(connectionString: localUrl, enableLogging: postgresLogging) - masterPool = DatabasePool(connectionString: masterUrl, enableLogging: postgresLogging) - logger.info("Dual database mode enabled") - logger.info("Local PostgreSQL URL: \(localUrl)") - logger.info("Master PostgreSQL URL: \(masterUrl)") - } else if let url = postgresUrl { - // Single database mode: use provided URL for both - localPool = DatabasePool(connectionString: url, enableLogging: postgresLogging) - masterPool = nil - logger.info("Single database mode") - logger.info("PostgreSQL URL: \(url)") - } else { + guard let url = postgresUrl else { logger.fatal("PostgreSQL URL is required for PostgreSQL corpus") } + let databasePool = DatabasePool(connectionString: url, enableLogging: postgresLogging) + logger.info("Connecting to master PostgreSQL database") + logger.info("PostgreSQL URL: \(url)") + corpus = PostgreSQLCorpus( minSize: minCorpusSize, maxSize: maxCorpusSize, minMutationsPerSample: minMutationsPerSample, - databasePool: localPool, + databasePool: databasePool, fuzzerInstanceId: fuzzerInstanceId, - syncInterval: TimeInterval(syncInterval), resume: resume, - enableLogging: postgresLogging, - masterDatabasePool: masterPool + enableLogging: postgresLogging ) logger.info("Created PostgreSQL corpus with instance ID: \(fuzzerInstanceId)") logger.info("Resume mode: \(resume)") - logger.info("Sync interval: \(syncInterval) seconds") - logger.info("Validate before cache: \(validateBeforeCache)") - logger.info("Execution history size: \(executionHistorySize)") default: logger.fatal("Invalid corpus name provided") } diff --git a/docker-compose.workers.yml b/docker-compose.workers.yml index dbca254cc..f35e3f915 100644 --- a/docker-compose.workers.yml +++ b/docker-compose.workers.yml @@ -2,27 +2,6 @@ version: '3.8' services: - # Worker 1 - Local Postgres - postgres-local-1: - image: postgres:15-alpine - container_name: postgres-local-1 - environment: - POSTGRES_DB: fuzzilli_local - POSTGRES_USER: fuzzilli - POSTGRES_PASSWORD: fuzzilli123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_local_data_1:/var/lib/postgresql/data - - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql - healthcheck: - test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - networks: - - fuzzing-network - # Worker 1 - Fuzzilli Container fuzzer-worker-1: build: @@ -30,19 +9,14 @@ services: dockerfile: Cloud/VRIG/Dockerfile.distributed container_name: fuzzer-worker-1 environment: - - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master - - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-1:5432/fuzzilli_local - - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-1:5432/fuzzilli_local + - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master - FUZZER_INSTANCE_NAME=fuzzer-1 - - SYNC_INTERVAL=300 - TIMEOUT=2500 - MIN_MUTATIONS_PER_SAMPLE=25 - DEBUG_LOGGING=false depends_on: postgres-master: condition: service_healthy - postgres-local-1: - condition: service_healthy volumes: - fuzzer_data_1:/home/app/Corpus - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro @@ -63,27 +37,6 @@ services: memory: 1G - # Worker 2 - Local Postgres - postgres-local-2: - image: postgres:15-alpine - container_name: postgres-local-2 - environment: - POSTGRES_DB: fuzzilli_local - POSTGRES_USER: fuzzilli - POSTGRES_PASSWORD: fuzzilli123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_local_data_2:/var/lib/postgresql/data - - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql - healthcheck: - test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - networks: - - fuzzing-network - # Worker 2 - Fuzzilli Container fuzzer-worker-2: build: @@ -91,19 +44,14 @@ services: dockerfile: Cloud/VRIG/Dockerfile.distributed container_name: fuzzer-worker-2 environment: - - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master - - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-2:5432/fuzzilli_local - - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-2:5432/fuzzilli_local + - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master - FUZZER_INSTANCE_NAME=fuzzer-2 - - SYNC_INTERVAL=300 - TIMEOUT=2500 - MIN_MUTATIONS_PER_SAMPLE=25 - DEBUG_LOGGING=false depends_on: postgres-master: condition: service_healthy - postgres-local-2: - condition: service_healthy volumes: - fuzzer_data_2:/home/app/Corpus - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro @@ -124,71 +72,6 @@ services: memory: 1G - # Worker 3 - Local Postgres - postgres-local-3: - image: postgres:15-alpine - container_name: postgres-local-3 - environment: - POSTGRES_DB: fuzzilli_local - POSTGRES_USER: fuzzilli - POSTGRES_PASSWORD: fuzzilli123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_local_data_3:/var/lib/postgresql/data - - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql - healthcheck: - test: ["CMD-SHELL", "pg_isready -U fuzzilli -d fuzzilli_local"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - networks: - - fuzzing-network - - # Worker 3 - Fuzzilli Container - fuzzer-worker-3: - build: - context: /home/tropic/vrig/fuzzilli-vrig-proj/fuzzillai - dockerfile: Cloud/VRIG/Dockerfile.distributed - container_name: fuzzer-worker-3 - environment: - - MASTER_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-master:5432/fuzzilli_master - - LOCAL_POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-3:5432/fuzzilli_local - - POSTGRES_URL=postgresql://fuzzilli:fuzzilli123@postgres-local-3:5432/fuzzilli_local - - FUZZER_INSTANCE_NAME=fuzzer-3 - - SYNC_INTERVAL=300 - - TIMEOUT=2500 - - MIN_MUTATIONS_PER_SAMPLE=25 - - DEBUG_LOGGING=false - depends_on: - postgres-master: - condition: service_healthy - postgres-local-3: - condition: service_healthy - volumes: - - fuzzer_data_3:/home/app/Corpus - - /home/tropic/vrig/fuzzilli-vrig-proj/fuzzbuild:/home/app/fuzzbuild:ro - restart: unless-stopped - healthcheck: - test: ["CMD-SHELL", "pgrep -f FuzzilliCli || exit 1"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 60s - networks: - - fuzzing-network - deploy: - resources: - limits: - memory: 2G - reservations: - memory: 1G - - volumes: - postgres_local_data_1: fuzzer_data_1: - postgres_local_data_2: fuzzer_data_2: - postgres_local_data_3: - fuzzer_data_3: