- Notifications
You must be signed in to change notification settings - Fork3
A powerful and easy-to-use fuzzing framework in Nim for C/C++/Obj-C targets
License
status-im/nim-drchaos
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Fuzzing is a technique for automated bug detection that involves providing random inputsto a target program to induce crashes. This approach can increase test coverage, enablingthe identification of edge cases and more efficient triggering of bugs.
Drchaos extends the Nim interface to LLVM/Clang libFuzzer, an in-process, coverage-guided,and evolutionary fuzzing engine, while also introducing support forstructured fuzzing.To utilize this functionality, users must specify the input type as a parameter for thetarget function, and the fuzzer generates valid inputs. This process employs valueprofiling to direct the fuzzer beyond these comparisons more efficiently than relying onthe probability of finding the exact sequence of bytes by chance.
Creating a fuzz target by defining a data type and a target function that performsoperations and verifies if the invariants are maintained via assert conditions is usuallyan uncomplicated task for most scenarios. For more information on creating effective fuzztargets, please refer toWhat makes a good fuzz targetOnce the target function is defined, thedefaultMutator
can be called with that functionas argument.
A basic fuzz target, such as verifying that the software under test remains stable withoutcrashing by defining a fixed-size type, can suffice:
import drchaosprocfuzzMe(s:string, a, b, c:int32)=# The function being tested.if a==0xdeadc0de'i32and b==0x11111111'i32and c==0x22222222'i32:if s.len==100:doAssertfalseprocfuzzTarget(data: (string,int32,int32,int32))=let (s, a, b, c)= datafuzzMe(s, a, b, c)defaultMutator(fuzzTarget)
WARNING: Modifying the input variable within fuzz targets is not allowed.If you are using ref types, you can prevent modifications by utilizing the
func
keywordand{.experimental: "strictFuncs".}
in your code.
It is also possible to create more complex fuzz targets, such as the one shown below:
import drchaostypeContentNodeKind=enum P,Br,TextContentNode=objectcase kind:ContentNodeKind of P: pChildren:seq[ContentNode] ofBr:discard ofText: textStr:stringproc`==`(a, b:ContentNode):bool=if a.kind!= b.kind:returnfalsecase a.kind of P:return a.pChildren== b.pChildren ofBr:returntrue ofText:return a.textStr== b.textStrprocfuzzTarget(x:ContentNode)=# Convert or translate `x` to any desired format (JSON, HMTL, binary, etc.),# and then feed it into the API being tested.defaultMutator(fuzzTarget)
Using drchaos, it is possible to generate millions of inputs and execute fuzzTarget withinjust a few seconds. More elaborate examples, such as fuzzing a graph library, can belocated in theexamples directory.
It is critical to define a==
proc for the input type. Overloadingproc default(_: typedesc[T]): T
can also be advantageous, especially whennil
is not avalid value forref
.
To compile the fuzz target, it is recommended to use at least the following flags:--cc:clang -d:useMalloc -t:"-fsanitize=fuzzer,address,undefined" -l:"-fsanitize=fuzzer,address,undefined" -d:nosignalhandler --nomain:on -g
.Additionally, it is recommended to use--mm:arc|orc
when possible.
Sample nim.cfg and .nimble files can be found in thetests/ directory andthis repository, respectively.
Alternatively, drchaos offers structured input for fuzzing usingnim-testutils. This includes a convenienttestrunner.
In some cases, it may be necessary to modify the randomized input to include specificvalues or create dependencies between certain fields. To support this functionality,drchaos offers a post-processing step that runs on compound types like object, tuple, ref,seq, string, array, and set. This step is only executed on these types for performance andclarity purposes, with distinct types being the exception.
procpostProcess(x:varContentNode; r:varRand)=if x.kind==Text: x.textStr="The man the professor the student has studies Rome."
ThedefaultMutator
is a convenient way to generate and mutate inputs for a givenfuzz target. However, if more fine-grained control is needed, thecustomMutator
can be used. WithcustomMutator
, the mutation procedure can be customized toperform specific actions, such as uncompressing aseq[byte]
before callingrunMutator
on the raw data, and then compressing the output again.
procmyTarget(x:seq[byte])=var data=uncompress(x)# ...procmyMutator(x:varseq[byte]; sizeIncreaseHint:Natural; r:varRand)=var data=uncompress(x)runMutator(data, sizeIncreaseHint, r) x=compress(data)customMutator(myTarget, myMutator)
Distinct types can be used to provide a mutate overload for fields with unique values orto restrict the search space. For example, it is possible to define a distinct type forfile signatures or other specific values that may be of interest.
# Inside the library being fuzzedwhendefined(runFuzzTests):typeClientId=distinctintproc`==`(a, b:ClientId):bool {.borrow.}else:typeClientId=int# Inside a test fileimport drchaos/mutatorconst idA=0.ClientId idB=2.ClientId idC=4.ClientIdprocmutate(value:varClientId; sizeIncreaseHint:int; enforceChanges:bool; r:varRand)=# Call `random.rand()` to return a new value.repeatMutate(r.sample([idA, idB, idC]))
Thedrchaos/mutator
module exports mutators for every supported type to aid in thecreation of mutate functions.
User overloads should follow the followingproc
signatures:
procfromData(data:openArray[byte]; pos:varint; output:var T)proctoData(data:varopenArray[byte]; pos:varint; input: T)procbyteSize(x: T):int {.inline.}# The amount of memory that the serialized type will occupy, measured in bytes.
The need for this arises only in the case of objects that include raw pointers. To addressthis,drchaos/common
offers read/write procedures to simplify the process.
It is necessary to define themutate
,default
and==
procedures. For containertypes, it is also necessary to definemitems
ormpairs
iterators.
Avoid using
echo
in a fuzz target as it can significantly slow down the execution speed.Prefer using
-d:danger
for maximum performance, but ensure that your code is free fromundefined behavior and does not rely on any assumptions that may break in unexpected ways.Once you have identified a crash, you can recompile the program with
-d:debug
and pass thecrashing test case as a parameter to further investigate the cause of the crash.Use
debugEcho(x)
in a target to print the input that caused the crash, which can behelpful in debugging and reproducing the issue.Although disabling sanitizers may improve performance, it is not recommended asAddressSanitizer can help catch memory errors and undefined behavior that may lead tocrashes or other bugs.
- Polymorphic types do not have serialization support.
- References with cycles are not supported. However, a .noFuzz custom pragma will be added soon for cursors.
- Object variants only work with the latest memory management model, which is
--mm:arc|orc
.
drchaos offers a number of advantages over frameworks based onFuzzDataProvider,which often have difficulty handling nested dynamic types. For a more detailedexplanation of these issues, you can read an article by the author of Fuzzcheck, availableat the following link:https://github.com/loiclec/fuzzcheck-rs/blob/main/articles/why_not_bytes.md
The drchaos framework has helped discover various bugs in software projects. Here are someexamples of bugs that were found in the Nim reference implementation with the help ofdrchaos:
- Use-after-free bugs in object variants (nim-lang/Nim#20305)
- OpenArray on an empty sequence triggers undefined behavior (nim-lang/Nim#20294)
Licensed and distributed under either of
- MIT license:LICENSE-MIT orhttp://opensource.org/licenses/MIT
or
- Apache License, Version 2.0, (LICENSE-APACHEv2 orhttp://www.apache.org/licenses/LICENSE-2.0)
at your option. These files may not be copied, modified, or distributed except according to those terms.
About
A powerful and easy-to-use fuzzing framework in Nim for C/C++/Obj-C targets