document portal: Implement the new api

tingping/wmclass
Matthias Clasen 2016-06-16 15:01:17 -04:00
parent 8bada3a819
commit 89a10ad9b2
1 changed files with 279 additions and 70 deletions

View File

@ -13,6 +13,7 @@
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include "xdp-dbus.h"
#include "xdp-impl-dbus.h"
#include "xdp-util.h"
#include "flatpak-db.h"
#include "flatpak-dbus.h"
@ -122,14 +123,16 @@ portal_grant_permissions (GDBusMethodInvocation *invocation,
entry = flatpak_db_lookup (db, id);
if (entry == NULL)
{
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_FOUND,
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_FOUND,
"No such document: %s", id);
return;
}
if (!flatpak_is_valid_name (target_app_id))
{
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid app name: %s", target_app_id);
return;
}
@ -140,7 +143,8 @@ portal_grant_permissions (GDBusMethodInvocation *invocation,
if (!xdp_entry_has_permissions (entry, app_id,
XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS | perms))
{
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
"Not enough permissions");
return;
}
@ -175,14 +179,16 @@ portal_revoke_permissions (GDBusMethodInvocation *invocation,
entry = flatpak_db_lookup (db, id);
if (entry == NULL)
{
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_FOUND,
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_FOUND,
"No such document: %s", id);
return;
}
if (!flatpak_is_valid_name (target_app_id))
{
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid app name: %s", target_app_id);
return;
}
@ -194,7 +200,8 @@ portal_revoke_permissions (GDBusMethodInvocation *invocation,
XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS) ||
strcmp (app_id, target_app_id) == 0)
{
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
"Not enough permissions");
return;
}
@ -228,14 +235,16 @@ portal_delete (GDBusMethodInvocation *invocation,
entry = flatpak_db_lookup (db, id);
if (entry == NULL)
{
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_FOUND,
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_FOUND,
"No such document: %s", id);
return;
}
if (!xdp_entry_has_permissions (entry, app_id, XDP_PERMISSION_FLAGS_DELETE))
{
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
"Not enough permissions");
return;
}
@ -317,6 +326,69 @@ do_create_doc (struct stat *parent_st_buf, const char *path, gboolean reuse_exis
return id;
}
static gboolean
validate_fd (int fd,
struct stat *st_buf,
struct stat *real_parent_st_buf,
char *path_buffer,
GError **error)
{
g_autofree char *proc_path = NULL;
ssize_t symlink_size;
g_autofree char *dirname = NULL;
g_autofree char *name = NULL;
int fd_flags;
glnx_fd_close int dir_fd = -1;
struct stat real_st_buf;
proc_path = g_strdup_printf ("/proc/self/fd/%d", fd);
if (fd == -1 ||
/* Must be able to get fd flags */
(fd_flags = fcntl (fd, F_GETFL)) == -1 ||
/* Must be O_PATH */
((fd_flags & O_PATH) != O_PATH) ||
/* Must not be O_NOFOLLOW (because we want the target file) */
((fd_flags & O_NOFOLLOW) == O_PATH) ||
/* Must be able to fstat */
fstat (fd, st_buf) < 0 ||
/* Must be a regular file */
(st_buf->st_mode & S_IFMT) != S_IFREG ||
/* Must be able to read path from /proc/self/fd */
/* This is an absolute and (at least at open time) symlink-expanded path */
(symlink_size = readlink (proc_path, path_buffer, PATH_MAX)) < 0)
{
g_set_error (error,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid fd passed");
return FALSE;
}
path_buffer[symlink_size] = 0;
/* We open the parent directory and do the stat in that, so that we have
* trustworthy parent dev/ino for later verification. Otherwise the caller
* could later replace a parent with a symlink and make us read some other file
*/
dirname = g_path_get_dirname (path_buffer);
name = g_path_get_basename (path_buffer);
dir_fd = open (dirname, O_CLOEXEC | O_PATH);
if (fstat (dir_fd, real_parent_st_buf) < 0 ||
fstatat (dir_fd, name, &real_st_buf, AT_SYMLINK_NOFOLLOW) < 0 ||
st_buf->st_dev != real_st_buf.st_dev ||
st_buf->st_ino != real_st_buf.st_ino)
{
/* Don't leak any info about real file path existence, etc */
g_set_error (error,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid fd passed");
return FALSE;
}
return TRUE;
}
static void
portal_add (GDBusMethodInvocation *invocation,
GVariant *parameters,
@ -325,16 +397,12 @@ portal_add (GDBusMethodInvocation *invocation,
GDBusMessage *message;
GUnixFDList *fd_list;
g_autofree char *id = NULL;
g_autofree char *proc_path = NULL;
int fd_id, fd, fds_len, fd_flags;
glnx_fd_close int dir_fd = -1;
const int *fds;
int fd_id, fd, fds_len;
char path_buffer[PATH_MAX + 1];
ssize_t symlink_size;
struct stat st_buf, real_st_buf, real_parent_st_buf;
g_autofree char *dirname = NULL;
g_autofree char *name = NULL;
const int *fds;
struct stat st_buf, real_parent_st_buf;
gboolean reuse_existing, persistent;
GError *error = NULL;
g_variant_get (parameters, "(hbb)", &fd_id, &reuse_existing, &persistent);
@ -349,52 +417,12 @@ portal_add (GDBusMethodInvocation *invocation,
fd = fds[fd_id];
}
proc_path = g_strdup_printf ("/proc/self/fd/%d", fd);
if (fd == -1 ||
/* Must be able to get fd flags */
(fd_flags = fcntl (fd, F_GETFL)) == -1 ||
/* Must be O_PATH */
((fd_flags & O_PATH) != O_PATH) ||
/* Must not be O_NOFOLLOW (because we want the target file) */
((fd_flags & O_NOFOLLOW) == O_PATH) ||
/* Must be able to fstat */
fstat (fd, &st_buf) < 0 ||
/* Must be a regular file */
(st_buf.st_mode & S_IFMT) != S_IFREG ||
/* Must be able to read path from /proc/self/fd */
/* This is an absolute and (at least at open time) symlink-expanded path */
(symlink_size = readlink (proc_path, path_buffer, sizeof (path_buffer) - 1)) < 0)
if (!validate_fd (fd, &st_buf, &real_parent_st_buf, path_buffer, &error))
{
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid fd passed");
g_dbus_method_invocation_take_error (invocation, error);
return;
}
path_buffer[symlink_size] = 0;
/* We open the parent directory and do the stat in that, so that we have
trustworthy parent dev/ino for later verification. Otherwise the caller
could later replace a parent with a symlink and make us read some other file */
dirname = g_path_get_dirname (path_buffer);
name = g_path_get_basename (path_buffer);
dir_fd = open (dirname, O_CLOEXEC | O_PATH);
if (fstat (dir_fd, &real_parent_st_buf) < 0 ||
fstatat (dir_fd, name, &real_st_buf, AT_SYMLINK_NOFOLLOW) < 0 ||
st_buf.st_dev != real_st_buf.st_dev ||
st_buf.st_ino != real_st_buf.st_ino)
{
/* Don't leak any info about real file path existence, etc */
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid fd passed");
return;
}
g_debug ("portal_add %s", path_buffer);
if (st_buf.st_dev == fuse_dev)
{
/* The passed in fd is on the fuse filesystem itself */
@ -416,9 +444,10 @@ portal_add (GDBusMethodInvocation *invocation,
AUTOLOCK (db);
/* If the entry doesn't exist anymore, fail. Also fail if not
resuse_existing, because otherwise the user could use this to
get a copy with permissions and thus escape later permission
revocations */
* reuse_existing, because otherwise the user could use this to
* get a copy with permissions and thus escape later permission
* revocations
*/
old_entry = flatpak_db_lookup (db, id);
if (old_entry == NULL ||
!reuse_existing)
@ -492,7 +521,8 @@ portal_add_named (GDBusMethodInvocation *invocation,
/* This is only allowed from the host, or else we could leak existence of files */
if (*app_id != 0)
{
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
"Not enough permissions");
return;
}
@ -601,6 +631,182 @@ handle_get_mount_point (XdpDbusDocuments *object, GDBusMethodInvocation *invocat
return TRUE;
}
static gboolean
portal_lookup (GDBusMethodInvocation *invocation,
GVariant *parameters,
const char *app_id)
{
const char *filename;
char path_buffer[PATH_MAX + 1];
int fd;
struct stat st_buf, real_parent_st_buf;
g_auto(GStrv) ids = NULL;
g_autofree char *id = NULL;
GError *error = NULL;
if (strcmp (app_id, "") != 0)
{
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
"Not allowed in sandbox");
return TRUE;
}
g_variant_get (parameters, "(^&ay)", &filename);
fd = open (filename, O_PATH | O_CLOEXEC);
if (fd == -1)
{
glnx_set_error_from_errno (&error);
g_dbus_method_invocation_take_error (invocation, error);
return TRUE;
}
if (!validate_fd (fd, &st_buf, &real_parent_st_buf, path_buffer, &error))
{
g_dbus_method_invocation_take_error (invocation, error);
return TRUE;
}
if (st_buf.st_dev == fuse_dev)
{
/* The passed in fd is on the fuse filesystem itself */
g_autoptr(FlatpakDbEntry) old_entry = NULL;
id = xdp_fuse_lookup_id_for_inode (st_buf.st_ino);
g_debug ("path on fuse, id %s", id);
}
else
{
g_autoptr(GVariant) data = NULL;
data = g_variant_ref_sink (g_variant_new ("(^ayttu)",
path_buffer,
(guint64)real_parent_st_buf.st_dev,
(guint64)real_parent_st_buf.st_ino,
0));
ids = flatpak_db_list_ids_by_value (db, data);
if (ids[0] != NULL)
id = g_strdup (ids[0]);
}
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(s)", id ? id : ""));
return TRUE;
}
static GVariant *
get_app_permissions (FlatpakDbEntry *entry)
{
g_autofree const char **apps = NULL;
GVariantBuilder builder;
int i;
apps = flatpak_db_entry_list_apps (entry);
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sas}"));
for (i = 0; apps[i] != NULL; i++)
{
g_autofree const char **permissions = flatpak_db_entry_list_permissions (entry, apps[i]);
g_variant_builder_add_value (&builder,
g_variant_new ("{s^as}", apps[i], permissions));
}
return g_variant_builder_end (&builder);
}
static GVariant *
get_path (FlatpakDbEntry *entry)
{
g_autoptr (GVariant) data = flatpak_db_entry_get_data (entry);
const char *path;
g_variant_get (data, "(^ayttu)", &path, NULL, NULL, NULL);
return g_variant_new_bytestring (path);
}
static gboolean
portal_info (GDBusMethodInvocation *invocation,
GVariant *parameters,
const char *app_id)
{
const char *id = NULL;
g_autoptr(FlatpakDbEntry) entry = NULL;
if (strcmp (app_id, "") != 0)
{
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
"Not allowed in sandbox");
return TRUE;
}
g_variant_get (parameters, "(&s)", &id);
AUTOLOCK (db);
entry = flatpak_db_lookup (db, id);
if (!entry)
{
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid ID passed");
return TRUE;
}
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(@ay@a{sas})",
get_path (entry),
get_app_permissions (entry)));
return TRUE;
}
static gboolean
portal_list (GDBusMethodInvocation *invocation,
GVariant *parameters,
const char *app_id)
{
g_auto(GStrv) ids = NULL;
GVariantBuilder builder;
int i;
if (strcmp (app_id, "") != 0)
{
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
"Not allowed in sandbox");
return TRUE;
}
g_variant_get (parameters, "(&s)", &app_id);
AUTOLOCK (db);
if (strcmp (app_id, "") == 0)
ids = flatpak_db_list_ids (db);
else
ids = flatpak_db_list_ids_by_app (db, app_id);
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{say}"));
for (i = 0; ids[i]; i++)
{
g_autoptr(FlatpakDbEntry) entry = NULL;
entry = flatpak_db_lookup (db, ids[i]);
g_variant_builder_add (&builder, "{s@ay}", ids[i], get_path (entry));
}
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(@a{say})",
g_variant_builder_end (&builder)));
return TRUE;
}
static void
on_bus_acquired (GDBusConnection *connection,
const gchar *name,
@ -617,6 +823,9 @@ on_bus_acquired (GDBusConnection *connection,
g_signal_connect_swapped (helper, "handle-grant-permissions", G_CALLBACK (handle_method), portal_grant_permissions);
g_signal_connect_swapped (helper, "handle-revoke-permissions", G_CALLBACK (handle_method), portal_revoke_permissions);
g_signal_connect_swapped (helper, "handle-delete", G_CALLBACK (handle_method), portal_delete);
g_signal_connect_swapped (helper, "handle-lookup", G_CALLBACK (handle_method), portal_lookup);
g_signal_connect_swapped (helper, "handle-info", G_CALLBACK (handle_method), portal_info);
g_signal_connect_swapped (helper, "handle-list", G_CALLBACK (handle_method), portal_list);
flatpak_connection_track_name_owners (connection);
@ -660,7 +869,7 @@ on_name_acquired (GDBusConnection *connection,
g_autoptr(GError) error = NULL;
struct stat stbuf;
g_debug ("org.freedesktop.portal.Documents acquired");
g_debug ("%s acquired", name);
if (!xdp_fuse_init (&error))
{
@ -688,11 +897,17 @@ on_name_lost (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
g_debug ("org.freedesktop.portal.Documents lost");
g_debug ("%s lost", name);
final_exit_status = 20;
g_main_loop_quit (loop);
}
static void
exit_handler (int sig)
{
g_main_loop_quit (loop);
}
static void
session_bus_closed (GDBusConnection *connection,
gboolean remote_peer_vanished,
@ -701,12 +916,6 @@ session_bus_closed (GDBusConnection *connection,
g_main_loop_quit (loop);
}
static void
exit_handler (int sig)
{
g_main_loop_quit (loop);
}
static int
set_one_signal_handler (int sig,
void (*handler)(int),