run: Don't block on blocking autofs mounts

When we start a sandbox and one of the directories we bind-mount into the sandbox, for example
one of the top dirs like /boot, is an autofs, then the bind mount will trigger an automount.
Often this is not really a problem, but in some cases (such as a missing device or a network
mount that is not available) this can block for potentially a very long time.

Ideally we would like to avoid this by not triggering the automount when bind-mounting, but
that doesn't seem possible with the current kernel APIs. So, instead we try to detect
any autofs paths and try to do a blocking operation on them, if this takes more than
200 milliseconds we assume the mountpoint is broken and ignore it.

There are various technical details here:

We can check for autofs using open(O_PATH) + fstatfs() and looking for
f_type == AUTOFS_SUPER_MAGIC (as described in the open manpage).

The blocking operation we do is open(O_DIRECTORY), but to allow this to timeout we
need to fork() and do this in the child, which we can kill after timing out, whick
we detect via select() using a self-pipe.

This fixes https://github.com/flatpak/flatpak/issues/1633

Closes: #1648
Approved by: alexlarsson
This commit is contained in:
Alexander Larsson
2018-05-04 17:32:41 +02:00
committed by Atomic Bot
parent c250857c59
commit 97dabb10a8
3 changed files with 103 additions and 1 deletions

View File

@@ -27,7 +27,10 @@
#include <sys/utsname.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/vfs.h>
#include <sys/personality.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <grp.h>
#include <unistd.h>
#include <gio/gunixfdlist.h>
@@ -400,6 +403,75 @@ do_export_path (FlatpakExports *exports,
g_hash_table_replace (exports->hash, ep->path, ep);
}
/* AUTOFS mounts are tricky, as using them as a source in a bind mount
* causes the mount to trigger, which can take a long time (or forever)
* waiting for a device or network mount. We try to open the directory
* but time out after a while, ignoring the mount. Unfortunately we
* have to mess with forks and stuff to be able to handle the timeout.
*/
static gboolean
check_if_autofs_works (const char *path)
{
int selfpipe[2];
struct timeval timeout;
pid_t pid;
fd_set rfds;
int res;
int wstatus;
if (pipe (selfpipe) == -1)
return FALSE;
fcntl (selfpipe[0], F_SETFL, fcntl (selfpipe[0], F_GETFL) | O_NONBLOCK);
fcntl (selfpipe[1], F_SETFL, fcntl (selfpipe[1], F_GETFL) | O_NONBLOCK);
pid = fork();
if (pid == -1)
{
close (selfpipe[0]);
close (selfpipe[1]);
return FALSE;
}
if (pid == 0)
{
/* Note: open, close and _exit are signal-async-safe, so it is ok to call in the child after fork */
close (selfpipe[0]); /* Close unused read end */
int dir_fd = open (path, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY);
_exit (dir_fd == -1 ? 1 : 0);
}
/* Parent */
close (selfpipe[1]); /* Close unused write end */
/* 200 msec timeout*/
timeout.tv_sec = 0;
timeout.tv_usec = 200 * 1000;
FD_ZERO (&rfds);
FD_SET (selfpipe[0], &rfds);
res = select (selfpipe[0]+1, &rfds, NULL, NULL, &timeout);
close (selfpipe[0]);
if (res == -1 /* Error */ || res == 0) /* Timeout */
{
/* Kill, but then waitpid to avoid zombie */
kill (pid, SIGKILL);
}
if (waitpid (pid, &wstatus, 0) != pid)
return FALSE;
if (res == -1 /* Error */ || res == 0) /* Timeout */
return FALSE;
if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0)
return FALSE;
return TRUE;
}
/* We use level to avoid infinite recursion */
static gboolean
@@ -410,8 +482,10 @@ _exports_path_expose (FlatpakExports *exports,
{
g_autofree char *canonical = NULL;
struct stat st;
struct statfs stfs;
char *slash;
int i;
glnx_autofd int o_path_fd = -1;
if (level > 40) /* 40 is the current kernel ELOOP check */
{
@@ -426,7 +500,11 @@ _exports_path_expose (FlatpakExports *exports,
}
/* Check if it exists at all */
if (lstat (path, &st) != 0)
o_path_fd = open (path, O_PATH | O_NOFOLLOW | O_CLOEXEC);
if (o_path_fd == -1)
return FALSE;
if (fstat (o_path_fd, &st) != 0)
return FALSE;
/* Don't expose weird things */
@@ -436,6 +514,19 @@ _exports_path_expose (FlatpakExports *exports,
S_ISSOCK (st.st_mode)))
return FALSE;
/* O_PATH + fstatfs is the magic that we need to statfs without automounting the target */
if (fstatfs (o_path_fd, &stfs) != 0)
return FALSE;
if (stfs.f_type == AUTOFS_SUPER_MAGIC)
{
if (!check_if_autofs_works (path))
{
g_debug ("ignoring blocking autofs path %s", path);
return FALSE;
}
}
path = canonical = flatpak_canonicalize_filename (path);
for (i = 0; dont_export_in[i] != NULL; i++)

View File

@@ -27,6 +27,7 @@
#include <sys/utsname.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/vfs.h>
#include <sys/personality.h>
#include <grp.h>
#include <unistd.h>
@@ -1793,6 +1794,9 @@ prepend_bwrap_argv_wrapper (GPtrArray *argv,
while (TRUE)
{
glnx_autofd int o_path_fd = -1;
struct statfs stfs;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dir_iter, &dent, NULL, error))
return FALSE;
@@ -1802,6 +1806,11 @@ prepend_bwrap_argv_wrapper (GPtrArray *argv,
if (strcmp (dent->d_name, ".flatpak-info") == 0)
continue;
/* O_PATH + fstatfs is the magic that we need to statfs without automounting the target */
o_path_fd = openat (dir_iter.fd, dent->d_name, O_PATH | O_NOFOLLOW | O_CLOEXEC);
if (o_path_fd == -1 || fstatfs (o_path_fd, &stfs) != 0 || stfs.f_type == AUTOFS_SUPER_MAGIC)
continue; /* AUTOFS mounts are risky and can cause us to block (see issue #1633), so ignore it. Its unlikely the proxy needs such a directory. */
if (dent->d_type == DT_DIR)
{
if (strcmp (dent->d_name, "tmp") == 0 ||

View File

@@ -34,6 +34,8 @@
#include <ostree.h>
#include <json-glib/json-glib.h>
#define AUTOFS_SUPER_MAGIC 0x0187
typedef enum {
FLATPAK_HOST_COMMAND_FLAGS_CLEAR_ENV = 1 << 0,
} FlatpakHostCommandFlags;