Panic on invalid programs and bad seed inputs#94
Conversation
Our custom mutators are the only supported producers of IR programs and are designed to *always* create valid programs. Thus validating programs on every mutation iteration is useless work for all supported use cases. The only time it's helpful is when not using our custom mutators or when the seed corpus was generated by something other than the current custom mutators. Subsequent commits will remove program validation entirely, instead relying on the ProgramBuilder and executor panicking when programs are invalid.
Now that smite-ir-mutator no longer calls Program::validate, nothing else uses it.
Make the executor treat ill-formed IR programs as panics rather than recoverable errors. We now require our AFL++ custom mutator shim to be used when fuzzing the IR scenario -- using the default AFL++ mutators or any other mutator library is highly likely to produce ill-formed programs that trigger panics. A major benefit of this approach is that any bugs in our mutators or generators can surface as panics rather than being hidden as errors that the executor recovered from. The tradeoff is that manually-created seeds or even seeds from previous versions of Smite will cause panics rather than being silently discarded.
I'm not sure how future proof this is. I think this approach will force mutators to do two things:
IMO, mutators should be dumb and try to do only (1). I guess doing (2) is fine as well (and we already do it), as long as we're talking about structural validity, i.e. validity of the program graph, because the information required to enforce it is already provided to the mutators (in the form of the program itself). But as discussed in #75, once we start implementing more advanced mutation schemes, this means we will have to maintain a rulebook of sorts for every new mutator that tries to modify the semantic structure of the program.
Okay, so the "rulebook" is supposed to reside in the executor. I don't feel very strongly about the executor panicking or not panicking, but I think it makes more sense for |
|
I think with this we get slightly faster code, since all the validation paths are gone. Previously, we had to maintain the validity of IR programs for the custom mutator anyway, and the same still holds now. So the main thing we lose is the ability to use AFL++’s default mutators, which were not very helpful anyway (especially once we have more interesting mutators in smite). I would lean towards the simpler and faster approach here, and be explicit that IR programs should use a custom mutator instead of wasting cycles on Skip. I think optimizations and performance improvements on the IR or mutator side are much more likely to uncover target bugs than relying on AFL++’s default mutators.
Though right now, adding a new operation is not backward compatible anyway, so previously collected corpora would already become invalid. Because of that, I don’t think resuming old corpora after adding new operations would help much for further fuzzing, so it is probably fine for them to be paniced/discarded completely. |
| let key_bytes = resolve_private_key(&variables, instr.inputs[0]); | ||
| let sk = SecretKey::from_slice(&key_bytes) | ||
| .map_err(|_| ExecuteError::InvalidPrivateKey)?; | ||
| .expect("mutator produced an invalid private key"); |
There was a problem hiding this comment.
As written, the message suggests that we're expecting the mutator to produce an invalid private key (but it didn't), while we're actually expecting that it didn't (but it did). So similar to how expect() is used in other places, I suggest this:
| .expect("mutator produced an invalid private key"); | |
| .expect("valid private key"); |
I think we are in agreement here. I'm using the term valid in the structural sense -- i.e. a valid program is runnable by the executor. I want mutators to always produce runnable programs but otherwise be ignorant of the higher-level LN protocol semantics (e.g., message sequencing). I think generators are better suited to encapsulate the semantics, and mutators should be free to break them. The main changes proposed in this PR are really:
|
Right.
Okay, this is where I think the friction arises from. Both
Yeah, I don't feel too strongly about this, either way is fine. |
The main semantic rule I foresee needing to follow at this point is not trying to receive a message that will never come (e.g., And assuming mutators will always follow the affine type rules, I don't think there's any substantial benefit from having |
True, for now we're mostly trying to avoid receiving a message that the target won't send and #97 should solve that. But there's at least one more case I can think of where we'll need to add additional semantic rules, or modify the current one:
The current solution of 'custom sends + affine state variables' cannot prevent this, and this is something that will come into play when we deal with concurrent operations for multiple channels (planned for Milestone 9+). This is just one example that I ran into while conducting a brief mental review of the affine architecture, and it's very possible that more such cases exist, perhaps even higher up in the Milestones. This is why I'd prefer the architecture to be as accommodating of additional semantic rules as possible.
They will, but not in the same sense as the structural rules. We need to allow the mutators to break semantics and catch these violations in a separate step: somewhere it makes sense to encode LN's protocol semantics. Panicking isn't useful here because there's nothing to "catch early and fix". Unless of course, we do something like this in the advanced mutators: But we still need to validate the program so... Edit: Thought about this for some time and maybe there's a middle ground: replace Since we need to perform an O(N^2) pass over instructions to check for semantic errors, there won't be much of a performance gain with this new validation, but I guess it would simplify the code a bit. |
I don't understand this issue. Can you explain it in detail?
Do you mean that we would send a message intended for one channel and attempt to receive a message for a different channel? I don't think it's an issue -- the receive shouldn't time out regardless of which channel the original message was for.
Again, I don't think we want mutators validating semantics at all. They should only care about the program being executable.
Besides the existence of a
Yeah, I'm still not convinced that we should be validating semantics at all. We shouldn't care about semantics at the program/IR/mutator level. Generators are much better suited to create semantically-valid flows. |
With the
Exactly.
Right, mutators create structurally valid programs by construction. But to solve issue #75, they need to respect semantic rules as well (which is just "no
I'm not sure I understand. The entire premise behind validating semantics (again, which is just "no
How do we do either without encoding the semantic rule at program/IR/mutator level? I understand that it is Generators that are supposed to deal with intricate flows, but that has nothing to with preserving mutation validity. |
We're talking past each other. By creating affine types with their own single-use rule, we are turning the limited semantic knowledge we do care about into a structural rule in the IR itself. Mutators and generators will need to respect the new rule. Other semantics will not be structurally encoded in the IR and should be completely ignored by mutators.
All we need is to enforce the new IR single-use rule, which will be part of the IR just like SSA-form and the type system we currently have. I don't think it's unreasonable or difficult to do this in every mutator, just like we currently do for preserving the SSA rules and type correctness.
When it's this easy to follow the structural rules, doing the shotgun approach (creating invalid mutations and catching them with
Yep -- I think it's best to do (1) for the new affine type single-use rule, which will become a structural part of the IR. Calling a |
Oh okay, so you meant making the mutators respect every structural rule in the IR (which now includes the one semantic rule we've encoded as a structural one).
True.
We can defer deleting an affine producer until it's consumer exists, which should be integrable in the type-matching pass we already perform.
True, since Generators supposed to be sources of truth: they only produce valid programs.
The changes needed for this mutator should already be encoded in
Hmm, since we've modified the IR to no longer have
And make the executor panic in case of a violation? Since mutators are now supposed to never violate rules (affine or not affine), I guess that makes sense. |
Demonstrates how we can simplify our code using the approach described in #92 (comment).
Pros:
Cons:
Lightly tested with a 10 minute LDK fuzzing session. If we decide we want to do this, we'll need to test longer runs of all 4 targets.