{"id":17965195,"url":"https://github.com/tokenrove/niffy","last_synced_at":"2025-03-25T06:31:13.604Z","repository":{"id":147167939,"uuid":"43766590","full_name":"tokenrove/niffy","owner":"tokenrove","description":"NIF testing harness","archived":false,"fork":false,"pushed_at":"2019-03-27T10:57:22.000Z","size":147,"stargazers_count":32,"open_issues_count":8,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-19T09:40:56.945Z","etag":null,"topics":["afl-fuzz","c","erlang","nif","testing","valgrind"],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tokenrove.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-10-06T17:29:37.000Z","updated_at":"2024-01-03T13:04:49.000Z","dependencies_parsed_at":null,"dependency_job_id":"cc43ba2d-2c9a-4bce-846c-7f35b8526d89","html_url":"https://github.com/tokenrove/niffy","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tokenrove%2Fniffy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tokenrove%2Fniffy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tokenrove%2Fniffy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tokenrove%2Fniffy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tokenrove","download_url":"https://codeload.github.com/tokenrove/niffy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245413707,"owners_count":20611353,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["afl-fuzz","c","erlang","nif","testing","valgrind"],"created_at":"2024-10-29T12:10:26.925Z","updated_at":"2025-03-25T06:31:13.596Z","avatar_url":"https://github.com/tokenrove.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# niffy - NIF testing harness\n\n\u003e ... an ad hoc, informally-specified, bug-ridden, slow implementation\n\u003e of 1% of Erlang\n\nThis is a very simple harness to allow running NIFs under valgrind and\nother debugging tools without all the machinery of building a special\nversion of the runtime.\n\nIt allows you to load a NIF and invoke its functions with arbitrary\nErlang terms (or, at least, some subset of acceptable Erlang terms).\n\nVery little of the ERTS is implemented, so it's likely that your NIF\nwill break in other ways if it's dependent on much of Erlang.\n\n## Etymology\n\nFrom WordNet (r) 3.0 (2006) [wn]:\n\n\u003e niffy\n\u003e     adj 1: (British informal) malodorous\n\nnifty was already taken.\n\n## Caveats\n\n- allocates lots of memory and doesn't free it\n- isn't character encoding aware (no UTF-8 support)\n- much of the NIF API is still unimplemented\n\n## License\n\n[GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)\n\n## Building\n\nRequires [ragel](http://www.colm.net/open-source/ragel/), and the\nheader files you'd use to compile a NIF.  Run `make all` to build\neverything.\n\n## Usage\n\n### Simple\n\nYou have a compiled NIF shared object `nif.so` and want to call some\nof its functions under valgrind:\n\n```\n$ valgrind niffy nif.so \u003c\u003cEOF\nV = nif:foo(42).\nV.\nnif:bar(\"abc\").\nnif:baz(V).\nEOF\n```\n\n(If your NIF fails to load because of missing symbols, try again with\nthe `--lazy` option.  You may still be able to do some things if you\navoid the parts of the NIF API that aren't implemented.)\n\nniffy expects a series of period-separated function invocations of the\nform `Module:Function(Arguments).` where `Module` is probably a NIF\nyou loaded, `Function` is some function it defines, and the\n`Arguments` are constant Erlang terms of an appropriate arity.\n\nYou can assign the return value of a call to a variable (pattern\nmatching is not supported), and use that variable in subsequent calls.\nIf no variable is supplied, niffy will print the return value of each\ncall on stdout.  A variable alone will print its bound value.\n\nIt operates on a line at a time so you can interact with it to some\nextent, but keep in mind that function invocations are terminated by a\nperiod.\n\nNote that the NIF's `load` function will not be called unless you\nexplicitly call `niffy:load_nif(nif_name, [])`.  (You don't need to\ncall this if your NIF doesn't have a load callback.)\n\nErlang BIFs available in niffy:\n\n- `niffy:load_nif/2`\n- `niffy:halt/0`\n- `niffy:byte_size/1`\n- `niffy:element/2`\n\n### Multiple NIFs and other libraries\n\nYou can specify several SOs on the command-line, all of which will be\nloaded.  Not all of them have to be NIFs.\n\n### Fuzzing a NIF with afl_fuzz\n\nBuild your NIF with `afl-gcc`:\n\n```\n$ cd my_nif\n$ CC=afl-gcc CXX=afl-g++ rebar co\n```\n\nBuild niffy and fuzz_skeleton with `afl-gcc`:\n```\n$ cd niffy\n$ make clean\n$ CC=afl-gcc make\n```\n\nYou'll need a binary to run `afl-fuzz` on; you could supply `niffy`\nitself, but you'd primarily be fuzzing niffy's term parser instead of\nyour NIF.  To make it easier to focus the fuzzing, there is a program\nprovided called `fuzz_skeleton`.\n\nAs is, this program reads stdin as bytes of a binary which it binds to\nthe variable `Input`, and then reads from a term file specified on the\ncommand line.  If you're primarily interested in fuzzing your NIF with\nbinaries, this may be all you need, and you'd supply a term file like\nthe following:\n\n```\n_ = niffy:load_nif(jiffy, []).\nTerm = jiffy:nif_decode_init(Input, []).\njiffy:nif_encode_init(Term, []).\n```\n\nand then run `afl-fuzz` like this:\n\n```\n$ afl-fuzz -i input_samples -o output -- ./fuzz_skeleton ../jiffy/priv/jiffy.so jiffy_template.term\n```\n\nwhere `input_samples` is a directory containing some small initial\ntest cases, and `output` is a directory that will be created to hold\nthe results.\n\nHowever, you probably want more control over the representation fed to\nyour NIF, in which case you can modify `fuzz_skeleton.c` to accept\ninput as appropriate for your NIF.\n\n**Tip:** To make sure everything is working, inject an intentional,\nobvious bug into the NIF and verify that afl-fuzz catches it.  Usually\nif you do something like make an allocation size off-by-one or\nsimilar, afl-fuzz will find a crashing case almost immediately.  Then\nyou know your setup is good, you can remove the bug, and proceed.\n\nFor extremely rudimentary property testing, the `assert:eq/2` and\n`assert:ne/2` functions are provided, which `abort` on assertion\nfailure.\n\nFor example, I used the following fuzz term for testing LZ4's\nround-trip:\n\n``` erlang\n_ = niffy:load_nif(lz4, []).\nCompressed = lz4:compress(Input, []).\nDecompressed = lz4:decompress(Compressed, []).\nassert:eq(Input, Decompressed).\n```\n\n**Warning:** If your NIF does not load without the `--lazy` option to\nniffy, you must set `LD_BIND_LAZY=1` in your environment; otherwise,\nafl-fuzz sets `LD_BIND_NOW` and your fuzzer will mysteriously abort on\nall your test cases if the NIF uses any unimplemented functionality.\n\n### Tracing eunit and running your NIF calls through niffy\n\nYou can setup a tracing process that feeds all the calls to your NIF\nthrough niffy running under valgrind through a port, and then invoke\nyour eunit test suite.  For the simplest NIFs, something like this is\nsufficient:\n\n```erlang\nstart() -\u003e\n    Port = open_port({spawn_executable, \"/usr/bin/valgrind\"},\n                     [{args, [\"--error-exitcode=42\", \"--\",\n                              \"../niffy/niffy\", \"./priv/my_nif.so\", \"-q\"]},\n                      binary, stream, exit_status,\n                      {line, 1024}]),\n    Port ! {self(), {command, \u003c\u003c\"_ = niffy:load_nif(my_nif, []).\\n\"\u003e\u003e}},\n    erlang:trace_pattern({my_nif, '_', '_'}, true, []),\n    erlang:trace(all, true, [call]),\n    loop(Port).\n\nloop(Port) -\u003e\n    receive\n        {trace, _, call, {Module, Function, Arguments}} -\u003e\n            Port ! {self(), {command, format_mfa(Module, Function, Arguments)}},\n            loop(Port);\n        {_Port, {exit_status, Status}} -\u003e\n            io:format(\"niffy exited with code ~p~n\", [Status])\n    end.\n```\n\nIn more complex situations, you may need to handle certain calls\n(those that return a non-printable term, for example) and rewrite them\nto the form `Handle = my_nif:creation_call(Args).`, and suitably\nreplace later arguments that containing that non-printable term with\n`Handle`.  An example should soon be included here, but until then,\nfeel free to contact me about it.\n\n\n## Alternatives\n\nAs an alternative to niffy, you could build a valgrind-enabled OTP,\n[using this recipe](https://gist.github.com/gburd/4157112).\n\n## Contact\n\nniffy is maintained by Julian Squires \u003cjulian@cipht.net\u003e.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftokenrove%2Fniffy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftokenrove%2Fniffy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftokenrove%2Fniffy/lists"}