This is an archive of the discontinued LLVM Phabricator instance.

[libFuzzer] Handle unstable edges by poisoning unstable edges
AcceptedPublic

Authored by kevinwkt on Jul 19 2018, 5:09 PM.

Details

Summary

Added a new mode within flag -handle_unstable for new unstable handling algorithm that does the following:

When you invalidate an edge in TracePC::UpdateUnstableCounters(), you increase a counter value.
Subtract the counter value from total number of new edges found for that input to get the total number of stable edges found.
Collect Features only if new stable edges are found.

This way we would be rewarding an input of finding a new edge only if that edge was deterministically found.

Diff Detail

Event Timeline

kevinwkt created this revision.Jul 19 2018, 5:09 PM

I don't think this will work. This just subtracts the number of unstable edges without actually removing those features? Or do i misunderstand how this actually works?

lib/fuzzer/FuzzerLoop.cpp
500–501

Please don't make multi-line if-bodies without curly braces

I don't think this will work. This just subtracts the number of unstable edges without actually removing those features? Or do i misunderstand how this actually works?

This is Max's method of handling edges described before. (In the chromium bug website.)
This poisons an edge definitely since it won't give weight to a poisoned edge on future runs since it's feature has already been collected from what I remember.

I don't think this will work. This just subtracts the number of unstable edges without actually removing those features? Or do i misunderstand how this actually works?

This is Max's method of handling edges described before. (In the chromium bug website.)

Could you link me to this please?

This poisons an edge definitely since it won't give weight to a poisoned edge on future runs since it's feature has already been collected from what I remember.

It does? where? Suppose a testcase has two new edges one unstable and one stable so it gets added to the corpus. Then we find a smaller testcase with the same stable feature, but the unstable feature isn't removed. Under this method the first testcase stays in the corpus right?

I don't think this will work. This just subtracts the number of unstable edges without actually removing those features? Or do i misunderstand how this actually works?

This is Max's method of handling edges described before. (In the chromium bug website.)

Could you link me to this please?

This poisons an edge definitely since it won't give weight to a poisoned edge on future runs since it's feature has already been collected from what I remember.

It does? where? Suppose a testcase has two new edges one unstable and one stable so it gets added to the corpus. Then we find a smaller testcase with the same stable feature, but the unstable feature isn't removed. Under this method the first testcase stays in the corpus right?

Yes, it would stay in the corpus.
Link: https://bugs.chromium.org/p/chromium/issues/detail?id=854276

Dor1s added a comment.Jul 20 2018, 9:10 AM

I don't think this will work. This just subtracts the number of unstable edges without actually removing those features? Or do i misunderstand how this actually works?

I think that was sort of an advantage of the algorithm. We do not remove unstable features, so that any new inputs triggering them won't be considered as new at all, but we don't add such inputs to the corpus to prevent its explosion.

Kevin, we need a test for this one as well. Also, maybe let's focus on finishing the first implementation, as this CL depends on it, and you probably don't want to rebase it and resolve conflicts several times.

metzman requested changes to this revision.Jul 20 2018, 5:49 PM

I don't think this will work. This just subtracts the number of unstable edges without actually removing those features? Or do i misunderstand how this actually works?

I think that was sort of an advantage of the algorithm. We do not remove unstable features, so that any new inputs triggering them won't be considered as new at all, but we don't add such inputs to the corpus to prevent its explosion.

Kevin agreed with me offline that this doesn't prevent corpus growth and does something other than intended. We will think about what it does before proceeding.

This revision now requires changes to proceed.Jul 20 2018, 5:49 PM
Dor1s added a comment.Jul 20 2018, 7:37 PM

I don't think this will work. This just subtracts the number of unstable edges without actually removing those features? Or do i misunderstand how this actually works?

I think that was sort of an advantage of the algorithm. We do not remove unstable features, so that any new inputs triggering them won't be considered as new at all, but we don't add such inputs to the corpus to prevent its explosion.

Kevin agreed with me offline that this doesn't prevent corpus growth and does something other than intended. We will think about what it does before proceeding.

Ok, let's discuss on Monday. Looks like I'm missing something!

I don't think this will work. This just subtracts the number of unstable edges without actually removing those features? Or do i misunderstand how this actually works?

I think that was sort of an advantage of the algorithm. We do not remove unstable features, so that any new inputs triggering them won't be considered as new at all, but we don't add such inputs to the corpus to prevent its explosion.

Kevin agreed with me offline that this doesn't prevent corpus growth and does something other than intended. We will think about what it does before proceeding.

