executor: improve opendir(3) error handling

While investigating an OpenBSD reproducer[1][2] I discovered the
following:

* All threads are stuck on the last `sleep(1000000)` syscall in main(),
  hence no output for the test machine.

* Each executor process created in loop() performs one iteration but
  exits abnormally during the call to remove_dir().

* Calling remove_dir() will eventually invoke itself recursively since
  one of the executed syscall is `mkdir("./file0", 0)` meaning that it
  will try to remove the directory created by execute_one(). However,
  `opendir(3)` fails with `EACCES` due to the permissions passed to
  `mkdir(2)` is zero.

Instead of exiting, trying to remove the problematic directory in a best
effort manner makes the reproducer continue executing the generated
syscalls. This work around might be considered to narrow. Another option
would be to replace the `sleep(1000000)` with `waitpid(-1, NULL, 0)`
until ECHILD is hit.

[1] https://syzkaller.appspot.com/bug?id=6f7ce2a0536580a94f65f44e478732ec505e88af
[2] https://syzkaller.appspot.com/text?tag=ReproC&x=10fd1a71900000
This commit is contained in:
Anton Lindqvist 2020-09-03 22:18:31 +02:00
parent 7b0683780a
commit ce441f065b
2 changed files with 21 additions and 2 deletions

View File

@ -199,6 +199,7 @@ static void use_temporary_dir(void)
#if GOOS_akaros || GOOS_netbsd || GOOS_freebsd || GOOS_openbsd || GOOS_test
#if (SYZ_EXECUTOR || SYZ_REPEAT) && SYZ_EXECUTOR_USES_FORK_SERVER && (SYZ_EXECUTOR || SYZ_USE_TMP_DIR)
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
@ -207,8 +208,19 @@ static void use_temporary_dir(void)
static void remove_dir(const char* dir)
{
DIR* dp = opendir(dir);
if (dp == NULL)
if (dp == NULL) {
if (errno == EACCES) {
// We could end up here in a recursive call to remove_dir() below.
// One of executed syscall could end up creating a directory rooted
// in the current working directory created by loop() with zero
// permissions. Try to perform a best effort removal of the
// directory.
if (rmdir(dir))
exitf("rmdir(%s) failed", dir);
return;
}
exitf("opendir(%s) failed", dir);
}
struct dirent* ep = 0;
while ((ep = readdir(dp))) {
if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0)

View File

@ -179,6 +179,7 @@ static void use_temporary_dir(void)
#if GOOS_akaros || GOOS_netbsd || GOOS_freebsd || GOOS_openbsd || GOOS_test
#if (SYZ_EXECUTOR || SYZ_REPEAT) && SYZ_EXECUTOR_USES_FORK_SERVER && (SYZ_EXECUTOR || SYZ_USE_TMP_DIR)
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
@ -187,8 +188,14 @@ static void use_temporary_dir(void)
static void remove_dir(const char* dir)
{
DIR* dp = opendir(dir);
if (dp == NULL)
if (dp == NULL) {
if (errno == EACCES) {
if (rmdir(dir))
exitf("rmdir(%s) failed", dir);
return;
}
exitf("opendir(%s) failed", dir);
}
struct dirent* ep = 0;
while ((ep = readdir(dp))) {
if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0)