diff --git a/pkg/ebpf/instruction_generators.go b/pkg/ebpf/instruction_generators.go index fcd4ae2..8a7a4b0 100644 --- a/pkg/ebpf/instruction_generators.go +++ b/pkg/ebpf/instruction_generators.go @@ -17,6 +17,7 @@ package ebpf import ( "buzzer/pkg/rand" pb "buzzer/proto/ebpf_go_proto" + protobuf "github.com/golang/protobuf/proto" ) // GenerateRandomAluInstruction provides a random ALU operation with either @@ -234,3 +235,12 @@ func generateRegAluInstruction(op pb.AluOperationCode, insClass pb.InsClass, dst return newAluInstruction(op, insClass, dstReg, srcReg) } + +func DuplicateProgram(prog []*pb.Instruction) []*pb.Instruction { + ret := []*pb.Instruction{} + for _, ins := range prog { + insCp := protobuf.Clone(ins).(*pb.Instruction) + ret = append(ret, insCp) + } + return ret +} diff --git a/pkg/ebpf/poc_generator.go b/pkg/ebpf/poc_generator.go index 0bf7443..6fa98ca 100644 --- a/pkg/ebpf/poc_generator.go +++ b/pkg/ebpf/poc_generator.go @@ -19,12 +19,11 @@ import ( "errors" "fmt" jsonpb "github.com/golang/protobuf/jsonpb" + protobuf "github.com/golang/protobuf/proto" "os" ) -// GeneratePoc generates a c program that can be used to reproduce fuzzer -// test cases. -func GeneratePoc(program *pb.Program) error { +func writeJsonProgram(program *pb.Program) error { m := &jsonpb.Marshaler{ OrigName: true, EnumsAsInts: false, @@ -43,5 +42,159 @@ func GeneratePoc(program *pb.Program) error { fmt.Printf("Writing eBPF PoC %q.\n", f.Name()) _, err = f.Write([]byte(textpbData)) return errors.Join(err, f.Close()) +} + +func copyProgram(program *pb.Program) *pb.Program { + return protobuf.Clone(program).(*pb.Program) +} + +func Minimizer(program *pb.Program, bugCheckFunction func(*pb.Program) bool, footerSize int) (*pb.Program, error) { + fmt.Println("Running minimizer...") + minimalProgram := copyProgram(program) + minimalProgramInstructions := DuplicateProgram(program.Functions[0].Instructions) + if footerSize > len(minimalProgramInstructions) { + return nil, fmt.Errorf("footerSize > len(program) (%d vs %d)", footerSize, len(minimalProgramInstructions)) + } + + minimalProgram.Functions[0].Instructions = minimalProgramInstructions + noop := Jmp(0) + + phase1 := func() bool { + // First step: change all instructions not contributing to the bug + // to be noops. + changed := true + fmt.Println("Step 1: noop'ing instructions") + roundCount := 1 + nooped := 0 + for changed { + changed = false + for i := 0; i < len(minimalProgramInstructions)-footerSize; i++ { + fmt.Printf("\tattempting to noop instruction: %d (of %d), nooped: %d \r", i, len(minimalProgramInstructions), nooped) + prev := minimalProgramInstructions[i] + + // Don't process any previous noops + if protobuf.Equal(prev, noop) { + continue + } + + // Replace instruction with a noop + minimalProgramInstructions[i] = noop + if bugCheckFunction(minimalProgram) { + changed = true + nooped += 1 + } else { + minimalProgramInstructions[i] = prev + } + } + fmt.Printf("\n\tround %d finished\n", roundCount) + roundCount += 1 + } + return nooped != 0 + } + + phase2 := func() bool { + changesDone := false + // Second step: reduce the jumps to a minimum offset. + fmt.Println("--------------------------------------") + fmt.Println("Step 2: minimizing jumps") + for i := 0; i < len(minimalProgramInstructions); i++ { + insn := minimalProgramInstructions[i] + switch insn.Opcode.(type) { + case *pb.Instruction_JmpOpcode: + if insn.Offset == 0 { + continue + } + previousOffset := insn.Offset + insn.Offset -= 1 + fmt.Printf("\tminimizing offset of instruction %d (initial: %d)\n", i, previousOffset) + for bugCheckFunction(minimalProgram) && insn.Offset != 0 { + changesDone = true + previousOffset = insn.Offset + insn.Offset -= 1 + fmt.Printf("\t\treducing offset to: %d \r", insn.Offset) + } + fmt.Printf("\t\tfound minimal offset: %d \n", previousOffset) + insn.Offset = previousOffset + default: + continue + } + } + return changesDone + } + phase3 := func() (bool, []*pb.Instruction) { + fmt.Println("--------------------------------------") + fmt.Println("Step 3: removing noop instructions") + removed := 0 + minimalModified := []*pb.Instruction{} + for i := 0; i < len(minimalProgramInstructions); i++ { + insn := minimalProgramInstructions[i] + fmt.Printf("\tattempting to remove instruction: %d, removed: %d \r", i, removed) + if !protobuf.Equal(insn, noop) { + minimalModified = append(minimalModified, insn) + continue + } + + candidateProgram := []*pb.Instruction{} + if i != (len(minimalProgramInstructions) - 1) { + candidateProgram = append(minimalModified, minimalProgramInstructions[i+1:]...) + } else { + candidateProgram = minimalModified + } + + // If bug is no longer present then the instruction cannot be removed + if !bugCheckFunction(&pb.Program{ + Functions: []*pb.Functions{ + { + Instructions: candidateProgram, + }, + }, + Maps: program.Maps, + }) { + minimalModified = append(minimalModified, insn) + } else { + // if the bug is present without the instruction then it can + // be removed from the program. + removed += 1 + } + } + fmt.Println() + return removed != 0, minimalModified + } + + changesDone := true + iteration := 0 + for changesDone { + fmt.Printf("> iteration: %d of minimizer\n", iteration) + changesDone = false + + // Noop instructions. + changes := phase1() + changesDone = changesDone || changes + + // Reduce jumps. + changes = phase2() + changesDone = changesDone || changes + + // Remove noops. + changes, minInstructions := phase3() + + changesDone = changesDone || changes + minimalProgramInstructions = minInstructions + minimalProgram.Functions[0].Instructions = minimalProgramInstructions + + iteration += 1 + } + return minimalProgram, nil + +} + +// GeneratePoc generates a c program that can be used to reproduce fuzzer +// test cases. +func GeneratePoc(program *pb.Program, bugCheckFunction func(*pb.Program) bool, footerSize int) error { + minimizedProgram, err := Minimizer(program, bugCheckFunction, footerSize) + if err != nil { + return err + } + return writeJsonProgram(minimizedProgram) } diff --git a/pkg/units/control.go b/pkg/units/control.go index 0618142..3e29490 100644 --- a/pkg/units/control.go +++ b/pkg/units/control.go @@ -178,7 +178,6 @@ func (cu *Control) runEbpf(prog *epb.Program) error { ok := cu.strat.OnExecuteDone(cu.ffi, exRes) if !ok { fmt.Println("Program produced unexpected results") - ebpf.GeneratePoc(prog) } return nil } diff --git a/pkg/units/ffi.go b/pkg/units/ffi.go index e8750a4..45d5045 100644 --- a/pkg/units/ffi.go +++ b/pkg/units/ffi.go @@ -165,7 +165,9 @@ func (e *FFI) ValidateEbpfProgram(encodedProgram *fpb.EncodedProgram) (*fpb.Vali if err != nil { return nil, err } - e.MetricsUnit.RecordVerificationResults(res) + if e.MetricsUnit != nil { + e.MetricsUnit.RecordVerificationResults(res) + } return res, nil }