Ok, let's discuss on Monday. Looks like I'm missing something!

I was incorrect in previous reply btw. It does prevent some kinds of corpus growth but not others.

kevinwkt updated this revision to Diff 156910.Jul 23 2018, 3:59 PM

PTAL

Created tests.

Dor1s added a comment.Jul 23 2018, 9:07 PM

Looks great! Left a couple comments + let's clean up the test. Since the test is specific for handle_unstable=3 mode, can we make it shorter, e.g. leave only 1 or 2 deterministic function, and so on. Also, let's rename the source file to HandleUnstablePoisonUnstableTest.cpp, the test file can be renamed to handle_unstable_poison_unstable.test for better readability.

lib/fuzzer/FuzzerLoop.cpp
463

no need to declare this variable here, move it to line 470, e.g. int NumNewUnstableEdges = ....

lib/fuzzer/FuzzerTracePC.cpp
86–87

let's make this condition the first one, so it would fit 2 lines, and then else if (UnstableMode == ZeroUnstable) case

kevinwkt updated this revision to Diff 157117.Jul 24 2018, 1:40 PM
kevinwkt marked an inline comment as done.

PTAL
Fixed Max's comments on variable declaration and initialization and moved tests into a single file.

Dor1s added a comment.Jul 24 2018, 3:48 PM

Can you re-phrase the CL description please, because it looks like a chat message I've sent to you in the past :)

lib/fuzzer/FuzzerInternal.h
70

I'm not a bit fan of using int type where values actually cannot be negative. IMO size_t is de facto standard type for unsigned values when you don't need a certain precision (e.g. uint32_t). Also, when unsure, always check the code around the project, e.g. https://github.com/llvm-mirror/compiler-rt/blob/master/lib/fuzzer/FuzzerCorpus.h#L28 uses size_t for features as well as execPerSec or getTotalNumberOfRuns a few lines above use size_t for another numerical value that cannot be negative. So, please use size_ instead of int here and in UpdateUnstableCounters.

lib/fuzzer/FuzzerLoop.cpp
517

This place is also a good hint regarding which type you should use.

lib/fuzzer/FuzzerTracePC.cpp
86–87

I think we can invert the condition and do return when it's true, just to decrease the indentation of the lines 88-95. Did Matt not accept that style in one of the past reviews?

lib/fuzzer/FuzzerTracePC.h
77–78

Looks like both FuzzerLoop and FuzzerDriver include FuzzerTracePC.h, so you can move this type definition outside of TracePC class and use it in functions accepting UnstableMode instead of int type.

test/fuzzer/HandleUnstableTest.cpp
47

Why we have 1000 here? Wouldn't things become deterministic after that?

52

is it left from local debugging or you want to have this in the test?

kevinwkt updated this revision to Diff 157175.Jul 24 2018, 5:11 PM
kevinwkt marked 5 inline comments as done.

Changed UpdateUnstableCounters to get rid of indentation.
Changed ints to size_ts.

2 questions, @metzman @Dor1s .

  1. For changing UnstableMode to UnstableOptions, FuzzerDriver and FuzzerLoop do include TracePC.h, but FuzzerOptions does not. Where would be the right place to cast the int from FuzzerOptions? Would it be fine to cast it right before it gets sent to CheckForUnstableCounters in RunOne? (inside FuzzerLoop)
  1. I did put 1000 for the tests with the purpose of a test starting as non deterministic but then turning deterministic. Because when it turns deterministic, collect features take all touched functions along with the new deterministic edge. (Hence it collects t0() in PoisonUnstable). Is this a bad practice? (printf() was for local debugging. Got erased.)
Dor1s added a comment.Jul 25 2018, 6:28 AM

Changed UpdateUnstableCounters to get rid of indentation.
Changed ints to size_ts.

2 questions, @metzman @Dor1s .

  1. For changing UnstableMode to UnstableOptions, FuzzerDriver and FuzzerLoop do include TracePC.h, but FuzzerOptions does not. Where would be the right place to cast the int from FuzzerOptions? Would it be fine to cast it right before it gets sent to CheckForUnstableCounters in RunOne? (inside FuzzerLoop)

I think it's fine to move enum HandleUnstableOptions declaration into FuzzerOptions.h, it actually seems to be even better place to me than FuzzerTracePC.h.

  1. I did put 1000 for the tests with the purpose of a test starting as non deterministic but then turning deterministic. Because when it turns deterministic, collect features take all touched functions along with the new deterministic edge. (Hence it collects t0() in PoisonUnstable). Is this a bad practice? (printf() was for local debugging. Got erased.)

