From 34b44e7496f4a6b919cc31a7ef31388176970c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfram=20R=C3=B6sler?= Date: Tue, 21 Apr 2020 23:36:31 +0200 Subject: [PATCH] Add fuzz test support Describe how to invoke the AFL fuzz tester on the KeePassXC CLI tool. As suggested in #2729. Fuzz test build of keepassxc-cli takes database password from environment variable instead of requiring it to be empty. Provide two empty kdbx files as initial fuzzer input, one kdbx 3 and one kdbx 4, both with minimal number of decryption rounds to speed up the test. --- docs/FuzzTest.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++ share/empty3.kdbx | Bin 0 -> 1190 bytes share/empty4.kdbx | Bin 0 -> 1317 bytes src/cli/Utils.cpp | 7 +++++ 4 files changed, 75 insertions(+) create mode 100644 docs/FuzzTest.md create mode 100644 share/empty3.kdbx create mode 100644 share/empty4.kdbx diff --git a/docs/FuzzTest.md b/docs/FuzzTest.md new file mode 100644 index 000000000..542999841 --- /dev/null +++ b/docs/FuzzTest.md @@ -0,0 +1,68 @@ +# Fuzz-Testing KeePassXC + +Fuzz-testing = feeding random input into a program until it crashes. Be smart about what's "random" by looking at how the program executes the input. + +We use the "American Fuzzy Lop" (AFL) fuzz tester (https://lcamtuf.coredump.cx/afl/). + +The following assumes that all tools and libraries required to build KeePassXC from source have already been installed. + +## Installing AFL + + $ sudo apt install afl + +Optionally, build AFL from source: + + $ git clone https://github.com/google/AFL + $ cd AFL + $ make + $ make install + +## Building KeePassXC For Fuzzing + +A special "instrumented build" is used that allows the fuzzer to look into the program as it executes. We place it in its own build directory so it doesn't confused with the production build. + + $ cd your_keepassxc_source_directory + $ mkdir buildafl + $ cd buildafl + $ CXX=afl-g++ AFL_HARDEN=1 cmake -DWITH_XC_ALL=ON .. + $ make + +In the source code, special behavior for fuzz testing can be implemented with `#ifdef __AFL_COMPILER`. For example, in fuzz builds, the KeePassXC CLI takes the database password from environment variable `KEYPASSXC_AFL_PASSWORD` to allow non-interactive operation. + +## Prepare Fuzzer Input + +To get the fuzzer started, we provide empty password database files (the password is `secret`). + + $ cd buildafl + $ mkdir -p findings/testcases + $ cp ../share/empty*.kdbx findings/testcases + +The fuzzer works by running KeePassXC with variations of this input, mutated in ways that make the program crash or hang. + +## Run The Fuzzer + + $ cd buildafl + $ KEYPASSXC_AFL_PASSWORD=secret afl-fuzz -i findings/testcases -o findings -m 2000 -t 1000 src/cli/keepassxc-cli ls @@ + +This fuzz-tests the `ls` command of the KeePassXC CLI, which loads and decrypts a database file and then lists its contents. The parameters mean: + +* `KEYPASSXC_AFL_PASSWORD=secret`: In fuzz test builds, the KeePassXC CLI takes the database password from this environment variable. +* `-i findings/testcases`: The directory which contains the initial fuzzer input. +* `-o findings`: The directory in which to store fuzzer results. +* `-m 2000`: Fuzzer memory (in megabytes). Adjust as required if the fuzzer fails to start up. +* `-t 1000`: Timeout until a hang is detected (in milliseconds). +* `src/cli/keepassxc-cli`: The instrumented executable. +* `ls`: The subcommand we're testing. +* `@@`: The fuzzer replaces this by the name of a file with the generated input. + +You may also need `export AFL_SKIP_CPUFREQ=1`. + +If KeePassXC crashes or hangs when processing the input, the fuzzer writes the database file (that was used in place of `@@`) to the `findings/crashes` or `findings/hangs` directory, respectively. + +To continue where the fuzzer left off, use `-i -`. To start over, remove and re-create the `findings` directory. + +## More Information + +AFL documentation: https://afl-1.readthedocs.io/en/latest/ + +Read this if you want to get serious about fuzz-testing. diff --git a/share/empty3.kdbx b/share/empty3.kdbx new file mode 100644 index 0000000000000000000000000000000000000000..1c4b1d67de23a066c4e4c367aa135a32519068e4 GIT binary patch literal 1190 zcmZR+xoB4UZ||*)49pBn0u0xfyR^IWeYP%DF`i|3d+RJ_76wKJ1_l-d2EWw{KHtsF zD6th2s@XKV{=}rCR@hRg77Cn3@0TB)HAl}-FHflJkmL0g zTlFs8xRbGHo#*pjHV&|13=9km>;eofU-YxgT1{PZZ>Vce7Oc_WP+-V2>SxJw3x9HQ zWq|M3zO}iD((==znP>P)ez=>MyCTwxQ-Og~anjj49?8veA}7)}x?4WT6YF0(dG8nY zUAJ`)rEdM*&BemN1hRmIftQPyE6PA!QSyjEi;t8w3hhzh#?Obm8w|uFL)#5(EE=opn9*c}>EK_=hJVzqWJpTiq~pTDkJkVa>Yo z@KeEyq6AOtkDa}_i>qL%neh#>}V?77A`P9O1%Z*f2{8nt;EgX{Q^l?S{ zYj(qbd@=@ij8C^Y{ENz{nHDn5(fZH(pR5m;EX~*F+7fh1JZt*X%M&}o+N!RcJlcBW zba_@twz^l<$Db}w3rrQ{cQDAWxV@%v+qT)-$9Q@yx2>J8XY%ei`~L0!>NiabzM8aj zYOBoUJ)2^yv=4I>c3e-~5~=W+>E_uDP8%LSH%^{AC*^bCwV!uGgWd0KI_WBRE8XwV zF5bsed20*<+wKJWwJ~oj*|gq!(dj3ee3=hdm_XwU` zzT3Ls{`wA)J?xIp^8=yxAI_S@oDt@jr)rWfsMpLyoW4$YQ#%UNbT?UyW?Zu#MX)AuZc1J&L3mTRNC zKd^uF`mX4CYvZzQC+hUdB+p*XT=eeLv^zl)4qhyGX5xwyJi#2eL&LwKd9u{OB9Swy z6GGPK9{Df$k z1=hbj*E4h5hAB=@ijHV4yS@L7dej_-t3Mu3vEy7`W?W})dEDfZq&chGOQneJX)lco zp6T%XW&dUI?ERF}pB1KEdNMau(v8=4=N$hP-X|>H?GoF0ET>?5s#W*X`PDJg)|E*r zEc(7+w`5wIq?S`tQ&sK}?gh=2SFI{SW4NC2EqECkwzAVw&MNO>cZbwVM*q!Tj_FyK Lp4*n^+3NuS6L&)8 literal 0 HcmV?d00001 diff --git a/share/empty4.kdbx b/share/empty4.kdbx new file mode 100644 index 0000000000000000000000000000000000000000..a9c0d0d0173c30bf16c85ba51c4f6b1b771f9c94 GIT binary patch literal 1317 zcmZR+xoB4UZ||)P3@i*x0t^fch6g`A+h6D$urGpDG3!s%e`Xd21_nk31_l-d1_p-b zG8Eb={`^qKEkGXhQL3Vg@fE0pMGcYiKMSMXb z3=AM03=AwFH32XOIe~b=AXjC`Jo`HFTh_YtYQ`tuXHRXNU*F*6YG)bmc2HxZAR}8d zSYH@OpD@^L5Q~?Km#ZW8taICYh057Bl?iLEny=Q&2|H}G?F>iv?x^2)?!SNfc7AG> zMf_oDt82-I%#XspupAeu^X_^rxR6!LTuLtN?ELtrrDO8Nqi$cxMim>C#~JodIfV{Q@?vRxVDZWYTRXz$0mQgZ%`Hwz|9OZ=TTF|7RM z>HmjgZfG+*E!yACvo_e!n)ivdear04Moz(ZCd}`c#((C;L7DZdEs7^GsXQ${QGVlz zqS~AVuWQ4uU*o;G>Vn5#;eFlTZpqE|Z=I?8=!@cvC0FKkPL&hP z(q(S0zg}Cp=hy)~ZRY3)W;ctuChI-<;%R#3P#%NRrWLy{zC5_70sWxrE#|v|Mn^C0#E1ruH$aCWD{Drt71Z?UZsP~Ca?F-mKC`- z{4$kaF`hA>^YYlIuV>3b?}nFmbIOXXP;lZu0MS-T~>Z_d-YsGe-eVt&{q`qtcTj7$<6a5cmyK-kX z-bnKQIobBwys|aY4;=h@)HDUcx&D3OH_I}~p18@Vw)t{odg3CZI9+M|T1L-ev3>j* zUDx0F)V%w{eq!Ryw~nVH*Qz`c3|%eL{(ggHy1?d?3I~@npAD>I+`lZS*!=3|G((ra z@;}@?cy4U*na4(y(I$?x!%0|I{9Z<{|&W9hC_n0JApjML~@)q{FzBab7?7|-_3Yv;Z4f;0M{ z<^M3de{RPVN|x20JgK$meL(;FdkyLvt=_K461(O8A^F~hW>coAQznQlEc0CD+o~p* zDRZ|(ZNKwsEp-Wvr0yx^@=}|21PPW2-Q$SgQuyuc|L<+jj~rs)l%DBv;s5ayiTbjf zx!L`KYbBeW*z}h7Y6`y;`6qF*-HvP934OLbNoNl{tvG+}dXxR>7v;fHySJYdcz)vJ uC;P*>jwKpB)u***)tgOn(>yDlh=I{Y(@9 literal 0 HcmV?d00001 diff --git a/src/cli/Utils.cpp b/src/cli/Utils.cpp index e25ffe02d..f395b0187 100644 --- a/src/cli/Utils.cpp +++ b/src/cli/Utils.cpp @@ -191,6 +191,12 @@ namespace Utils */ QString getPassword(bool quiet) { +#ifdef __AFL_COMPILER + // Fuzz test build takes password from environment variable to + // allow non-interactive operation + const auto env = getenv("KEYPASSXC_AFL_PASSWORD"); + return env ? env : ""; +#else auto& in = STDIN; auto& out = quiet ? DEVNULL : STDERR; @@ -200,6 +206,7 @@ namespace Utils out << endl; return line; +#endif // __AFL_COMPILER } /**