Ensure we don't install world-writable dirs or setuid files

This is solved in a much nicer way on master, using new ostree
APIs. However, here we take a brute-force approach of scanning
all staged files and ensuring we don't have any files or
directories with invalid modes before committing the transaction.

If any bad permissions were found we delete the entire staging
directory.
This commit is contained in:
Alexander Larsson
2017-06-19 19:01:08 +02:00
parent 02a299f5c0
commit 2c8e2417de
2 changed files with 125 additions and 1 deletions

View File

@@ -2110,6 +2110,96 @@ flatpak_dir_pull_oci (FlatpakDir *self,
return TRUE;
}
static gboolean
ensure_safe_objdir (int dir_fd, const char *rel_path, GError **error)
{
g_auto(GLnxDirFdIterator) iter = {0};
if (!glnx_dirfd_iterator_init_at (dir_fd, rel_path, TRUE, &iter, error))
return FALSE;
while (TRUE)
{
struct dirent *dent;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iter, &dent, NULL, error))
return FALSE;
if (dent == NULL)
break;
if (dent->d_type == DT_DIR)
{
if (!ensure_safe_objdir (iter.fd, dent->d_name, error))
return FALSE;
}
else
{
struct stat stbuf;
if (fstatat (iter.fd, dent->d_name, &stbuf, 0) == 0 &&
((stbuf.st_mode & ~S_IFMT) & ~0775) != 0)
return flatpak_fail (error, "Invalid file mode 0%04o", stbuf.st_mode);
if (g_str_has_suffix (dent->d_name, ".dirmeta"))
{
glnx_fd_close int dirmeta_fd = -1;
g_autoptr(GBytes) data = NULL;
g_autoptr(GVariant) variant = NULL;
guint32 mode;
dirmeta_fd = openat (iter.fd, dent->d_name, O_RDONLY | O_CLOEXEC);
if (dirmeta_fd < 0)
flatpak_fail(error, "Can't read dirmeta");
data = glnx_fd_readall_bytes (dirmeta_fd, NULL, error);
if (!data)
return FALSE;
variant = g_variant_new_from_bytes (OSTREE_DIRMETA_GVARIANT_FORMAT, data, TRUE);
g_variant_ref_sink (variant);
g_variant_get_child (variant, 2, "u", &mode);
mode = GUINT32_FROM_BE (mode);
if (((mode & ~S_IFMT) & ~0775) != 0)
return flatpak_fail (error, "Invalid directory mode 0%04o", mode);
}
}
}
return TRUE;
}
static gboolean
ensure_safe_staging_permissions (OstreeRepo *repo, GError **error)
{
g_auto(GLnxDirFdIterator) tmp_iter = {0};
/* We don't know which stage dir is in use, so check all */
if (!glnx_dirfd_iterator_init_at (ostree_repo_get_dfd (repo), "tmp", TRUE, &tmp_iter, error))
return FALSE;
while (TRUE)
{
struct dirent *dent;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&tmp_iter, &dent, NULL, error))
return FALSE;
if (dent == NULL)
break;
if (dent->d_type == DT_DIR && g_str_has_prefix (dent->d_name, "staging-") &&
!ensure_safe_objdir (tmp_iter.fd, dent->d_name, error))
{
glnx_shutil_rm_rf_at (tmp_iter.fd, dent->d_name, NULL, NULL);
return FALSE;
}
}
return TRUE;
}
gboolean
flatpak_dir_pull (FlatpakDir *self,
const char *repository,
@@ -2238,6 +2328,9 @@ flatpak_dir_pull (FlatpakDir *self,
goto out;
if (!ensure_safe_staging_permissions (repo, error))
goto out;
if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
goto out;

View File

@@ -24,7 +24,7 @@ set -euo pipefail
skip_without_bwrap
skip_without_user_xattrs
echo "1..10"
echo "1..12"
setup_repo
install_repo
@@ -338,3 +338,34 @@ ${FLATPAK} build-export ${FL_GPGARGS} repos/test ${DIR}
${FLATPAK} ${U} update org.test.OldVersion
echo "ok version checks"
rm -rf app
flatpak build-init app org.test.Writable org.test.Platform org.test.Platform
mkdir -p app/files/a-dir
chmod a+rwx app/files/a-dir
flatpak build-finish --command=hello.sh app
ostree --repo=repos/test commit ${FL_GPGARGS} --branch=app/org.test.Writable/$ARCH/master app
update_repo
if ${FLATPAK} ${U} install test-repo org.test.Writable &> err.txt; then
assert_not_reached "Should not be able to install with world-writable directory"
fi
assert_file_has_content err.txt [Ii]nvalid
echo "ok no world writable dir"
rm -rf app
flatpak build-init app org.test.Setuid org.test.Platform org.test.Platform
mkdir -p app/files/
touch app/files/exe
chmod u+s app/files/exe
flatpak build-finish --command=hello.sh app
ostree --repo=repos/test commit ${FL_GPGARGS} --branch=app/org.test.Setuid/$ARCH/master app
update_repo
if ${FLATPAK} ${U} install test-repo org.test.Setuid &> err2.txt; then
assert_not_reached "Should not be able to install with setuid file"
fi
assert_file_has_content err2.txt [Ii]nvalid
echo "ok no setuid"