Add fuzzing architecture

This commit is contained in:
Tim Rühsen 2017-06-09 16:27:37 +02:00
parent fa2985a535
commit f41c6aaf63
16 changed files with 559 additions and 6 deletions

View File

@ -1,6 +1,6 @@
# got some hints from https://gitorious.org/openismus-playground/examplelib/source
SUBDIRS = po include src tools $(LIBPSL_DOCS) tests
SUBDIRS = po include src tools $(LIBPSL_DOCS) fuzz tests
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
@ -23,17 +23,20 @@ dist-hook:
clean-local:
rm -rf */*.gc?? */*/*.gc?? libpsl.info lcov
check-coverage:
LCOV_INFO=libpsl.info
check-coverage: clean
if test -z "$(XLIB)"; then \
CFLAGS=$$CFLAGS" --coverage -O0" LDFLAGS=$$LDFLAGS" --coverage" ./configure --disable-runtime --disable-builtin; \
else \
CFLAGS=$$CFLAGS" --coverage -O0" LDFLAGS=$$LDFLAGS" --coverage" ./configure --enable-runtime=$(XLIB) --enable-builtin=$(XLIB); \
fi
$(MAKE) clean && $(MAKE)
lcov --capture --initial --directory src --output-file libpsl.info
$(MAKE)
lcov --capture --initial --directory src --output-file $(LCOV_INFO)
$(MAKE) check
lcov --capture --directory src --output-file libpsl.info
genhtml --prefix . libpsl.info --legend --title "libpsl" --output-directory=lcov
lcov --capture --directory src --output-file $(LCOV_INFO)
genhtml --prefix . $(LCOV_INFO) --legend --title "libpsl" --output-directory=lcov
@echo
@echo "You can now view the coverage report with 'xdg-open lcov/index.html'"
check-coverage-libidn:
XLIB=libidn $(MAKE) check-coverage
@ -43,3 +46,14 @@ check-coverage-libidn2:
check-coverage-libicu:
XLIB=libicu $(MAKE) check-coverage
fuzz-coverage: clean
$(MAKE) -C src CFLAGS="$(CFLAGS) --coverage" LDFLAGS="$(LDFLAGS) --coverage"
$(MAKE) -C fuzz fuzz-coverage CFLAGS="$(CFLAGS) --coverage" LDFLAGS="$(LDFLAGS) --coverage"
lcov --capture --initial --directory src --directory fuzz --output-file $(LCOV_INFO)
lcov --capture --directory src --directory fuzz --output-file $(LCOV_INFO)
# lcov --remove $(LCOV_INFO) '*/test_linking.c' '*/css_tokenizer.lex' '*/<stdout>' -o $(LCOV_INFO)
genhtml --prefix . --ignore-errors source $(LCOV_INFO) --legend --title "libpsl-fuzz" --output-directory=lcov
@echo
@echo "You can now view the coverage report with 'xdg-open lcov/index.html'"

View File

@ -273,6 +273,7 @@ AC_CONFIG_FILES([Makefile
src/Makefile
tools/Makefile
po/Makefile.in
fuzz/Makefile
tests/Makefile
libpsl.pc:libpsl.pc.in])
AC_OUTPUT

42
fuzz/Makefile.am Normal file
View File

@ -0,0 +1,42 @@
AM_CFLAGS = $(WERROR_CFLAGS) $(WARN_CFLAGS) -Wno-unused-parameter
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(srcdir) -DSRCDIR=\"$(abs_srcdir)\" -DTEST_RUN
AM_LDFLAGS = -static
LDADD = ../src/libpsl.la
PSL_TESTS = \
libpsl_fuzzer$(EXEEXT) \
libpsl_load_dafsa_fuzzer$(EXEEXT)
check_PROGRAMS = $(PSL_TESTS)
libpsl_fuzzer_SOURCES = libpsl_fuzzer.c main.c fuzzer.h
libpsl_load_dafsa_fuzzer_SOURCES = libpsl_load_dafsa_fuzzer.c main.c fuzzer.h
dist-hook:
find . -name '*.options' -exec cp -v '{}' $(distdir) ';'
find . -name '*.dict' -exec cp -v '{}' $(distdir) ';'
find . -name '*.in' -exec cp -vr '{}' $(distdir) ';'
find . -name '*.repro' -exec cp -vr '{}' $(distdir) ';'
TESTS_ENVIRONMENT = TESTS_VALGRIND="@VALGRIND_ENVIRONMENT@"
TESTS = $(PSL_TESTS)
clean-local:
rm -rf *.gc?? *.log lcov coverage.info
fuzz-coverage: $(PSL_TESTS)
find . -name '*_fuzzer' -exec ./coverage.sh '{}' ';'
oss-fuzz:
if test "$$OUT" != ""; then \
for ccfile in *_fuzzer.c; do \
fuzzer=$$(basename $$ccfile .c); \
$$CXX $$CXXFLAGS -I$(top_srcdir)/include -I$(top_srcdir) \
"$${fuzzer}.c" -o "$${fuzzer}" \
../src/.libs/libpsl.a $${LIB_FUZZING_ENGINE} -Wl,-Bstatic \
-lidn2 -lunistring \
-Wl,-Bdynamic; \
done; \
fi
.PHONY: oss-fuzz

