mirror of
https://github.com/maride/afl-transmit.git
synced 2024-11-23 07:44:24 +00:00
Only sync main nodes across servers
This commit is contained in:
parent
a95b6ce3fe
commit
7807b45ed5
30
README.md
30
README.md
@ -4,12 +4,11 @@ Transfer AFL files over a mesh to fuzz across multiple servers
|
||||
|
||||
## Features
|
||||
|
||||
- Automatically syncs the fuzzers over all nodes
|
||||
- No obscure dependencies, no painful setup process - just a single, self-contained binary
|
||||
- Using DEFLATE compression format (see [RFC 1951](https://www.ietf.org/rfc/rfc1951.html))
|
||||
- Automatically syncs the main fuzzer to secondary nodes, and all secondary fuzzers back to the main node
|
||||
- Encrypts traffic between nodes using AES-256, dropping plaintext packets
|
||||
- Usable on UNIX-like systems (Linux, OSX) and Windows
|
||||
- Reduces the amount of transmitted test cases to a bare minimum
|
||||
|
||||
## Usage
|
||||
|
||||
@ -23,9 +22,9 @@ As a countermeasure, use the `--restrict-to-peers` flags to only allow connectio
|
||||
|
||||
Let's assume you have three servers running with some instances of AFL, all in secondary (`-S`) mode, except the main fuzzer running on the box 10.0.0.1.
|
||||
To sync test cases across those servers, you'd need to run
|
||||
- on 10.0.0.1: `./afl-transmit --fuzzer-directory /ram/output --main --peers 10.0.0.2,10.0.0.3`
|
||||
- on 10.0.0.2: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.1`
|
||||
- on 10.0.0.3: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.1`
|
||||
- on 10.0.0.1: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.2,10.0.0.3`
|
||||
- on 10.0.0.2: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.1,10.0.0.3`
|
||||
- on 10.0.0.3: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.1,10.0.0.2`
|
||||
|
||||
Because *afl-transmit* stays in the foreground, you should probably run it in a `tmux` window or something comparable.
|
||||
|
||||
@ -43,24 +42,3 @@ dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tee transmit.key
|
||||
```
|
||||
|
||||
As already said, the same key must be used on all nodes.
|
||||
|
||||
### Traffic reduction
|
||||
|
||||
On default, *afl-transmit* avoids sending files with the same name present in different fuzzer directories.
|
||||
This will greatly reduce the traffic between your nodes (I measured 621 kB to 1.3 kB, for example).
|
||||
Please note that there might be some edge cases when you don't want that behaviour, e.g.
|
||||
- you want to preserve the queue of each fuzzer
|
||||
- you expect your fuzzers to give the same (file) name to different test cases, in which case *afl-transmit* would mistakenly assume that the file has the same *contents* and not only the same *name*
|
||||
- you don't care for traffic
|
||||
|
||||
To avoid reducing the transmitted files by comparing filenames, add `--no-duplicates=false` as argument.
|
||||
|
||||
Also on default, *afl-transmit* tries to check if the queue of the observed fuzzers contain test cases which originated from another fuzzer instance.
|
||||
In that case, the file name contains the keyword "sync" in it, and looks e.g. like this: `id:001815,time:0,orig:id:001805,sync:main,src:001794`
|
||||
If it was copied from another fuzzer, it means that the file is already present in the fuzzer cluster, and can safely be skipped on those fuzzer instances which copied it.
|
||||
Please note that this will produce false positives if the filename of your testcases contain `,sync:` for whatever reason.
|
||||
|
||||
To avoid reducing the transmitted files by filtering synced files out, add `--avoid-synced=false` as argument.
|
||||
|
||||
If you still have trouble paying the invoice for your ISP due to heavy traffic usage, try increasing the `--rescan` value, so files are transmitted less often.
|
||||
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@ -12,39 +11,25 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var noDuplicates bool
|
||||
|
||||
// avoidSynced is the flag to set if files containing "sync" should be packed or not.
|
||||
// Those files are from another fuzzer - no need to pack them twice.
|
||||
var avoidSynced bool
|
||||
|
||||
// RegisterPackerFlags registers flags which are required by the packer
|
||||
func RegisterPackerFlags() {
|
||||
flag.BoolVar(&noDuplicates, "no-duplicates", true, "Avoid transmitting the same file multiple times, e.g. because it is present in multiple fuzzer's queues")
|
||||
flag.BoolVar(&avoidSynced, "avoid-synced", true, "Avoid transmitting files containing the keyword 'sync', as they are from other fuzzers anyways, and should be included by their afl-transmit instance")
|
||||
}
|
||||
|
||||
// PackFuzzers packs all targeted fuzzers into a TAR - at least queue/, fuzz_bitmap, fuzzer_stats
|
||||
func PackFuzzers(fuzzers []string, fuzzerDirectory string) ([]byte, error) {
|
||||
// PackFuzzer packs the fuzzer into a TAR: queue/, fuzz_bitmap, fuzzer_stats
|
||||
func PackFuzzer(fuzzer string, fuzzerDirectory string) ([]byte, error) {
|
||||
// Create TAR archive
|
||||
var tarBuffer bytes.Buffer
|
||||
tarWriter := tar.NewWriter(&tarBuffer)
|
||||
|
||||
// Essentially we want to pack three things from each targeted fuzzer:
|
||||
// Essentially we want to pack three things from the targeted fuzzer:
|
||||
// - the fuzz_bitmap file
|
||||
// - the fuzzer_stats file
|
||||
// - the queue/ directory - but avoiding duplicates
|
||||
var pkgCont []string // list of queue files already present in the archive
|
||||
for _, fuzzer := range fuzzers {
|
||||
// We need full paths to read, but will write relative paths into the TAR archive
|
||||
absFuzzerPath := fuzzerDirectory
|
||||
relFuzzerPath := strings.TrimPrefix(fuzzer, fuzzerDirectory)
|
||||
|
||||
// Read-n-Pack™
|
||||
packSingleFile(tarWriter, absFuzzerPath, relFuzzerPath, "fuzz_bitmap")
|
||||
packSingleFile(tarWriter, absFuzzerPath, relFuzzerPath, "fuzzer_stats")
|
||||
packQueueFiles(tarWriter, absFuzzerPath, relFuzzerPath, &pkgCont)
|
||||
}
|
||||
// We need full paths to read, but will write relative paths into the TAR archive
|
||||
absFuzzerPath := fuzzerDirectory
|
||||
relFuzzerPath := strings.TrimPrefix(fuzzer, fuzzerDirectory)
|
||||
|
||||
// Read-n-Pack™
|
||||
packSingleFile(tarWriter, absFuzzerPath, relFuzzerPath, "fuzz_bitmap")
|
||||
packSingleFile(tarWriter, absFuzzerPath, relFuzzerPath, "fuzzer_stats")
|
||||
packQueueFiles(tarWriter, absFuzzerPath, relFuzzerPath)
|
||||
|
||||
// Close TAR archive
|
||||
tarWriter.Close()
|
||||
@ -90,7 +75,7 @@ func packSingleFile(tarWriter *tar.Writer, absPath string, relPath string, fileN
|
||||
}
|
||||
|
||||
// Packs the files in the given directory into a tar archive
|
||||
func packQueueFiles(tarWriter *tar.Writer, absPath string, relPath string, pkgCont *[]string) {
|
||||
func packQueueFiles(tarWriter *tar.Writer, absPath string, relPath string) {
|
||||
// Get list of queue files
|
||||
queuePath := fmt.Sprintf("%s%c%s%cqueue", absPath, os.PathSeparator, relPath, os.PathSeparator)
|
||||
filesInDir, readErr := ioutil.ReadDir(queuePath)
|
||||
@ -107,37 +92,7 @@ func packQueueFiles(tarWriter *tar.Writer, absPath string, relPath string, pkgCo
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if we should care fore duplicates
|
||||
if noDuplicates && checkDuplicate(f.Name(), pkgCont) {
|
||||
// that file is already present in the package - avoid packing it again
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if we should care for the keyword 'sync' in file name
|
||||
if avoidSynced && strings.Contains(f.Name(), ",sync:") {
|
||||
// seems like this file was put into the queue of this fuzzer by syncing it from another fuzzer. We don't
|
||||
// need to transmit it then, because the fuzzer which found that case will have the same file but without
|
||||
// the keyword "sync" in it. Simply put, we avoid sending the same file multiple times with different names.
|
||||
continue
|
||||
}
|
||||
|
||||
// Pack into the archive
|
||||
packSingleFile(tarWriter, absPath, relPath, fmt.Sprintf("queue%c%s", os.PathSeparator, f.Name()))
|
||||
|
||||
if noDuplicates {
|
||||
// Append added file name to the list of things included in the package
|
||||
*pkgCont = append(*pkgCont, f.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkDuplicate checks if name is already present in pkgCont
|
||||
func checkDuplicate(name string, pkgCont *[]string) bool {
|
||||
for _, p := range *pkgCont {
|
||||
if p == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
2
main.go
2
main.go
@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/maride/afl-transmit/logistic"
|
||||
"github.com/maride/afl-transmit/net"
|
||||
"github.com/maride/afl-transmit/stats"
|
||||
"github.com/maride/afl-transmit/watchdog"
|
||||
@ -20,7 +19,6 @@ func main() {
|
||||
net.RegisterSenderFlags()
|
||||
net.RegisterListenFlags()
|
||||
net.RegisterCryptFlags()
|
||||
logistic.RegisterPackerFlags()
|
||||
stats.RegisterStatsFlags()
|
||||
RegisterGlobalFlags()
|
||||
flag.Parse()
|
||||
|
@ -13,21 +13,26 @@ import (
|
||||
|
||||
var (
|
||||
rescan int
|
||||
isMainNode bool
|
||||
)
|
||||
|
||||
// RegisterWatchdogFlags registers required flags for the watchdog
|
||||
func RegisterWatchdogFlags() {
|
||||
flag.IntVar(&rescan, "rescan", 30, "Minutes to wait before rescanning local fuzzer directories")
|
||||
flag.BoolVar(&isMainNode, "main", false, "Set this option if this afl-transmit instance is running on the node running the main afl-fuzz instance")
|
||||
flag.IntVar(&rescan, "rescan", 30, "Minutes to wait before rescanning local fuzzer directory")
|
||||
}
|
||||
|
||||
// Watch over the specified directory, send updates to peers and re-scan after the specified amount of seconds
|
||||
// WatchFuzzers watches over the specified directory, sends updates to peers and re-scans after the specified amount of seconds
|
||||
func WatchFuzzers(outputDirectory string) {
|
||||
// Loop forever
|
||||
for {
|
||||
// Pack important parts of the fuzzers into an archive
|
||||
packedFuzzers, packerErr := logistic.PackFuzzers(getTargetFuzzer(outputDirectory), outputDirectory)
|
||||
// Search for main fuzzer
|
||||
targetFuzzer, targetErr := getTargetFuzzer(outputDirectory)
|
||||
if targetErr != nil {
|
||||
log.Printf("Failed to detect main fuzzer: %s", targetErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Pack important parts of the fuzzer into an archive
|
||||
packedFuzzers, packerErr := logistic.PackFuzzer(targetFuzzer, outputDirectory)
|
||||
if packerErr != nil {
|
||||
log.Printf("Failed to pack fuzzer: %s", packerErr)
|
||||
continue
|
||||
@ -41,24 +46,24 @@ func WatchFuzzers(outputDirectory string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Searches in the specified output directory for fuzzers which we want to pack.
|
||||
// Identifying the main fuzzer is done by searching for the file "is_main_node"
|
||||
// This relies on the "election process" done by secondary fuzzers if they don't find a local main node.
|
||||
// If that is detected, a secondary fuzzer becomes the main node in terms of syncing. That means we need to focus on
|
||||
// the main node even if there is no real main node running locally.
|
||||
// Searches in the specified output directory for the main fuzzer.
|
||||
// Identifying the main fuzzer is done by searching for the file "is_main_node". On secondary-only servers, this relies
|
||||
// on the "election process" done by secondary fuzzers if they don't find a local main node. In that election process, a
|
||||
// secondary fuzzer becomes the main node in terms of syncing. That means we need to focus on the main node even if
|
||||
// there is no real main node running locally. Good thing is that the elected node will create a "is_main_node" file, so
|
||||
// we really just need to search for that.
|
||||
// However it is important to avoid transmitting the is_main_node file - else, the secondaries will think that there is
|
||||
// a main node running locally. Skipping the is_main_node file means the other fuzzers (and especially the elected main
|
||||
// fuzzer) detect the transmitted fuzzer as a normal (and dead) secondary. ... blabla
|
||||
// fuzzer) detect the transmitted fuzzer as a normal (and dead) secondary.
|
||||
//
|
||||
// (see AFLplusplus/src/afl-fuzz.run.c:542)
|
||||
func getTargetFuzzer(outputDirectory string) []string {
|
||||
func getTargetFuzzer(outputDirectory string) (string, error) {
|
||||
// find main fuzzer directory - its the only one required by the secondaries
|
||||
|
||||
// List files (read: fuzzers) in output directory
|
||||
filesInDir, readErr := ioutil.ReadDir(outputDirectory)
|
||||
if readErr != nil {
|
||||
log.Printf("Failed to list directory content of %s: %s", outputDirectory, readErr)
|
||||
return nil
|
||||
return "", fmt.Errorf("Failed to list directory content of %s: %s", outputDirectory, readErr)
|
||||
}
|
||||
|
||||
for _, f := range filesInDir {
|
||||
@ -75,13 +80,9 @@ func getTargetFuzzer(outputDirectory string) []string {
|
||||
}
|
||||
|
||||
// is_main_node file exists, so we found the correct fuzzer directory - return it
|
||||
fullPath := fmt.Sprintf("%s%c%s", outputDirectory, os.PathSeparator, f.Name())
|
||||
return []string{
|
||||
fullPath,
|
||||
}
|
||||
return fmt.Sprintf("%s%c%s", outputDirectory, os.PathSeparator, f.Name()), nil
|
||||
}
|
||||
|
||||
// Failed to find the main node - probably we are in --main mode by accident
|
||||
log.Printf("Unable to find main node in %s - sure afl-transmit should run in --main mode?", outputDirectory)
|
||||
return nil
|
||||
return "", fmt.Errorf("Unable to find main node in %s", outputDirectory)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user