extra-data: Don't allow creating files with non-canonical permissions in apply_extra

When installing a flatpak with extra-data we execute the apply_extra
script from the flatpak to extract the extra data files we
created. This script runs with very little filesystem acces, but it
does have write permissions to the location that will eventually be
/app/extra in the finished flatpak. This is especially problematic for
the systemwide install case, because the script is then run as root,
so it could potentially create a setuid file there.

Such a file would not be usable inside the sandbox (because setuid is
disabled in the sandbox), but it could potentially be a problem if the
user could be tricked into running the file directly on the host. This
is the same behaviour as e.g. rpm or deb which both can install setuid
files, but we want to guarantee that flatpak is better than that.

The fix is to run the script with all capabilities dropped (bwrap
--cap-drop ALL) which removes a bunch of possible attack vectors (for
instance setting file capabilities). However, even without
capabilities, it is possible for a user to make any file setuid to the
same user, so we also need to canonicalize the permissions of all
files generated by running the script.

Additionally, while running the script we set the toplevel directory
only be accessible to the user, meaning we will not temporarily leak
any potential setuid files to other users.

Note, this commit actually goes furthen than that and completely
canonicalizes all the file permissions to be the same as those
otherwise used by flatpak. For example we fix up cases where the
script creates files writable or unreadable by non-root users.

Closes: #2323
Approved by: alexlarsson
This commit is contained in:
Alexander Larsson
2018-11-15 16:12:24 +01:00
committed by Atomic Bot
parent 1ce0246b0d
commit 35598f46a5
3 changed files with 122 additions and 0 deletions

View File

@@ -6569,6 +6569,8 @@ apply_extra_data (FlatpakDir *self,
"--ro-bind", flatpak_file_get_path_cached (app_files), "/app",
"--bind", flatpak_file_get_path_cached (extra_files), "/app/extra",
"--chdir", "/app/extra",
/* We run as root in the system-helper case, so drop all caps */
"--cap-drop", "ALL",
NULL);
if (!flatpak_run_setup_base_argv (bwrap, runtime_files, NULL, runtime_ref_parts[2],
@@ -6592,6 +6594,17 @@ apply_extra_data (FlatpakDir *self,
g_debug ("Running /app/bin/apply_extra ");
/* We run the sandbox without caps, but it can still create files owned by itself with
* arbitrary permissions, including setuid myself. This is extra risky in the case where
* this runs as root in the system helper case. We canonicalize the permissions at the
* end, but do avoid non-canonical permissions leaking out before then we make the
* toplevel dir only accessible to the user */
if (chmod (flatpak_file_get_path_cached (extra_files), 0700) != 0)
{
glnx_set_error_from_errno (error);
return FALSE;
}
if (!g_spawn_sync (NULL,
(char **) bwrap->argv->pdata,
bwrap->envp,
@@ -6602,6 +6615,9 @@ apply_extra_data (FlatpakDir *self,
error))
return FALSE;
if (!flatpak_canonicalize_permissions (AT_FDCWD, flatpak_file_get_path_cached (extra_files), error))
return FALSE;
if (exit_status != 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,

View File

@@ -475,6 +475,10 @@ gboolean flatpak_rm_rf (GFile *dir,
GCancellable *cancellable,
GError **error);
gboolean flatpak_canonicalize_permissions (int parent_dfd,
const char *rel_path,
GError **error);
char * flatpak_readlink (const char *path,
GError **error);
char * flatpak_resolve_link (const char *path,

View File

@@ -1986,6 +1986,108 @@ out:
return ret;
}
static gboolean
_flatpak_canonicalize_permissions (int parent_dfd,
const char *rel_path,
gboolean toplevel,
GError **error)
{
struct stat stbuf;
gboolean res = TRUE;
/* Note, in order to not leave non-canonical things around in case
* of error, this continues after errors, but returns the first
* error. */
if (TEMP_FAILURE_RETRY (fstatat (parent_dfd, rel_path, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
{
glnx_set_error_from_errno (error);
return FALSE;
}
if (S_ISDIR (stbuf.st_mode))
{
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
/* For the toplevel we set to 0700 so we can modify it, but not
expose any non-canonical files to any other user, then we set
it to 0755 afterwards. */
if (fchmodat (parent_dfd, rel_path, toplevel ? 0700 : 0755, 0) != 0)
{
glnx_set_error_from_errno (error);
error = NULL;
res = FALSE;
}
if (glnx_dirfd_iterator_init_at (parent_dfd, rel_path, FALSE, &dfd_iter, NULL))
{
while (TRUE)
{
struct dirent *dent;
if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, NULL, NULL) || dent == NULL)
break;
if (!_flatpak_canonicalize_permissions (dfd_iter.fd, dent->d_name, FALSE, error))
{
error = NULL;
res = FALSE;
}
}
}
if (toplevel &&
fchmodat (parent_dfd, rel_path, 0755, 0) != 0)
{
glnx_set_error_from_errno (error);
error = NULL;
res = FALSE;
}
return res;
}
else if (S_ISREG(stbuf.st_mode))
{
mode_t mode;
/* If use can execute, make executable by all */
if (stbuf.st_mode & S_IXUSR)
mode = 0755;
else /* otherwise executable by none */
mode = 0644;
if (fchmodat (parent_dfd, rel_path, mode, 0) != 0)
{
glnx_set_error_from_errno (error);
res = FALSE;
}
}
else if (S_ISLNK(stbuf.st_mode))
{
/* symlinks have no permissions */
}
else
{
/* some weird non-canonical type, lets delete it */
if (unlinkat(parent_dfd, rel_path, 0) != 0)
{
glnx_set_error_from_errno (error);
res = FALSE;
}
}
return res;
}
/* Canonicalizes files to the same permissions as bare-user-only checkouts */
gboolean
flatpak_canonicalize_permissions (int parent_dfd,
const char *rel_path,
GError **error)
{
return _flatpak_canonicalize_permissions (parent_dfd, rel_path, TRUE, error);
}
/* Make a directory, and its parent. Don't error if it already exists.
* If you want a failure mode with EEXIST, use g_file_make_directory_with_parents. */
gboolean