@@ -19,12 +19,11 @@ import (
1919 "errors"
2020 "fmt"
2121 jsonpb "github.com/golang/protobuf/jsonpb"
22+ protobuf "github.com/golang/protobuf/proto"
2223 "os"
2324)
2425
25- // GeneratePoc generates a c program that can be used to reproduce fuzzer
26- // test cases.
27- func GeneratePoc (program * pb.Program ) error {
26+ func writeJsonProgram (program * pb.Program ) error {
2827 m := & jsonpb.Marshaler {
2928 OrigName : true ,
3029 EnumsAsInts : false ,
@@ -43,5 +42,159 @@ func GeneratePoc(program *pb.Program) error {
4342 fmt .Printf ("Writing eBPF PoC %q.\n " , f .Name ())
4443 _ , err = f .Write ([]byte (textpbData ))
4544 return errors .Join (err , f .Close ())
45+ }
46+
47+ func copyProgram (program * pb.Program ) * pb.Program {
48+ return protobuf .Clone (program ).(* pb.Program )
49+ }
50+
51+ func Minimizer (program * pb.Program , bugCheckFunction func (* pb.Program ) bool , footerSize int ) (* pb.Program , error ) {
52+ fmt .Println ("Running minimizer..." )
53+ minimalProgram := copyProgram (program )
54+ minimalProgramInstructions := DuplicateProgram (program .Functions [0 ].Instructions )
55+ if footerSize > len (minimalProgramInstructions ) {
56+ return nil , fmt .Errorf ("footerSize > len(program) (%d vs %d)" , footerSize , len (minimalProgramInstructions ))
57+ }
58+
59+ minimalProgram .Functions [0 ].Instructions = minimalProgramInstructions
60+ noop := Jmp (0 )
61+
62+ phase1 := func () bool {
63+ // First step: change all instructions not contributing to the bug
64+ // to be noops.
65+ changed := true
66+ fmt .Println ("Step 1: noop'ing instructions" )
67+ roundCount := 1
68+ nooped := 0
69+ for changed {
70+ changed = false
71+ for i := 0 ; i < len (minimalProgramInstructions )- footerSize ; i ++ {
72+ fmt .Printf ("\t attempting to noop instruction: %d (of %d), nooped: %d \r " , i , len (minimalProgramInstructions ), nooped )
73+ prev := minimalProgramInstructions [i ]
74+
75+ // Don't process any previous noops
76+ if protobuf .Equal (prev , noop ) {
77+ continue
78+ }
79+
80+ // Replace instruction with a noop
81+ minimalProgramInstructions [i ] = noop
82+ if bugCheckFunction (minimalProgram ) {
83+ changed = true
84+ nooped += 1
85+ } else {
86+ minimalProgramInstructions [i ] = prev
87+ }
88+ }
89+ fmt .Printf ("\n \t round %d finished\n " , roundCount )
90+ roundCount += 1
91+ }
92+ return nooped != 0
93+ }
94+
95+ phase2 := func () bool {
96+ changesDone := false
97+ // Second step: reduce the jumps to a minimum offset.
98+ fmt .Println ("--------------------------------------" )
99+ fmt .Println ("Step 2: minimizing jumps" )
100+ for i := 0 ; i < len (minimalProgramInstructions ); i ++ {
101+ insn := minimalProgramInstructions [i ]
102+ switch insn .Opcode .(type ) {
103+ case * pb.Instruction_JmpOpcode :
104+ if insn .Offset == 0 {
105+ continue
106+ }
107+ previousOffset := insn .Offset
108+ insn .Offset -= 1
109+ fmt .Printf ("\t minimizing offset of instruction %d (initial: %d)\n " , i , previousOffset )
110+ for bugCheckFunction (minimalProgram ) && insn .Offset != 0 {
111+ changesDone = true
112+ previousOffset = insn .Offset
113+ insn .Offset -= 1
114+ fmt .Printf ("\t \t reducing offset to: %d \r " , insn .Offset )
115+ }
116+ fmt .Printf ("\t \t found minimal offset: %d \n " , previousOffset )
117+ insn .Offset = previousOffset
118+ default :
119+ continue
120+ }
121+ }
122+ return changesDone
123+ }
124+ phase3 := func () (bool , []* pb.Instruction ) {
125+ fmt .Println ("--------------------------------------" )
126+ fmt .Println ("Step 3: removing noop instructions" )
127+ removed := 0
128+ minimalModified := []* pb.Instruction {}
129+ for i := 0 ; i < len (minimalProgramInstructions ); i ++ {
130+ insn := minimalProgramInstructions [i ]
131+ fmt .Printf ("\t attempting to remove instruction: %d, removed: %d \r " , i , removed )
132+ if ! protobuf .Equal (insn , noop ) {
133+ minimalModified = append (minimalModified , insn )
134+ continue
135+ }
136+
137+ candidateProgram := []* pb.Instruction {}
138+ if i != (len (minimalProgramInstructions ) - 1 ) {
139+ candidateProgram = append (minimalModified , minimalProgramInstructions [i + 1 :]... )
140+ } else {
141+ candidateProgram = minimalModified
142+ }
143+
144+ // If bug is no longer present then the instruction cannot be removed
145+ if ! bugCheckFunction (& pb.Program {
146+ Functions : []* pb.Functions {
147+ {
148+ Instructions : candidateProgram ,
149+ },
150+ },
151+ Maps : program .Maps ,
152+ }) {
153+ minimalModified = append (minimalModified , insn )
154+ } else {
155+ // if the bug is present without the instruction then it can
156+ // be removed from the program.
157+ removed += 1
158+ }
159+ }
160+ fmt .Println ()
161+ return removed != 0 , minimalModified
162+ }
163+
164+ changesDone := true
165+ iteration := 0
166+ for changesDone {
167+ fmt .Printf ("> iteration: %d of minimizer\n " , iteration )
168+ changesDone = false
169+
170+ // Noop instructions.
171+ changes := phase1 ()
172+ changesDone = changesDone || changes
173+
174+ // Reduce jumps.
175+ changes = phase2 ()
176+ changesDone = changesDone || changes
177+
178+ // Remove noops.
179+ changes , minInstructions := phase3 ()
180+
181+ changesDone = changesDone || changes
182+ minimalProgramInstructions = minInstructions
183+ minimalProgram .Functions [0 ].Instructions = minimalProgramInstructions
184+
185+ iteration += 1
186+ }
46187
188+ return minimalProgram , nil
189+
190+ }
191+
192+ // GeneratePoc generates a c program that can be used to reproduce fuzzer
193+ // test cases.
194+ func GeneratePoc (program * pb.Program , bugCheckFunction func (* pb.Program ) bool , footerSize int ) error {
195+ minimizedProgram , err := Minimizer (program , bugCheckFunction , footerSize )
196+ if err != nil {
197+ return err
198+ }
199+ return writeJsonProgram (minimizedProgram )
47200}
0 commit comments