63
fuzz/README.md Normal file
View File

@ -0,0 +1,63 @@
# Fuzzers
These are fuzzers designed for use with `libFuzzer` or `afl`. They can
be used to run on Google's OSS-Fuzz (https://github.com/google/oss-fuzz/).
The convention used here is that the initial values for each parser fuzzer
are taken from the $NAME.in directory.
Crash reproducers from OSS-Fuzz are put into $NAME.repro directory for
regression testing with top dir 'make check' or 'make check-valgrind'.
# Running a fuzzer using clang
Use the following commands on top dir:
```
export CC=clang-5.0
export CXX=clang++-5.0
export CFLAGS="-O1 -fno-omit-frame-pointer -gline-tables-only -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=address -fsanitize-address-use-after-scope -fsanitize-coverage=trace-pc-guard,trace-cmp"
export CXXFLAGS="-O1 -fno-omit-frame-pointer -gline-tables-only -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=address -fsanitize-address-use-after-scope -fsanitize-coverage=trace-pc-guard,trace-cmp -stdlib=libc++"
./configure --enable-static --disable-gtk-doc
make clean
make -j$(nproc)
cd fuzz
# build and run libpsl_fuzzer
./run-clang.sh libpsl_fuzzer
```
# Running a fuzzer using AFL
Use the following commands on top dir:
```
$ CC=afl-clang-fast ./configure --disable-gtk-doc
$ make -j$(nproc) clean all
$ cd fuzz
$ ./run-afl.sh libpsl_fuzzer
```
# Fuzz code coverage using the corpus directories *.in/
Code coverage reports currently work best with gcc+lcov+genhtml.
In the top directory:
```
CC=gcc CFLAGS="-O0 -g" ./configure --disable-gtk-doc
make fuzz-coverage
xdg-open lcov/index.html
```
Each fuzzer target has it's own functions to cover, e.g.
`libpsl_fuzzer` covers psl_is_public_suffix.
To work on corpora for better coverage, `cd fuzz` and use e.g.
`./view-coverage.sh libpsl_fuzzer`.
# Enhancing the testsuite for issues found
Each reproducer file should be dropped into the appropriate *.repro/
directory.

5
fuzz/coverage.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
for f in $1.in/*; do
$1 < $f >/dev/null
done

28
fuzz/fuzzer.h Normal file
View File

@ -0,0 +1,28 @@
/*
* Copyright(c) 2017 Tim Ruehsen
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* This file is part of libpsl.
*/
#include <stddef.h> // size_t
#include <stdint.h> // uint8_t
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);

56
fuzz/libpsl_fuzzer.c Normal file
View File

@ -0,0 +1,56 @@
/*
* Copyright(c) 2017 Tim Ruehsen
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* This file is part of libpsl.
*/
#include <config.h>
#include <assert.h> // assert
#include <stdint.h> // uint8_t
#include <stdlib.h> // malloc, free
#include <string.h> // memcpy
#include "libpsl.h"
#include "fuzzer.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char *domain = (char *) malloc(size + 1);
assert(domain != NULL);
// 0 terminate
memcpy(domain, data, size);
domain[size] = 0;
psl_ctx_t *psl;
psl = (psl_ctx_t *) psl_builtin();
psl_is_public_suffix(NULL, domain);
psl_is_public_suffix(psl, domain);
psl_is_public_suffix2(psl, domain, PSL_TYPE_PRIVATE);
psl_is_public_suffix2(psl, domain, PSL_TYPE_ICANN);
psl_free(psl);
free(domain);
return 0;
}

