From 57189942ee8442069a6ab8609cc3a8a5d2d67fb1 Mon Sep 17 00:00:00 2001 From: Emil Mikulic Date: Sat, 23 Mar 2024 11:51:57 +1100 Subject: [PATCH] Factor out get_wwwroot_parent and test it. --- .gitignore | 1 + darkhttpd.c | 60 +++++++++++++++++++------------- devel/Makefile | 10 ++++-- devel/fuzz_get_wwwroot_parent.c | 20 +++++++++++ devel/fuzz_get_wwwroot_parent.sh | 6 ++++ devel/run-tests | 10 +++++- devel/test_get_wwwroot_parent.c | 29 +++++++++++++++ 7 files changed, 108 insertions(+), 28 deletions(-) create mode 100644 devel/fuzz_get_wwwroot_parent.c create mode 100755 devel/fuzz_get_wwwroot_parent.sh create mode 100644 devel/test_get_wwwroot_parent.c diff --git a/.gitignore b/.gitignore index 328a541..5e496a9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ devel/test.out.stdout devel/test.out.stderr devel/test_make_safe_uri devel/test_password_equal +devel/test_get_wwwroot_parent devel/tmp.httpd.tests diff --git a/darkhttpd.c b/darkhttpd.c index dab4fab..e34ffc7 100644 --- a/darkhttpd.c +++ b/darkhttpd.c @@ -2916,6 +2916,38 @@ static void pidfile_create(void) { } /* [<-] end of pidfile helpers. */ +/* In the --single-file case, wwwroot is a path to a file. + * Return the parent dir of the wwwroot and mutate the wwwroot + * to just the filename. Caller must free the return value. */ +char* get_wwwroot_parent() { + off_t ofs; + size_t len = strlen(wwwroot) + 1; + if (len == 1) return xstrdup("."); + char *path = xstrdup(wwwroot); + for (ofs = strlen(wwwroot); + (ofs >= 0) && (wwwroot[ofs] != '/'); + ofs--) + ; + /* wwwroot file is not in current directory */ + if (ofs >= 0) { + path[ofs + 1] = '\0'; + memmove(wwwroot, &wwwroot[ofs], len - ofs); + } else { + path[0] = '.'; + path[1] = '\0'; + } + return path; +} + +static void xchroot(const char *path) { + if (chdir(path) == -1) + err(1, "chdir(%s)", path); + if (chroot(path) == -1) + err(1, "chroot(%s)", path); + printf("chrooted to `%s'\n", path); +} + +/* chroot() into wwwroot. */ static void change_root(void) { #ifdef HAVE_NON_ROOT_CHROOT /* We run this even as root, which should never be a bad thing. */ @@ -2927,33 +2959,11 @@ static void change_root(void) { tzset(); /* read /etc/localtime before we chroot */ if (want_single_file) { - off_t ofs; - size_t len = strlen(wwwroot) + 1; - char *path = xstrdup(wwwroot); - for (ofs = strlen(wwwroot); - (ofs >= 0) && (wwwroot[ofs] != '/'); - ofs--) - ; - /* wwwroot file is not in current directory */ - if (ofs >= 0) { - path[ofs + 1] = '\0'; - if (chdir(path) == -1) - err(1, "chdir(%s)", path); - memmove(wwwroot, &wwwroot[ofs], len - ofs); - } else { - path[0] = '.'; - path[1] = '\0'; - } - if (chroot(path) == -1) - err(1, "chroot(%s)", path); - printf("chrooted to `%s'\n", path); + char *path = get_wwwroot_parent(); + xchroot(path); free(path); } else { - if (chdir(wwwroot) == -1) - err(1, "chdir(%s)", wwwroot); - if (chroot(wwwroot) == -1) - err(1, "chroot(%s)", wwwroot); - printf("chrooted to `%s'\n", wwwroot); + xchroot(wwwroot); wwwroot[0] = '\0'; /* empty string */ } } diff --git a/devel/Makefile b/devel/Makefile index ae1e45c..d0e7c4f 100644 --- a/devel/Makefile +++ b/devel/Makefile @@ -9,6 +9,12 @@ clean: test.pyc \ test_make_safe_uri \ test_password_equal \ - a.out darkhttpd.gcda darkhttpd.gcno \ - fuzz_darkhttpd.o fuzz_llvm_make_safe_uri fuzz_socket + test_get_wwwroot_parent \ + a.out \ + darkhttpd.gcda \ + darkhttpd.gcno \ + fuzz_darkhttpd.o \ + fuzz_llvm_make_safe_uri \ + fuzz_socket \ + fuzz_get_wwwroot_parent rm -rf tmp.fuzz tmp.httpd.tests diff --git a/devel/fuzz_get_wwwroot_parent.c b/devel/fuzz_get_wwwroot_parent.c new file mode 100644 index 0000000..f20c6c5 --- /dev/null +++ b/devel/fuzz_get_wwwroot_parent.c @@ -0,0 +1,20 @@ +#define main darkhttpd_main +#include "../darkhttpd.c" +#undef main + +char* sdup(const uint8_t *data, size_t size) { + char *buf = malloc(size + 1); + memcpy(buf, data, size); + buf[size] = 0; + return buf; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + wwwroot = sdup(data, size); + char *path = get_wwwroot_parent(); + free(path); + free(wwwroot); + return 0; +} + +/* vim:set ts=2 sw=2 sts=2 expandtab tw=78: */ diff --git a/devel/fuzz_get_wwwroot_parent.sh b/devel/fuzz_get_wwwroot_parent.sh new file mode 100755 index 0000000..f2d331a --- /dev/null +++ b/devel/fuzz_get_wwwroot_parent.sh @@ -0,0 +1,6 @@ +#!/bin/bash -e +set -x +clang -g -O -fsanitize=fuzzer,address \ + fuzz_get_wwwroot_parent.c -o fuzz_get_wwwroot_parent +mkdir -p fuzz_get_wwwroot_parent_testcases +./fuzz_get_wwwroot_parent -only_ascii=1 $* fuzz_get_wwwroot_parent_testcases/ diff --git a/devel/run-tests b/devel/run-tests index 847780f..bd17561 100755 --- a/devel/run-tests +++ b/devel/run-tests @@ -196,6 +196,14 @@ if ./test_password_equal | egrep '^FAIL:'; then exit 1 fi +echo "===> test_get_wwwroot_parent" +$CC -g -O2 -fsanitize=address -fsanitize=undefined \ + test_get_wwwroot_parent.c -o test_get_wwwroot_parent | exit 1 +if ./test_get_wwwroot_parent | egrep '^FAIL:'; then + echo test_get_wwwroot_parent failed >&2 + exit 1 +fi + # Check that the code builds with various defines. echo "===> building without -DDEBUG" $CC -O2 -Wall ../darkhttpd.c || exit 1 @@ -239,7 +247,7 @@ $CC -g -O2 -fprofile-arcs -ftest-coverage -fsanitize=address \ exit 1 } echo "===> generating report" -gcov darkhttpd +gcov a-darkhttpd chmod 755 $DIR/forbidden chmod 755 $DIR/unreadable rm -rf $DIR diff --git a/devel/test_get_wwwroot_parent.c b/devel/test_get_wwwroot_parent.c new file mode 100644 index 0000000..54a997e --- /dev/null +++ b/devel/test_get_wwwroot_parent.c @@ -0,0 +1,29 @@ +#define main _main_disabled_ +#include "../darkhttpd.c" +#undef main + +static void test(const char* input_wwwroot, const char* expected_path, + const char* expected_wwwroot) { + wwwroot = xstrdup(input_wwwroot); + char* path = get_wwwroot_parent(); + int pass = (strcmp(path, expected_path) == 0) && + (strcmp(wwwroot, expected_wwwroot) == 0); + printf("%s: \"%s\" -> \"%s\" : \"%s\"", pass ? "PASS" : "FAIL", input_wwwroot, + path, wwwroot); + if (!pass) { + printf(" (expected \"%s\" : \"%s\")", expected_path, expected_wwwroot); + } + printf("\n"); + free(path); + free(wwwroot); +} + +int main(void) { + test("", ".", ""); + test(".", ".", "."); + test("dir/file", "dir/", "/file"); + test("./a/b/c", "./a/b/", "/c"); + return 0; +} + +/* vim:set tabstop=4 shiftwidth=4 expandtab tw=78: */