Hmmm, I see, thanks. Probably it's fine, but add a comment before if (c < 1000) { to clarify that.

kevinwkt updated this revision to Diff 157358.Jul 25 2018, 2:21 PM
kevinwkt marked 2 inline comments as done.

PTAL
Added comment before c < 1000
Moved enum to FuzzerOptions and included them on necessary files.

PTAL
Added comment before c < 1000
Moved enum to FuzzerOptions and included them on necessary files.

I don't see the test source file anymore. Please add it and ask @morehouse to review.

test/fuzzer/handle-unstable.test
23–31

please fix the casing here and on line 33 to be consistent with line 48

kevinwkt updated this revision to Diff 157399.Jul 25 2018, 4:44 PM
kevinwkt marked 2 inline comments as done.

PTAL
Updated the syntax for test files.

kevinwkt updated this revision to Diff 157400.Jul 25 2018, 4:54 PM

ptal
Fixed syntax again.

kevinwkt edited the summary of this revision. (Show Details)Jul 25 2018, 4:58 PM

Changed UpdateUnstableCounters to get rid of indentation.
Changed ints to size_ts.

2 questions, @metzman @Dor1s .

  1. For changing UnstableMode to UnstableOptions, FuzzerDriver and FuzzerLoop do include TracePC.h, but FuzzerOptions does not. Where would be the right place to cast the int from FuzzerOptions? Would it be fine to cast it right before it gets sent to CheckForUnstableCounters in RunOne? (inside FuzzerLoop)

I think it's fine to move enum HandleUnstableOptions declaration into FuzzerOptions.h, it actually seems to be even better place to me than FuzzerTracePC.h.

  1. I did put 1000 for the tests with the purpose of a test starting as non deterministic but then turning deterministic. Because when it turns deterministic, collect features take all touched functions along with the new deterministic edge. (Hence it collects t0() in PoisonUnstable). Is this a bad practice? (printf() was for local debugging. Got erased.)

Hmmm, I see, thanks. Probably it's fine, but add a comment before if (c < 1000) { to clarify that.

Agreed, I think this is fine. It's not really non-deterministic, it's just stateful from libFuzzer's point of view, so it's not like we are actually calling rand without a seed in a test.

The one thing I'd like to see added is a brief explanation of what 3 does in FuzzerFlagsDef, though I don't think it is 100% necessary, since this flag is very experimental and somewhat complicated to understand without knowing a lot about libFuzzer.

metzman accepted this revision.Jul 25 2018, 5:00 PM
This revision is now accepted and ready to land.Jul 25 2018, 5:00 PM
Dor1s edited subscribers, added: kcc, llvm-commits; removed: morehouse.Jul 25 2018, 5:29 PM
Dor1s added inline comments.
lib/fuzzer/FuzzerTracePC.cpp
91

Can you move this to line 89 please, and then have switch (UnstableMode) ? That way it would be slightly more readable and straightforward.

kevinwkt updated this revision to Diff 157411.Jul 25 2018, 5:45 PM

ptal
Fixed bug that was introduced.

kevinwkt marked an inline comment as done.Jul 25 2018, 5:47 PM
kevinwkt added inline comments.
lib/fuzzer/FuzzerTracePC.cpp
91

We are supposed to look for the amount of New Unstable Edges.
The way we do this is check if has already been marked as unstable before.
This is also the reason UnstableCounters[UnstableIdx].IsUnstable = true happens after the check.

This bug is now fixed.

Dor1s accepted this revision.Jul 25 2018, 10:59 PM

LGTM

lib/fuzzer/FuzzerFlags.def
120

Frankly, I don't understand this explanation... The problem might be on my side though, let's see what Matt thinks.

So this is basically the ZeroUnstable mode with one less pass over the counters?

lib/fuzzer/FuzzerDriver.cpp
627

This cast looks odd since we're comparing int to enum without a cast right before this. Probably just do implicit cast.

lib/fuzzer/FuzzerFlags.def
120

This confuses me too.

lib/fuzzer/FuzzerOptions.h
23

Might be cleaner to do something like

enum class UnstableMode {
  Disabled = 0,
  Min = 1,
  Zero = 2,
  Poison = 3,
};
Dor1s added inline comments.Jul 26 2018, 9:58 AM
lib/fuzzer/FuzzerDriver.cpp
627

That's what I though initially, but surprisingly, implicit cast doesn't work in that direction, only from enum to an integer!

Dor1s added inline comments.Jul 26 2018, 10:00 AM
lib/fuzzer/FuzzerDriver.cpp
627
$ cat test.cpp 

