diff --git a/Instrumentor/qemu/patches-feedback/chatkey.c b/Instrumentor/qemu/patches-feedback/chatkey.c index a2ce635..3be2bcc 100644 --- a/Instrumentor/qemu/patches-feedback/chatkey.c +++ b/Instrumentor/qemu/patches-feedback/chatkey.c @@ -18,7 +18,7 @@ extern unsigned int afl_forksrv_pid; #define FORKSRV_FD 198 #define TSL_FD (FORKSRV_FD - 1) -#define MAX_TRACE_LEN (100000) +#define MAX_TRACE_LEN (1000000) static abi_ulong hash = 5381; // djb2 hash diff --git a/Instrumentor/qemu/patches-pathcov/chatkey.cc b/Instrumentor/qemu/patches-pathcov/chatkey.cc index 67673cc..de2f05e 100644 --- a/Instrumentor/qemu/patches-pathcov/chatkey.cc +++ b/Instrumentor/qemu/patches-pathcov/chatkey.cc @@ -21,24 +21,25 @@ extern unsigned int afl_forksrv_pid; abi_ulong chatkey_entry_point; /* ELF entry point (_start) */ -#define MODE_COUNT_NEW 0 // Count newly covered nodes, along with path hash. -#define MODE_HASH 1 // Calculate node set hash. -#define MODE_SET 2 // Return the set of the visited nodes. +#define MODE_COUNT_NEW 0 // Count newly covered edges, along with path hash. +#define MODE_HASH 1 // Calculate edge set hash. +#define MODE_SET 2 // Return the set of the visited edges. int chatkey_mode = -1; -static uint32_t new_node_cnt = 0; // # of new nodes visited in this execution -static abi_ulong node_set_hash = 5381; // djb2 hash +static uint32_t new_edge_cnt = 0; // # of new edges visited in this execution +static abi_ulong edge_set_hash = 5381; // djb2 hash static abi_ulong path_hash = 5381; // djb2 hash +static abi_ulong prev_node = 0; /* Global file pointers */ static FILE* coverage_fp; static FILE* dbg_fp; static int is_fp_closed = 0; -static unsigned char * accum_node_bitmap; -static unsigned char node_bitmap[0x10000]; -// Holds nodes visited in this exec (will be dumped into a file) -static dense_hash_set node_set; +static unsigned char * accum_edge_bitmap; +static unsigned char edge_bitmap[0x10000]; +// Holds edges visited in this exec (will be dumped into a file) +static dense_hash_set edge_set; static void dump_set(dense_hash_set * set, FILE* output_fp) { @@ -57,10 +58,10 @@ extern "C" void chatkey_setup_before_forkserver(void) { shm_id = getenv("CK_SHM_ID"); assert(shm_id != NULL); - accum_node_bitmap = (unsigned char *) shmat(atoi(shm_id), NULL, 0); - assert(accum_node_bitmap != (void *) -1); + accum_edge_bitmap = (unsigned char *) shmat(atoi(shm_id), NULL, 0); + assert(accum_edge_bitmap != (void *) -1); - node_set.set_empty_key(0); + edge_set.set_empty_key(0); } extern "C" void chatkey_setup_after_forkserver(void) { @@ -121,28 +122,28 @@ extern "C" void chatkey_exit(void) { if (chatkey_mode == MODE_COUNT_NEW) { - /* Output new node # and path hash. */ - fprintf(coverage_fp, "%d\n", new_node_cnt); + /* Output new edge # and path hash. */ + fprintf(coverage_fp, "%d\n", new_edge_cnt); #ifdef TARGET_X86_64 fprintf(coverage_fp, "%lu\n", path_hash); - fprintf(coverage_fp, "%lu\n", node_set_hash); + fprintf(coverage_fp, "%lu\n", edge_set_hash); #else fprintf(coverage_fp, "%u\n", path_hash); - fprintf(coverage_fp, "%u\n", node_set_hash); + fprintf(coverage_fp, "%u\n", edge_set_hash); #endif fclose(coverage_fp); } else if (chatkey_mode == MODE_HASH) { - /* Output path hash and node hash */ + /* Output path hash and edge hash */ #ifdef TARGET_X86_64 - fprintf(coverage_fp, "%lu\n", node_set_hash); + fprintf(coverage_fp, "%lu\n", edge_set_hash); #else - fprintf(coverage_fp, "%u\n", node_set_hash); + fprintf(coverage_fp, "%u\n", edge_set_hash); #endif fclose(coverage_fp); } else if (chatkey_mode == MODE_SET) { - /* Dump visited node set */ - dump_set(&node_set, coverage_fp); + /* Dump visited edge set */ + dump_set(&edge_set, coverage_fp); fclose(coverage_fp); } else { assert(false); @@ -158,35 +159,41 @@ static inline void chatkey_update_path_hash(register abi_ulong addr) { path_hash = ((path_hash << 5) + path_hash) + ((addr >> (i<<3)) & 0xff); } -static inline void update_node_set_hash(register abi_ulong node_hash) { - node_set_hash = node_set_hash ^ node_hash; +static inline void update_edge_set_hash(register abi_ulong edge_hash) { + edge_set_hash = edge_set_hash ^ edge_hash; } extern "C" void chatkey_log_bb(abi_ulong addr, abi_ulong callsite) { - abi_ulong node, hash; + abi_ulong edge, hash; unsigned int byte_idx, byte_mask; unsigned char old_byte, new_byte; chatkey_update_path_hash(addr); - node = addr; + edge = addr; +#ifdef TARGET_X86_64 + edge = (prev_node << 16) ^ addr; +#else + edge = (prev_node << 8) ^ addr; +#endif + prev_node = addr; if (chatkey_mode == MODE_COUNT_NEW) { - // Check and update both node_bitmap and accumulative bitmap - hash = (node >> 4) ^ (node << 8); + // Check and update both edge_bitmap and accumulative bitmap + hash = (edge >> 4) ^ (edge << 8); byte_idx = (hash >> 3) & 0xffff; byte_mask = 1 << (hash & 0x7); // Use the lowest 3 bits to shift - old_byte = node_bitmap[byte_idx]; + old_byte = edge_bitmap[byte_idx]; new_byte = old_byte | byte_mask; if (old_byte != new_byte) { - node_bitmap[byte_idx] = new_byte; - // If it's a new node, update node hash and also add to accumulative map - update_node_set_hash(hash); - old_byte = accum_node_bitmap[byte_idx]; + edge_bitmap[byte_idx] = new_byte; + // If it's a new edge, update edge hash and also add to accumulative map + update_edge_set_hash(hash); + old_byte = accum_edge_bitmap[byte_idx]; new_byte = old_byte | byte_mask; if (old_byte != new_byte) { - new_node_cnt++; - accum_node_bitmap[byte_idx] = new_byte; - /* Log newly found nodes if dbg_fp is not NULL */ + new_edge_cnt++; + accum_edge_bitmap[byte_idx] = new_byte; + /* Log visited addrs if dbg_fp is not NULL */ if (dbg_fp) { #ifdef TARGET_X86_64 fprintf(dbg_fp, "(0x%lx, 0x%lx)\n", addr, callsite); @@ -197,20 +204,20 @@ extern "C" void chatkey_log_bb(abi_ulong addr, abi_ulong callsite) { } } } else if (chatkey_mode == MODE_HASH) { - // Check and update node_bitmap only - hash = (node >> 4) ^ (node << 8); + // Check and update edge_bitmap only + hash = (edge >> 4) ^ (edge << 8); byte_idx = (hash >> 3) & 0xffff; byte_mask = 1 << (hash & 0x7); // Lowest 3 bits - old_byte = node_bitmap[byte_idx]; + old_byte = edge_bitmap[byte_idx]; new_byte = old_byte | byte_mask; if (old_byte != new_byte) { - node_bitmap[byte_idx] = new_byte; - // If it's a new node, update node hash and also add to accumulative map - update_node_set_hash(hash); + edge_bitmap[byte_idx] = new_byte; + // If it's a new edge, update edge hash and also add to accumulative map + update_edge_set_hash(hash); } } else if (chatkey_mode == MODE_SET ) { - // Just insert currently covered node to the node set - node_set.insert(node); + // Just insert currently covered edge to the edge set + edge_set.insert(edge); } else if (chatkey_mode != -1) { /* If chatkey_mode is -1, it means that chatkey_setup() is not called yet * This happens when QEMU is executing a dynamically linked program. Other diff --git a/src/Core/Typedef.fs b/src/Core/Typedef.fs index 56c553b..a920ce1 100644 --- a/src/Core/Typedef.fs +++ b/src/Core/Typedef.fs @@ -66,7 +66,7 @@ module InputKind = | StdIn -> true // Currently, consider only one-time standard input. | File -> true // Currently, consider only one file input. -/// Priority of found seed. A seed that increased node coverage is assigned +/// Priority of found seed. A seed that increased edge coverage is assigned /// 'Favored' priority, while a seed that increased path coverage is assigned /// 'Normal' priority. type Priority = Favored | Normal \ No newline at end of file diff --git a/src/Eclipser.fsproj b/src/Eclipser.fsproj index 3238133..a0ccab7 100644 --- a/src/Eclipser.fsproj +++ b/src/Eclipser.fsproj @@ -1,5 +1,4 @@  - Exe netcoreapp2.0 @@ -32,6 +31,7 @@ + diff --git a/src/Executor/Executor.fs b/src/Executor/Executor.fs index f15f7a4..a180599 100644 --- a/src/Executor/Executor.fs +++ b/src/Executor/Executor.fs @@ -10,7 +10,7 @@ open Syscall open TestCase /// Kinds of QEMU instrumentor. Each instrumentor serves different purposes. -type Tracer = Coverage | Branch | Syscall | BBCount +type Tracer = Coverage | BranchAt | BranchAll | Syscall | BBCount /// Specifies the method to handle execution timeout. It is necessary to use GDB /// when replaying test cases against a gcov-compiled binary, to log coverage @@ -23,12 +23,12 @@ type TimeoutHandling = /// Mode of coverage tracer execution. type CoverageTracerMode = - /// Count the number of new nodes (also obtains a path hash as a by-product) - | CountNewNode = 0 - /// Calculate the hash of node set visitied in this execution - | NodeHash = 1 - /// Find the node set visited in this execution - | NodeSet = 2 + /// Count the number of new edges (also obtains a path hash as a by-product) + | CountNewEdge = 0 + /// Calculate the hash of edge set visitied in this execution + | EdgeHash = 1 + /// Find the edge set visited in this execution + | EdgeSet = 2 [] extern void set_env (string env_variable, string env_value) [] extern void initialize_exec (TimeoutHandling is_replay) @@ -59,8 +59,10 @@ let selectTracer tracer arch = match tracer, arch with | Coverage, X86 -> coverageTracerX86 | Coverage, X64 -> coverageTracerX64 - | Branch, X86 -> branchTracerX86 - | Branch, X64 -> branchTracerX64 + | BranchAt, X86 -> branchTracerX86 + | BranchAt, X64 -> branchTracerX64 + | BranchAll, X86 -> branchTracerX86 + | BranchAll, X64 -> branchTracerX64 | Syscall, X86 -> syscallTracerX86 | Syscall, X64 -> syscallTracerX64 | BBCount, X86 -> bbCountTracerX86 @@ -69,7 +71,6 @@ let selectTracer tracer arch = let mutable pathHashLog = "" let mutable branchTraceLog = "" let mutable coverageLog = "" -let mutable nodeLog = "" let mutable syscallTraceLog = "" let mutable dbgLog = "" @@ -86,14 +87,11 @@ let initialize outdir verbosity = pathHashLog <- System.IO.Path.Combine(outdir, ".path_hash") branchTraceLog <- System.IO.Path.Combine(outdir, ".branch_trace") coverageLog <- System.IO.Path.Combine(outdir, ".coverage") - // 'nodeLog' is managed in tracer side, we just provide a dedicated file path. - nodeLog <- System.IO.Path.Combine(outdir, ".node_raw") syscallTraceLog <- System.IO.Path.Combine(outdir, ".syscall_trace") dbgLog <- System.IO.Path.Combine(outdir, ".debug_msg") set_env("CK_HASH_LOG", System.IO.Path.GetFullPath(pathHashLog)) set_env("CK_FEED_LOG", System.IO.Path.GetFullPath(branchTraceLog)) set_env("CK_COVERAGE_LOG", System.IO.Path.GetFullPath(coverageLog)) - set_env("CK_NODE_LOG", System.IO.Path.GetFullPath(nodeLog)) set_env("CK_SYSCALL_LOG", System.IO.Path.GetFullPath(syscallTraceLog)) if verbosity >= 2 then set_env("CK_DBG_LOG", System.IO.Path.GetFullPath(dbgLog)) @@ -105,7 +103,7 @@ let initialize outdir verbosity = set_env("CK_CTX_SENSITIVITY", string CtxSensitivity) let cleanUpFiles () = - removeFiles [pathHashLog; branchTraceLog; coverageLog; nodeLog] + removeFiles [pathHashLog; branchTraceLog; coverageLog] removeFiles [syscallTraceLog; dbgLog] (*** Statistics ***) @@ -119,11 +117,14 @@ let resetPhaseExecutions () = phaseExecutions <- 0 (*** Resource scheduling ***) -let mutable allowedExecutions = 0 -let allocateResource n = allowedExecutions <- n -let isResourceExhausted () = allowedExecutions <= 0 -let incrExecutionCount () = - allowedExecutions <- allowedExecutions - 1 +let mutable allowedResource = 0 +let allocateResource n = allowedResource <- n +let isResourceExhausted () = allowedResource <= 0 +let incrExecutionCount tracerTyp = + // TODO: Add calibration mode like AFL, and estimate the weight of each tracer + // type based on execution time. + let diminish = if tracerTyp = BranchAll then 4 else 1 + allowedResource <- allowedResource - diminish totalExecutions <- totalExecutions + 1 phaseExecutions <- phaseExecutions + 1 @@ -142,7 +143,7 @@ let initForkServer opt = let pidCoverage = init_forkserver_coverage(args.Length, args, opt.ExecTimeout) if pidCoverage = -1 then failwith "Failed to initialize fork server for coverage tracer" - let branchTracer = selectTracer Branch opt.Architecture + let branchTracer = selectTracer BranchAll opt.Architecture let args = Array.append [|branchTracer; opt.TargetProg|] initArgs let pidBranch = init_forkserver_branch (args.Length, args, opt.ExecTimeout) if pidBranch = -1 then @@ -197,8 +198,8 @@ let setupFiles targetProg (tc: TestCase) autoFuzzMode = let private parseCoverage filename = let content = readAllLines filename match content with - | [newNodeCnt; pathHash; nodeHash] -> - int newNodeCnt, uint64 pathHash, uint64 nodeHash + | [newEdgeCnt; pathHash; edgeHash] -> + int newEdgeCnt, uint64 pathHash, uint64 edgeHash | _ -> log "[Warning] Coverage logging failed : %A" content; (0, 0UL, 0UL) let private parseExecHash filename = @@ -211,20 +212,20 @@ let private is64Bit = function | X86 -> false | X64 -> true -let private readNodeSet opt filename = +let private readEdgeSet opt filename = try let f = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read) use r = new BinaryReader(f) let arch = opt.Architecture let mutable valid = true - let nodeList = + let edgeList = [ while valid do let addr = try if is64Bit arch then r.ReadUInt64() else uint64 (r.ReadUInt32()) with :? EndOfStreamException -> 0UL if addr = 0UL then valid <- false else yield addr ] - Set.ofList nodeList + Set.ofList edgeList with | :? FileNotFoundException -> Set.empty let private parseBranchTraceLog opt (r:BinaryReader) tryVal = @@ -270,7 +271,7 @@ let private readBranchTrace opt filename tryVal = (*** Tracer execute functions ***) let private runTracer (tc: TestCase) tracerType opt = - incrExecutionCount () + incrExecutionCount tracerType let targetProg = opt.TargetProg let timeout = opt.ExecTimeout let usePty = opt.UsePty @@ -280,14 +281,14 @@ let private runTracer (tc: TestCase) tracerType opt = exec(argc, args, tc.StdIn.Length, tc.StdIn, timeout, usePty) let private runCoverageTracerForked (tc: TestCase) opt = - incrExecutionCount () + incrExecutionCount Coverage let timeout = opt.ExecTimeout let signal = exec_fork_coverage(timeout, tc.StdIn.Length, tc.StdIn) if signal = Signal.ERROR then abandonForkServer () signal -let private runBranchTracerForked (tc: TestCase) opt = - incrExecutionCount () +let private runBranchTracerForked (tc: TestCase) tracerType opt = + incrExecutionCount tracerType let timeout = opt.ExecTimeout let signal = exec_fork_branch(timeout, tc.StdIn.Length, tc.StdIn) if signal = Signal.ERROR then abandonForkServer () @@ -295,41 +296,41 @@ let private runBranchTracerForked (tc: TestCase) opt = (*** Top-level tracer executor functions ***) -let getNodeHash opt seed = +let getEdgeHash opt seed = let tc = TestCase.fromSeed seed let autoFuzzMode = opt.FuzzMode = AutoFuzz let files = setupFiles opt.TargetProg tc autoFuzzMode - set_env("CK_MODE", string (int CoverageTracerMode.NodeHash)) + set_env("CK_MODE", string (int CoverageTracerMode.EdgeHash)) let _ = if forkServerEnabled then runCoverageTracerForked tc opt else runTracer tc Coverage opt - let nodeHash = parseExecHash coverageLog + let edgeHash = parseExecHash coverageLog removeFiles files - nodeHash + edgeHash let getCoverage opt seed = let tc = TestCase.fromSeed seed let autoFuzzMode = opt.FuzzMode = AutoFuzz let files = setupFiles opt.TargetProg tc autoFuzzMode - set_env("CK_MODE", string (int CoverageTracerMode.CountNewNode)) + set_env("CK_MODE", string (int CoverageTracerMode.CountNewEdge)) let exitSig = if forkServerEnabled then runCoverageTracerForked tc opt else runTracer tc Coverage opt - let newNodeCnt, pathHash, nodeHash = parseCoverage coverageLog + let newEdgeCnt, pathHash, edgeHash = parseCoverage coverageLog removeFiles files - (newNodeCnt, pathHash, nodeHash, exitSig) + (newEdgeCnt, pathHash, edgeHash, exitSig) -let getNodeSet opt seed = +let getEdgeSet opt seed = let tc = TestCase.fromSeed seed let autoFuzzMode = opt.FuzzMode = AutoFuzz let files = setupFiles opt.TargetProg tc autoFuzzMode - set_env("CK_MODE", string (int CoverageTracerMode.NodeSet)) + set_env("CK_MODE", string (int CoverageTracerMode.EdgeSet)) let _ = if forkServerEnabled then runCoverageTracerForked tc opt else runTracer tc Coverage opt - let nodeSet = readNodeSet opt coverageLog + let edgeSet = readEdgeSet opt coverageLog removeFiles files - nodeSet + edgeSet let getBranchTrace opt seed tryVal = let tc = TestCase.fromSeed seed @@ -338,8 +339,8 @@ let getBranchTrace opt seed tryVal = let autoFuzzMode = opt.FuzzMode = AutoFuzz let files = setupFiles opt.TargetProg tc autoFuzzMode let _ = if forkServerEnabled - then runBranchTracerForked tc opt - else runTracer tc Branch opt + then runBranchTracerForked tc BranchAll opt + else runTracer tc BranchAll opt let pathHash = parseExecHash pathHashLog let branchTrace = readBranchTrace opt branchTraceLog tryVal removeFiles (pathHashLog :: branchTraceLog :: files) @@ -352,8 +353,8 @@ let getBranchInfoAt opt seed tryVal targPoint = let autoFuzzMode = opt.FuzzMode = AutoFuzz let files = setupFiles opt.TargetProg tc autoFuzzMode let _ = if forkServerEnabled - then runBranchTracerForked tc opt - else runTracer tc Branch opt + then runBranchTracerForked tc BranchAt opt + else runTracer tc BranchAt opt let pathHash = parseExecHash pathHashLog let branchInfoOpt = match readBranchTrace opt branchTraceLog tryVal with diff --git a/src/Fuzz/Fuzz.fs b/src/Fuzz/Fuzz.fs index 1bbc606..014566c 100644 --- a/src/Fuzz/Fuzz.fs +++ b/src/Fuzz/Fuzz.fs @@ -5,38 +5,6 @@ open Config open Utils open Options -let createSeeds opt = - let maxArgLens = List.filter (fun len -> len > 0) opt.MaxArgLen - let inputSrc = InputKind.ofFuzzMode opt.FuzzMode - let initSeedWithNArgs n = - let (maxArgLens', _) = splitList n maxArgLens - Seed.make inputSrc maxArgLens' opt.MaxFileLen opt.MaxStdInLen - List.ofSeq { 1 .. List.length maxArgLens } - |> List.map initSeedWithNArgs - |> List.rev // Prioritize seed with more # of args, for better exploration. - -let importSeeds opt = - let inputSrc = InputKind.ofFuzzMode opt.FuzzMode - let maxLen = - match inputSrc with - | Args when List.length opt.MaxArgLen = 1 -> List.head opt.MaxArgLen - | Args -> failwith "Invalid max length option on argument input" - | File -> opt.MaxFileLen - | StdIn -> opt.MaxStdInLen - System.IO.Directory.EnumerateFiles opt.InitSeedsDir // Obtain file list - |> List.ofSeq // Convert list to array - |> List.map System.IO.File.ReadAllBytes // Read in file contents - |> List.map (Seed.makeWith inputSrc maxLen) // Create seed with content - -let initializeSeeds opt = - let initArg = opt.InitArg - let fpath = opt.Filepath - let seedDir = opt.InitSeedsDir - // Initialize seeds, and set the initial argument/file path of each seed - if seedDir = "" then createSeeds opt else importSeeds opt - |> List.map (fun s -> if initArg <> "" then Seed.setArgs s initArg else s) - |> List.map (fun s -> if fpath <> "" then Seed.setFilepath s fpath else s) - let findInputSrc opt seed = if opt.FuzzMode = AutoFuzz then Executor.getSyscallTrace opt seed @@ -167,8 +135,8 @@ let run args = let opt = parseFuzzOption args validateFuzzOption opt assertFileExists opt.TargetProg - printfn "Fuzz target : %s" opt.TargetProg - printfn "Time limit : %d sec" opt.Timelimit + log "[*] Fuzz target : %s" opt.TargetProg + log "[*] Time limit : %d sec" opt.Timelimit createDirectoryIfNotExists opt.OutDir Manager.initialize opt.OutDir Executor.initialize opt.OutDir opt.Verbosity @@ -176,13 +144,8 @@ let run args = Executor.prepareSharedMem () if opt.FuzzMode = StdinFuzz || opt.FuzzMode = FileFuzz then Executor.initForkServer opt - let initialSeeds = initializeSeeds opt - let initItems = List.map (fun s -> (Favored, s)) initialSeeds let queueDir = sprintf "%s/.internal" opt.OutDir - let greyConcQueue = ConcolicQueue.initialize queueDir - let greyConcQueue = List.fold ConcolicQueue.enqueue greyConcQueue initItems - let randFuzzQueue = RandFuzzQueue.initialize queueDir - let randFuzzQueue = List.fold RandFuzzQueue.enqueue randFuzzQueue initItems - log "Fuzzing starts" + let greyConcQueue, randFuzzQueue = Initialize.initQueue opt queueDir + log "[*] Fuzzing starts" Async.Start (fuzzingTimer opt.Timelimit queueDir) fuzzLoop opt greyConcQueue randFuzzQueue diff --git a/src/Fuzz/GreyConcolic/GreyConcolic.fs b/src/Fuzz/GreyConcolic/GreyConcolic.fs index 586112b..f51d004 100644 --- a/src/Fuzz/GreyConcolic/GreyConcolic.fs +++ b/src/Fuzz/GreyConcolic/GreyConcolic.fs @@ -23,17 +23,19 @@ let evaluateEfficiency () = let newPathNum = List.sum (Queue.elements recentNewPathNums) if execNum = 0 then 1.0 else float newPathNum / float execNum -let printFoundSeed seed newNodeN = - let seedStr = Seed.toString seed - let nodeStr = if newNodeN > 0 then sprintf "(%d new nodes) " newNodeN else "" - log "[*] Found by grey-box concolic %s: %s" nodeStr seedStr +let printFoundSeed verbosity seed newEdgeN = + let edgeStr = if newEdgeN > 0 then sprintf "(%d new edges) " newEdgeN else "" + if verbosity >= 1 then + log "[*] Found by grey-box concolic %s: %s" edgeStr (Seed.toString seed) + elif verbosity >= 0 then + log "[*] Found by grey-box concolic %s" edgeStr let evalSeedsAux opt accSeeds seed = - let newNodeN, pathHash, nodeHash, exitSig = Executor.getCoverage opt seed - let isNewPath = Manager.storeSeed opt seed newNodeN pathHash nodeHash exitSig - if newNodeN > 0 && opt.Verbosity >= 0 then printFoundSeed seed newNodeN + let newEdgeN, pathHash, edgeHash, exitSig = Executor.getCoverage opt seed + let isNewPath = Manager.save opt seed newEdgeN pathHash edgeHash exitSig false + if newEdgeN > 0 then printFoundSeed opt.Verbosity seed newEdgeN if isNewPath && not (Signal.isTimeout exitSig) && not (Signal.isCrash exitSig) - then let priority = if newNodeN > 0 then Favored else Normal + then let priority = if newEdgeN > 0 then Favored else Normal (priority, seed) :: accSeeds else accSeeds diff --git a/src/Fuzz/Initialize.fs b/src/Fuzz/Initialize.fs new file mode 100644 index 0000000..0725f13 --- /dev/null +++ b/src/Fuzz/Initialize.fs @@ -0,0 +1,87 @@ +module Eclipser.Initialize + +open System +open Config +open Utils +open Options + +let createSeeds opt = + let maxArgLens = List.filter (fun len -> len > 0) opt.MaxArgLen + let inputSrc = InputKind.ofFuzzMode opt.FuzzMode + let initSeedWithNArgs n = + let (maxArgLens', _) = splitList n maxArgLens + Seed.make inputSrc maxArgLens' opt.MaxFileLen opt.MaxStdInLen + List.ofSeq { 1 .. List.length maxArgLens } + |> List.map initSeedWithNArgs + |> List.rev // Prioritize seed with more # of args, for better exploration. + +let importSeeds opt = + let inputSrc = InputKind.ofFuzzMode opt.FuzzMode + let maxLen = + match inputSrc with + | Args when List.length opt.MaxArgLen = 1 -> List.head opt.MaxArgLen + | Args -> failwith "Invalid max length option on argument input" + | File -> opt.MaxFileLen + | StdIn -> opt.MaxStdInLen + System.IO.Directory.EnumerateFiles opt.InitSeedsDir // Obtain file list + |> List.ofSeq // Convert list to array + |> List.map System.IO.File.ReadAllBytes // Read in file contents + |> List.map (Seed.makeWith inputSrc maxLen) // Create seed with content + +let initializeSeeds opt = + let initArg = opt.InitArg + let fpath = opt.Filepath + let seedDir = opt.InitSeedsDir + // Initialize seeds, and set the initial argument/file path of each seed + if seedDir = "" then createSeeds opt else importSeeds opt + |> List.map (fun s -> if initArg <> "" then Seed.setArgs s initArg else s) + |> List.map (fun s -> if fpath <> "" then Seed.setFilepath s fpath else s) + +let findInputSrc opt seed = + if opt.FuzzMode = AutoFuzz + then Executor.getSyscallTrace opt seed + else Set.empty + +let rec moveCursorsAux opt isFromConcolic accConcolic accRandom items = + match items with + | [] -> (accConcolic, accRandom) + | (priority, seed) :: tailItems -> + let inputSrcs = findInputSrc opt seed + let concSeeds = Seed.moveCursors seed isFromConcolic inputSrcs + let concItems = List.map (fun s -> (priority, s)) concSeeds + let randSeeds = seed :: Seed.moveSourceCursor seed inputSrcs + let randItems = List.map (fun s -> (priority, s)) randSeeds + let accConcolic = concItems @ accConcolic + let accRandom = randItems @ accRandom + moveCursorsAux opt isFromConcolic accConcolic accRandom tailItems + +let moveCursors opt isFromConcolic seeds = + let concItems, randItems = moveCursorsAux opt isFromConcolic [] [] seeds + (List.rev concItems, List.rev randItems) + +let preprocessAux opt seed = + let newEdgeN, pathHash, edgeHash, exitSig = Executor.getCoverage opt seed + let isNewPath = Manager.save opt seed newEdgeN pathHash edgeHash exitSig true + let inputSrcs = findInputSrc opt seed + let newSeeds = seed :: Seed.moveSourceCursor seed inputSrcs + if newEdgeN > 0 then List.map (fun s -> (Favored, s)) newSeeds + elif isNewPath then List.map (fun s -> (Normal, s)) newSeeds + else [] + +let preprocess opt seeds = + log "[*] Total %d initial seeds" (List.length seeds) + let items = List.collect (preprocessAux opt) seeds + let favoredCount = List.filter (fst >> (=) Favored) items |> List.length + let normalCount = List.filter (fst >> (=) Normal) items |> List.length + log "[*] %d initial items with high priority" favoredCount + log "[*] %d initial items with low priority" normalCount + items + +let initQueue opt queueDir = + let initialSeeds = initializeSeeds opt + let initItems = preprocess opt initialSeeds + let greyConcQueue = ConcolicQueue.initialize queueDir + let greyConcQueue = List.fold ConcolicQueue.enqueue greyConcQueue initItems + let randFuzzQueue = RandFuzzQueue.initialize queueDir + let randFuzzQueue = List.fold RandFuzzQueue.enqueue randFuzzQueue initItems + (greyConcQueue, randFuzzQueue) diff --git a/src/Fuzz/Manager.fs b/src/Fuzz/Manager.fs index 8ccaf54..8be3e0a 100644 --- a/src/Fuzz/Manager.fs +++ b/src/Fuzz/Manager.fs @@ -30,7 +30,7 @@ let mutable private stdinTestCaseCount = 0 let mutable private fileTestCaseCount = 0 let mutable private testCaseCount = 0 let private pathHashes = new HashSet() -let private crashNodeHashes = new HashSet() +let private crashEdgeHashes = new HashSet() let isNewPath pathHash = // The case of pathHash = 0UL means abortion due to threshold or other errors. @@ -41,8 +41,8 @@ let private addPathHash pathHash = let isNewPath = pathHash <> 0UL && pathHashes.Add pathHash isNewPath -let private addCrashHash crashNodeHash = - crashNodeHash <> 0UL && crashNodeHashes.Add crashNodeHash +let private addCrashHash crashEdgeHash = + crashEdgeHash <> 0UL && crashEdgeHashes.Add crashEdgeHash let getPathCount () = pathHashes.Count @@ -101,20 +101,20 @@ let private dumpTestCase seed = System.IO.File.WriteAllText(tcPath, (TestCase.toJSON tc)) updateTestcaseCount seed -let private checkCrash opt exitSig tc nodeHash = - if Signal.isCrash exitSig && addCrashHash nodeHash +let private checkCrash opt exitSig tc edgeHash = + if Signal.isCrash exitSig && addCrashHash edgeHash then (true, exitSig) elif Signal.isTimeout exitSig then // Check again with native execution let exitSig' = Executor.nativeExecute opt tc - if Signal.isCrash exitSig' && addCrashHash nodeHash + if Signal.isCrash exitSig' && addCrashHash edgeHash then (true, exitSig') else (false, exitSig') else (false, exitSig) -let storeSeed opt seed newN pathHash nodeHash exitSig = +let save opt seed newN pathHash edgeHash exitSig isInitSeed = let tc = TestCase.fromSeed seed let isNewPath = addPathHash pathHash - let isNewCrash, exitSig' = checkCrash opt exitSig tc nodeHash - if newN > 0 then dumpTestCase seed + let isNewCrash, exitSig' = checkCrash opt exitSig tc edgeHash + if newN > 0 || isInitSeed then dumpTestCase seed if isNewCrash then dumpCrash opt seed exitSig' isNewPath diff --git a/src/Fuzz/Options.fs b/src/Fuzz/Options.fs index 609eeea..08a934b 100644 --- a/src/Fuzz/Options.fs +++ b/src/Fuzz/Options.fs @@ -40,9 +40,9 @@ with "standard input and file inputs." // Options related to seed initialization | InitSeedsDir _ -> "Directory containing initial seeds." - | MaxArgLen _ -> "Maximum len of cmdline argument (default:[8])" - | MaxFileLen _ -> "Maximum len of file input (default:[8])" - | MaxStdInLen _ -> "Maximum len of file input (default:[8])" + | MaxArgLen _ -> "Maximum len of cmdline argument (default: [8byte])" + | MaxFileLen _ -> "Maximum len of file input (default: 1MB)" + | MaxStdInLen _ -> "Maximum len of standard input (default: 1MB)" | InitArg _ -> "Initial command-line argument of program under test." | Filepath _ -> "File input's (fixed) path" // Options related to execution of program @@ -100,8 +100,8 @@ let parseFuzzOption (args: string array) = // Options related to seed InitSeedsDir = r.GetResult(<@ InitSeedsDir @>, defaultValue = "") MaxArgLen = r.GetResult (<@ MaxArgLen @>, defaultValue = [8]) - MaxFileLen = r.GetResult (<@ MaxFileLen @>, defaultValue = 8) - MaxStdInLen = r.GetResult (<@ MaxStdInLen @>, defaultValue = 8) + MaxFileLen = r.GetResult (<@ MaxFileLen @>, defaultValue = 1048576) + MaxStdInLen = r.GetResult (<@ MaxStdInLen @>, defaultValue = 1048576) InitArg = r.GetResult (<@ InitArg @>, defaultValue = "") Filepath = r.GetResult (<@ Filepath @>, defaultValue = "") // Options related to test case generation diff --git a/src/Fuzz/RandomFuzz.fs b/src/Fuzz/RandomFuzz.fs index fcd711b..509d1f7 100644 --- a/src/Fuzz/RandomFuzz.fs +++ b/src/Fuzz/RandomFuzz.fs @@ -58,44 +58,46 @@ let rec roundUpExpAux accVal input = let roundUpExp input = roundUpExpAux 1 input -let rec trimAux opt accSeed nodeHash trimMinSize trimSize pos = +let rec trimAux opt accSeed edgeHash trimMinSize trimSize pos = if trimSize < trimMinSize then accSeed // Cannot lower trimSize anymore, time to stop elif pos + trimSize >= Seed.getCurInputLen accSeed then (* Reached end, retry with more fine granularity (reset idx to 0). *) - trimAux opt accSeed nodeHash trimMinSize (trimSize / 2) 0 + trimAux opt accSeed edgeHash trimMinSize (trimSize / 2) 0 else let trySeed = Seed.removeBytesFrom accSeed pos trimSize - let tryNodeHash = Executor.getNodeHash opt trySeed - if tryNodeHash = nodeHash then // Trimming succeeded. + let tryEdgeHash = Executor.getEdgeHash opt trySeed + if tryEdgeHash = edgeHash then // Trimming succeeded. (* Caution : Next trimming position is not 'pos + trimSize' *) - trimAux opt trySeed nodeHash trimMinSize trimSize pos + trimAux opt trySeed edgeHash trimMinSize trimSize pos else (* Trimming failed, move on to next position *) let newPos = (pos + trimSize) - trimAux opt accSeed nodeHash trimMinSize trimSize newPos + trimAux opt accSeed edgeHash trimMinSize trimSize newPos -let trim opt nodeHash seed = +let trim opt edgeHash seed = let inputLen = Seed.getCurInputLen seed let inputLenRounded = roundUpExp inputLen let trimSize = max (inputLenRounded / TRIM_START_STEPS) TRIM_MIN_BYTES let trimMinSize = max (inputLenRounded / TRIM_END_STEPS) TRIM_MIN_BYTES - let trimmedSeed = trimAux opt seed nodeHash trimMinSize trimSize 0 + let trimmedSeed = trimAux opt seed edgeHash trimMinSize trimSize 0 // Should adjust byte cursor again within a valid range. Seed.shuffleByteCursor trimmedSeed -let printFoundSeed seed newNodeN = - let seedStr = Seed.toString seed - let nodeStr = if newNodeN > 0 then sprintf "(%d new nodes) " newNodeN else "" - log "[*] Found by random fuzzing %s: %s" nodeStr seedStr +let printFoundSeed verbosity seed newEdgeN = + let edgeStr = if newEdgeN > 0 then sprintf "(%d new edges) " newEdgeN else "" + if verbosity >= 1 then + log "[*] Found by random fuzzing %s: %s" edgeStr (Seed.toString seed) + elif verbosity >= 0 then + log "[*] Found by random fuzzing %s" edgeStr let evalSeedsAux opt accItems seed = - let newNodeN, pathHash, nodeHash, exitSig = Executor.getCoverage opt seed - let isNewPath = Manager.storeSeed opt seed newNodeN pathHash nodeHash exitSig - if newNodeN > 0 && opt.Verbosity >= 0 then printFoundSeed seed newNodeN + let newEdgeN, pathHash, edgeHash, exitSig = Executor.getCoverage opt seed + let isNewPath = Manager.save opt seed newEdgeN pathHash edgeHash exitSig false + if newEdgeN > 0 then printFoundSeed opt.Verbosity seed newEdgeN if isNewPath && not (Signal.isTimeout exitSig) && not (Signal.isCrash exitSig) then - let priority = if newNodeN > 0 then Favored else Normal - // Trimming is only for seeds that found new nodes - let seed' = if newNodeN > 0 then trim opt nodeHash seed else seed + let priority = if newEdgeN > 0 then Favored else Normal + // Trimming is only for seeds that found new edges + let seed' = if newEdgeN > 0 then trim opt edgeHash seed else seed (priority, seed') :: accItems else accItems diff --git a/src/Fuzz/SeedQueue.fs b/src/Fuzz/SeedQueue.fs index 4783795..1656569 100644 --- a/src/Fuzz/SeedQueue.fs +++ b/src/Fuzz/SeedQueue.fs @@ -103,15 +103,15 @@ module RandFuzzQueue = let rec findRedundantsGreedyAux queue seedEntries accRedundantSeeds = if List.isEmpty seedEntries then accRedundantSeeds else - (* Choose an entry that has largest number of covered nodes *) - let getNodeCount (idx, seed, nodes) = Set.count nodes - let seedEntriesSorted = List.sortByDescending getNodeCount seedEntries - let _, _, chosenNodes = List.head seedEntriesSorted + (* Choose an entry that has largest number of covered edges *) + let getEdgeCount (idx, seed, edges) = Set.count edges + let seedEntriesSorted = List.sortByDescending getEdgeCount seedEntries + let _, _, chosenEdges = List.head seedEntriesSorted let seedEntries = List.tail seedEntriesSorted - (* Now update (i.e. subtract node set) seed entries *) - let subtractNodes nodes (i, s, ns) = (i, s, Set.difference ns nodes) - let seedEntries = List.map (subtractNodes chosenNodes) seedEntries - (* If the node set entry is empty, it means that seed is redundant *) + (* Now update (i.e. subtract edge set) seed entries *) + let subtractEdges edges (i, s, ns) = (i, s, Set.difference ns edges) + let seedEntries = List.map (subtractEdges chosenEdges) seedEntries + (* If the edge set entry is empty, it means that seed is redundant *) let redundantEntries, seedEntries = List.partition (fun (i, s, ns) -> Set.isEmpty ns) seedEntries let redundantSeeds = List.map (fun (i, s, _) -> (i, s)) redundantEntries @@ -122,7 +122,7 @@ module RandFuzzQueue = let seedEntries = List.fold (fun accSets (idx, seed) -> - (idx, seed, Executor.getNodeSet opt seed) :: accSets + (idx, seed, Executor.getEdgeSet opt seed) :: accSets ) [] seeds findRedundantsGreedyAux queue seedEntries [] diff --git a/src/fsc.props b/src/fsc.props deleted file mode 100644 index 59ee90c..0000000 --- a/src/fsc.props +++ /dev/null @@ -1,41 +0,0 @@ - - - - - true - true - true - - - C:\Program Files (x86)\Microsoft SDKs\F#\4.1\Framework\v4.0 - fsc.exe - - - C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\Common7\IDE\CommonExtensions\Microsoft\FSharp - fsc.exe - - - C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\FSharp - fsc.exe - - - C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\CommonExtensions\Microsoft\FSharp - fsc.exe - - - C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\CommonExtensions\Microsoft\FSharp - fsc.exe - - - C:\Program Files (x86)\Microsoft SDKs\F#\10.1\Framework\v4.0 - fsc.exe - - - /Library/Frameworks/Mono.framework/Versions/Current/Commands - fsharpc - - - /usr/bin - fsharpc - -