View File

@ -0,0 +1 @@
x.com

View File

@ -0,0 +1 @@
.äöü.de

View File

@ -0,0 +1 @@
x

View File

@ -0,0 +1,59 @@
/*
* Copyright(c) 2017 Tim Ruehsen
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* This file is part of libpsl.
*/
#include <config.h>
#include <assert.h> // assert
#include <stdint.h> // uint8_t
#include <stdlib.h> // malloc, free
#include <string.h> // memcpy
#include "libpsl.h"
#include "fuzzer.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char *in = (char *) malloc(size + 16);
assert(in != NULL);
// create a valid DAFSA input file
memcpy(in, ".DAFSA@PSL_0 \n", 16);
memcpy(in + 16, data, size);
FILE *fp = fmemopen(in, size + 16, "r");
assert(fp != NULL);
psl_ctx_t *psl;
psl = psl_load_fp(fp);
psl_is_public_suffix(NULL, NULL);
psl_is_public_suffix(psl, ".ü.com");
psl_free(psl);
fclose(fp);
free(in);
return 0;
}

View File

@ -0,0 +1 @@
aguchéÀDò„uzzo.iôÀ´o.ðºkhazia.sõÀéƒrá™ko.chiâ‰eno.osaká<6B>ashiri.hokkaido.jðÀy.cáÀtáƒrborte.î…a.prïÀ`® ÀNóƒsìˆåÀRprod.fastly.neô²¹¼¸º·¸¶¶µ´´°õŸ³«utilitieó²ž000.hõ±”kapp.comˆ°ˆemm.comŠ.bg„

144
fuzz/main.c Normal file
View File

@ -0,0 +1,144 @@
/*
* Copyright(c) 2017 Tim Ruehsen
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* This file is part of libpsl.
*/
#include "../config.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include "fuzzer.h"
#ifdef TEST_RUN
#include <dirent.h>
static void test_all_from(const char *dirname)
{
DIR *dirp;
struct dirent *dp;
if ((dirp = opendir(dirname))) {
while ((dp = readdir(dirp))) {
if (*dp->d_name == '.') continue;
char fname[strlen(dirname) + strlen(dp->d_name) + 2];
snprintf(fname, sizeof(fname), "%s/%s", dirname, dp->d_name);
int fd;
if ((fd = open(fname, O_RDONLY)) == -1) {
fprintf(stderr, "Failed to open %s (%d)\n", fname, errno);
continue;
}
struct stat st;
if (fstat(fd, &st) != 0) {
fprintf(stderr, "Failed to stat %d (%d)\n", fd, errno);
close(fd);
continue;
}
uint8_t *data = malloc(st.st_size);
ssize_t n;
if ((n = read(fd, data, st.st_size)) == st.st_size) {
printf("testing %zu bytes from '%s'\n", st.st_size, fname);
LLVMFuzzerTestOneInput(data, st.st_size);
} else
fprintf(stderr, "Failed to read %zu bytes from %s (%d), got %zd\n", st.st_size, fname, errno, n);
free(data);
close(fd);
}
closedir(dirp);
}
}
int main(int argc, char **argv)
{
/* if VALGRIND testing is enabled, we have to call ourselves with valgrind checking */
if (argc == 1) {
const char *valgrind = getenv("TESTS_VALGRIND");
if (valgrind && *valgrind) {
size_t cmdsize = strlen(valgrind) + strlen(argv[0]) + 32;
char *cmd = alloca(cmdsize);
snprintf(cmd, cmdsize, "TESTS_VALGRIND="" %s %s", valgrind, argv[0]);
return system(cmd) != 0;
}
}
const char *target = strrchr(argv[0], '/');
target = target ? target + 1 : argv[0];
char corporadir[sizeof(SRCDIR) + 1 + strlen(target) + 8];
snprintf(corporadir, sizeof(corporadir), SRCDIR "/%s.in", target);
test_all_from(corporadir);
snprintf(corporadir, sizeof(corporadir), SRCDIR "/%s.repro", target);
test_all_from(corporadir);
return 0;
}
#else
#ifndef __AFL_LOOP
static int __AFL_LOOP(int n)
{
static int first = 1;
if (first) {
first = 0;
return 1;
}
return 0;
}
#endif
int main(int argc, char **argv)
{
int ret;
unsigned char buf[64 * 1024];
while (__AFL_LOOP(10000)) { // only works with afl-clang-fast
ret = fread(buf, 1, sizeof(buf), stdin);
if (ret < 0)
return 0;
LLVMFuzzerTestOneInput(buf, ret);
}
return 0;
}
#endif /* TEST_RUN */

67
fuzz/run-afl.sh Executable file
View File

@ -0,0 +1,67 @@
#!/bin/sh -eu
#
# Copyright(c) 2017 Tim Ruehsen
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
# This file is part of libpsl.
srcdir="${srcdir:-.}"
export LD_LIBRARY_PATH=${srcdir}/../lib/.libs/
cat ${srcdir}/../config.log|grep afl-clang-fast >/dev/null 2>&1
if test $? != 0; then
echo "compile first library as:"
echo "CC=afl-clang-fast ./configure"
exit 1
fi
if test -z "$1"; then
echo "Usage: $0 test-case"
echo "Example: $0 libpsl_fuzzer"
exit 1
fi
rm -f $1
CFLAGS="-g -O2" CC=afl-clang-fast make "$1" || exit 1
### minimize test corpora
if test -d ${fuzzer}.in; then
mkdir -p ${fuzzer}.min
for i in `ls ${fuzzer}.in`; do
fin="${fuzzer}.in/$i"
fmin="${fuzzer}.min/$i"
if ! test -e $fmin || test $fin -nt $fmin; then
afl-tmin -i $fin -o $fmin -- ./${fuzzer}
fi
done
fi
TMPOUT=${fuzzer}.$$.out
mkdir -p ${TMPOUT}
if test -f ${fuzzer}.dict; then
afl-fuzz -i ${fuzzer}.min -o ${TMPOUT} -x ${fuzzer}.dict -- ./${fuzzer}
else
afl-fuzz -i ${fuzzer}.min -o ${TMPOUT} -- ./${fuzzer}
fi
echo "output was stored in $TMPOUT"
exit 0

49
fuzz/run-clang.sh Executable file
View File

@ -0,0 +1,49 @@
#!/bin/sh -eu
#
# Copyright(c) 2017 Tim Ruehsen
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
# This file is part of libpsl.
if test -z "$1"; then
echo "Usage: $0 <fuzzer target>"
echo "Example: $0 libpsl_fuzzer"
exit 1
fi
fuzzer=$1
workers=4
clang-5.0 \
$CFLAGS -I../include -I.. \
${fuzzer}.c -o ${fuzzer} \
-Wl,-Bstatic ../src/.libs/libpsl.a -lFuzzer \
-Wl,-Bdynamic -lidn2 -lunistring -lclang-5.0 -lstdc++
# create directory for NEW test corpora (covering new areas of code)
mkdir -p ${fuzzer}.new
if test -f ${fuzzer}.dict; then
./${fuzzer} -workers=$workers -dict=${fuzzer}.dict ${fuzzer}.new ${fuzzer}.in
else
./${fuzzer} -workers=$workers ${fuzzer}.new ${fuzzer}.in
fi
exit 0

21
fuzz/view-coverage.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash -eu
# 1. execute 'make fuzz-coverage' in the top directory
# 2. execute './view-coverage.sh <fuzz target>
# example: ./view-coverage.sh libpsl_fuzzer
if test -z "$1"; then
echo "Usage: $0 <fuzz target>"
echo "Example: $0 libpsl_fuzzer"
exit 1
fi
fuzzer="./"$1
LCOV_INFO=coverage.info
#make fuzz-coverage CFLAGS="$(CFLAGS) --coverage" LDFLAGS="$(LDFLAGS) --coverage"
./coverage.sh $fuzzer
lcov --capture --initial --directory ../src --directory . --output-file $LCOV_INFO
lcov --capture --directory ../src --output-file $LCOV_INFO
#lcov --remove $LCOV_INFO '*/test_linking.c' '*/css_tokenizer.lex' '*/<stdout>' '*/*.h' -o $LCOV_INFO
genhtml --prefix . --ignore-errors source $LCOV_INFO --legend --title "$1" --output-directory=lcov
xdg-open lcov/index.html