enum Enum {
  One = 1,
  Two = 2
};

int main(int argc, char* argv[]) {
  Enum E = argc;
  return 0;
}


$ clang++ test.cpp 
test.cpp:8:8: error: cannot initialize a variable of type 'Enum' with an lvalue of type 'int'
  Enum E = argc;
       ^   ~~~~
1 error generated.
kevinwkt marked an inline comment as done.Jul 26 2018, 10:11 AM

So this is basically the ZeroUnstable mode with one less pass over the counters?

"ZeroUnstable" puts 0 in unstable edges and pretends it was never hit.
"PoisonUnstable" takes into account new unstable edges only if a new stable edge is also found.
This comes from the assumption that unstable edges should be given weight as long as a new deterministic edge is found to reward that input.
We only take into account new unstable edges (and not any unstable edge hit), since we believe that this "reward" for reaching that edge has been given to other inputs.

Hope this makes it a bit clearer.

Won't the result be the same? This mode will add an input to the corpus as long as there's at least one new stable edge. But so will ZeroUnstable.

Won't the result be the same? This mode will add an input to the corpus as long as there's at least one new stable edge. But so will ZeroUnstable.

I think the adding to corpus will be similar (it will certainly start out the same way) but because you collect features of these unstable edges while in "Zero" you do not, corpus addition will be different the longer you run this.

Right. The biggest differences I see are

  1. If a new input exercises an edge stably while a previous input exercised that edge unstably, ZeroUnstable will add the new input while PoisonUnstable may not.
  2. If an input sometimes exercises an edge twice and sometimes not at all, ZeroUnstable won't add that input while PoisonUnstable will. This is because we record additional features for inputs that hit the same edge multiple times, but PoisonUnstable only subtracts one of those features from the total.

Do we expect this to perform better in some scenarios?

Right. The biggest differences I see are

  1. If a new input exercises an edge stably while a previous input exercised that edge unstably, ZeroUnstable will add the new input while PoisonUnstable may not.
  2. If an input sometimes exercises an edge twice and sometimes not at all, ZeroUnstable won't add that input while PoisonUnstable will. This is because we record additional features for inputs that hit the same edge multiple times, but PoisonUnstable only subtracts one of those features from the total.

Do we expect this to perform better in some scenarios?

This is a way to handle unstableness at a small extent, by accepting unstable edges at certain scenarios.
We believe that this "handles unstableness" compared to normal libfuzzer, but its not as extreme as ZeroUnstable.
Since this is slightly faster than ZeroUnstable and we use poison the unstable which we assume will cause less inputs to be added to the corpus in the long run, we think this might produce an interesting result.

We need to update the summary and flag description then to make it clear what this new mode does.

kevinwkt updated this revision to Diff 157566.EditedJul 26 2018, 1:40 PM
kevinwkt marked 7 inline comments as done.

ptal
Changed to strictly typed enum, and changed names within enum.
Changed description for FuzzerFlag.

The current flag description still doesn't tell the whole story.

If we detect a new input has unstable edges, we're adding the unstable edges to the corpus features even if we don't add the input itself. This means any time an edge is found to be unstable, we completely ignore that edge forever. Even if a later input exercises the edge stably, we will not consider that edge again.

Is this intentional, or a bug in the implementation?

lib/fuzzer/FuzzerLoop.cpp
496

These casts are quite awkward. Maybe we should just keep Options.HandleUnstable as an int.

lib/fuzzer/FuzzerOptions.h
23

I don't really like these Start and End values, especially since 0 should probably be Disabled.

Dor1s added inline comments.Jul 27 2018, 2:13 PM
lib/fuzzer/FuzzerLoop.cpp
496

I sort of insisted on not using an int in there, but final decision would yours :)

morehouse added inline comments.Jul 27 2018, 2:15 PM
lib/fuzzer/FuzzerLoop.cpp
496

Similar libFuzzer options all use int, so at least for consistency I think we should use an int.

I would prefer to get @kcc's opinion in a separate refactoring patch if you would like to switch from ints to enums.

Dor1s added inline comments.Jul 27 2018, 2:28 PM
lib/fuzzer/FuzzerLoop.cpp
496

Yeah... but there isn't any other option that selects different modes. All of them are either boolean flags, integer values, or a close_fd_mask thing. Either way, I hope we'll choose the best strategy and delete the others in the upcoming weeks, so this option will become a boolean flag after all.

metzman resigned from this revision.Aug 21 2018, 7:15 PM
morehouse resigned from this revision.Aug 22 2018, 9:13 AM