Store and verify parent dir dev/ino and pass O_PATH fds

In order to be robust against symlink attacks (i.e. make a document
for a path, then replace it with a symlink somewhere else and have the
portal read that instead) we store the parent dev/ino when we create
the document id and always verify that (atomically with the *at
syscalls) on each use.

Also, we pass O_PATH fds when creating documents, as it allows us
to be a bit safer. For instance we can verify that the fd is a O_PATH
fd before doing any ops on it, and it makes it possible to avoid other
symlink trickery.

Also, we drop the double add methods, and just use the O_PATH version.
This commit is contained in:
Alexander Larsson committed 2015-09-03 22:17:00 +02:00
1 parent c0e480df94
commit 04879fdea5
5 files changed
+273 -161

No files matched your search

+1 -5
View File
@@ -29,11 +29,7 @@
<arg type='ay' name='path' direction='out'/>
</method>
<method name="Add">
<arg type='ay' name='path' direction='in'/>
<arg type='s' name='doc_id' direction='out'/>
</method>
<method name="AddLocal">
<arg type='h' name='fd' direction='in'/>
<arg type='h' name='o_path_fd' direction='in'/>
<arg type='s' name='doc_id' direction='out'/>
</method>
<method name="GrantPermissions">
+140 -84
View File
@@ -108,7 +108,7 @@ get_entry_cache_time (int st_mode)
* the list (add/remove) or name of a tmpfile.
*
* Each instance has a mutex that locks access to the backing path,
* as it can be removed at runtime. Use get/steal_backing_path() to
* as it can be removed at runtime. Use get/steal_backing_basename() to
* safely access it.
*
******************************* XdpTmp *******************************/
@@ -122,6 +122,7 @@ typedef struct
/* These are immutable, no lock needed */
guint64 parent_inode;
guint32 tmp_id;
XdgAppDbEntry *entry;
/* Changes always done under tmp_files lock */
char *name;
@@ -129,7 +130,7 @@ typedef struct
GMutex mutex;
/* protected by mutex */
char *backing_path;
char *backing_basename;
} XdpTmp;
/* Owns a ref to the files */
@@ -148,30 +149,31 @@ xdp_tmp_unref (XdpTmp *tmp)
{
if (g_atomic_int_dec_and_test (&tmp->ref_count))
{
xdg_app_db_entry_unref (tmp->entry);
g_free (tmp->name);
g_free (tmp->backing_path);
g_free (tmp->backing_basename);
g_free (tmp);
}
}
char *
xdp_tmp_get_backing_path (XdpTmp *tmp)
xdp_tmp_get_backing_basename (XdpTmp *tmp)
{
char *res;
g_mutex_lock (&tmp->mutex);
res = g_strdup (tmp->backing_path);
res = g_strdup (tmp->backing_basename);
g_mutex_unlock (&tmp->mutex);
return res;
}
char *
xdp_tmp_steal_backing_path (XdpTmp *tmp)
xdp_tmp_steal_backing_basename (XdpTmp *tmp)
{
char *res;
g_mutex_lock (&tmp->mutex);
res = tmp->backing_path;
tmp->backing_path = NULL;
res = tmp->backing_basename;
tmp->backing_basename = NULL;
g_mutex_unlock (&tmp->mutex);
return res;
@@ -227,17 +229,25 @@ find_tmp_by_id (guint32 tmp_id)
/* Caller must hold tmp_files lock */
static XdpTmp *
xdp_tmp_new_nolock (fuse_ino_t parent,
XdgAppDbEntry *entry,
const char *name,
const char *path)
const char *tmp_basename)
{
XdpTmp *tmp;
g_autofree char *tmp_dirname = NULL;
/* We store the pathname instead of dir_fd + basename, because
its very easy to get a lot of tempfiles leaking and that would
mean quite a lot of open fds */
tmp_dirname = xdp_entry_dup_dirname (entry);
tmp = g_new0 (XdpTmp, 1);
tmp->ref_count = 2; /* One owned by tmp_files */
tmp->tmp_id = g_atomic_int_add (&next_tmp_id, 1);
tmp->parent_inode = parent;
tmp->name = g_strdup (name);
tmp->backing_path = g_strdup (path);
tmp->tmp_id = g_atomic_int_add (&next_tmp_id, 1);
tmp->ref_count = 1; /* Owned by tmp_files */
tmp->entry = xdg_app_db_entry_ref (entry);
tmp->backing_basename = g_strdup (tmp_basename);
tmp_files = g_list_prepend (tmp_files, tmp);
@@ -249,11 +259,15 @@ static void
xdp_tmp_unlink_nolock (XdpTmp *tmp)
{
g_autofree char *backing_path = NULL;
g_autofree char *backing_basename = NULL;
backing_path = xdp_tmp_steal_backing_path (tmp);
if (backing_path)
unlink (backing_path);
backing_basename = xdp_tmp_steal_backing_basename (tmp);
if (backing_basename)
{
glnx_fd_close int dir_fd = xdp_entry_open_dir (tmp->entry);
if (dir_fd)
unlinkat (dir_fd, backing_basename, 0);
}
tmp_files = g_list_remove (tmp_files, tmp);
xdp_tmp_unref (tmp);
@@ -291,8 +305,9 @@ typedef struct
/* These are immutable, no lock needed */
guint32 tmp_id;
fuse_ino_t inode;
char *trunc_path;
char *real_path;
int dir_fd;
char *trunc_basename;
char *real_basename;
/* These need a lock whenever they are used */
int fd;
@@ -319,24 +334,24 @@ xdp_fh_finalize (XdpFh *fh)
if (fh->truncated)
{
fsync (fh->trunc_fd);
if (rename (fh->trunc_path, fh->real_path) != 0)
if (renameat (fh->dir_fd, fh->trunc_basename,
fh->dir_fd, fh->real_basename) != 0)
g_warning ("Unable to replace truncated document");
}
else if (fh->trunc_path)
unlink (fh->trunc_path);
else if (fh->trunc_basename)
unlinkat (fh->dir_fd, fh->trunc_basename, 0);
if (fh->fd >= 0)
close (fh->fd);
if (fh->trunc_fd >= 0)
close (fh->trunc_fd);
if (fh->fd >= 0)
close (fh->fd);
if (fh->trunc_fd >= 0)
close (fh->trunc_fd);
if (fh->dir_fd >= 0)
close (fh->dir_fd);
g_clear_pointer (&fh->trunc_path, g_free);
g_clear_pointer (&fh->real_path, g_free);
g_clear_pointer (&fh->trunc_basename, g_free);
g_clear_pointer (&fh->real_basename, g_free);
g_free (fh);
}
@@ -398,6 +413,7 @@ xdp_fh_new (fuse_ino_t inode,
fh->fd = fd;
if (tmp)
fh->tmp_id = tmp->tmp_id;
fh->dir_fd = -1;
fh->trunc_fd = -1;
fh->ref_count = 1; /* Owned by fuse_file_info fi */
@@ -656,7 +672,7 @@ app_can_see_doc (XdgAppDbEntry *entry, guint32 app_id)
const char *app_name = get_app_name_from_id (app_id);
if (app_name != NULL &&
xdp_has_permissions (entry, app_name, XDP_PERMISSION_FLAGS_READ))
xdp_entry_has_permissions (entry, app_name, XDP_PERMISSION_FLAGS_READ))
return TRUE;
return FALSE;
@@ -670,10 +686,9 @@ xdp_stat (fuse_ino_t ino,
XdpInodeClass class = get_class (ino);
guint64 class_ino = get_class_ino (ino);
g_autoptr (XdgAppDbEntry) entry = NULL;
const char *path = NULL;
struct stat tmp_stbuf;
g_autoptr(XdpTmp) tmp = NULL;
g_autofree char *backing_path = NULL;
g_autofree char *backing_basename = NULL;
stbuf->st_ino = ino;
@@ -736,8 +751,7 @@ xdp_stat (fuse_ino_t ino,
stbuf->st_nlink = DOC_FILE_NLINK;
path = xdp_get_path (entry);
if (stat (path, &tmp_stbuf) != 0)
if (xdp_entry_stat (entry, &tmp_stbuf, AT_SYMLINK_NOFOLLOW) != 0)
return ENOENT;
stbuf->st_mode = S_IFREG | get_user_perms (&tmp_stbuf);
@@ -759,10 +773,16 @@ xdp_stat (fuse_ino_t ino,
stbuf->st_mode = S_IFREG;
stbuf->st_nlink = DOC_FILE_NLINK;
backing_path = xdp_tmp_get_backing_path (tmp);
backing_basename = xdp_tmp_get_backing_basename (tmp);
if (backing_path == NULL || stat (backing_path, &tmp_stbuf) != 0)
return ENOENT;
{
glnx_fd_close int dir_fd = xdp_entry_open_dir (tmp->entry);
if (backing_basename == NULL ||
dir_fd == -1 ||
fstatat (dir_fd, backing_basename, &tmp_stbuf, 0) != 0)
return ENOENT;
}
stbuf->st_mode = S_IFREG | get_user_perms (&tmp_stbuf);
stbuf->st_size = tmp_stbuf.st_size;
@@ -905,7 +925,7 @@ xdp_lookup (fuse_ino_t parent,
entry = xdp_lookup_doc (parent_class_ino);
if (entry != NULL)
{
g_autofree char *basename = xdp_dup_basename (entry);
g_autofree char *basename = xdp_entry_dup_basename (entry);
if (strcmp (name, basename) == 0)
{
*inode = make_inode (DOC_FILE_INO_CLASS, parent_class_ino);
@@ -1036,9 +1056,8 @@ dirbuf_add_doc_file (fuse_req_t req,
guint32 doc_id)
{
struct stat tmp_stbuf;
const char *path = xdp_get_path (entry);
g_autofree char *basename = xdp_dup_basename (entry);
if (stat (path, &tmp_stbuf) == 0)
g_autofree char *basename = xdp_entry_dup_basename (entry);
if (xdp_entry_stat (entry, &tmp_stbuf, AT_SYMLINK_NOFOLLOW) == 0)
dirbuf_add (req, b, basename,
make_inode (DOC_FILE_INO_CLASS, doc_id));
}
@@ -1222,14 +1241,13 @@ get_open_flags (struct fuse_file_info *fi)
}
static char *
create_tmp_for_doc (XdgAppDbEntry *entry, int flags, int *fd_out)
create_tmp_for_doc (XdgAppDbEntry *entry, int dir_fd, int flags, int *fd_out)
{
g_autofree char *dirname = xdp_dup_dirname (entry);
g_autofree char *basename = xdp_dup_basename (entry);
g_autofree char *template = g_strconcat (dirname, "/.", basename, ".XXXXXX", NULL);
g_autofree char *basename = xdp_entry_dup_basename (entry);
g_autofree char *template = g_strconcat (".xdp_", basename, ".XXXXXX", NULL);
int fd;
fd = g_mkstemp_full (template, flags, 0600);
fd = xdg_app_mkstempat (dir_fd, template, flags, 0600);
if (fd == -1)
return NULL;
@@ -1246,7 +1264,6 @@ xdp_fuse_open (fuse_req_t req,
guint64 class_ino = get_class_ino (ino);
struct stat stbuf = {0};
g_autoptr (XdgAppDbEntry) entry = NULL;
const char *path = NULL;
g_autoptr(XdpTmp) tmp = NULL;
glnx_fd_close int fd = -1;
int res;
@@ -1268,50 +1285,59 @@ xdp_fuse_open (fuse_req_t req,
if (entry && class == DOC_FILE_INO_CLASS)
{
g_autofree char *write_path = NULL;
g_autofree char *tmp_basename = NULL;
glnx_fd_close int write_fd = -1;
glnx_fd_close int dir_fd = -1;
g_autofree char *basename = xdp_entry_dup_basename (entry);
path = xdp_get_path (entry);
dir_fd = xdp_entry_open_dir (entry);
if (dir_fd == -1)
{
fuse_reply_err (req, errno);
return;
}
if ((fi->flags & 3) != O_RDONLY)
{
if (access (path, W_OK) != 0)
if (faccessat (dir_fd, basename, W_OK, 0) != 0)
{
fuse_reply_err (req, errno);
return;
}
write_path = create_tmp_for_doc (entry, O_RDWR, &write_fd);
if (write_path == NULL)
tmp_basename = create_tmp_for_doc (entry, dir_fd, O_RDWR, &write_fd);
if (tmp_basename == NULL)
{
fuse_reply_err (req, errno);
return;
}
}
fd = open (path, O_RDONLY);
fd = openat (dir_fd, basename, O_RDONLY|O_NOFOLLOW|O_CLOEXEC);
if (fd < 0)
{
fuse_reply_err (req, errno);
return;
}
fh = xdp_fh_new (ino, fi, steal_fd(&fd), NULL);
fh->dir_fd = steal_fd (&dir_fd);
fh->trunc_fd = steal_fd (&write_fd);
fh->trunc_path = g_steal_pointer (&write_path);
fh->real_path = g_strdup (path);
fh->trunc_basename = g_steal_pointer (&tmp_basename);
fh->real_basename = g_strdup (basename);
if (fuse_reply_open (req, fi))
xdp_fh_unref (fh);
}
else if (class == TMPFILE_INO_CLASS &&
(tmp = find_tmp_by_id (class_ino)))
{
g_autofree char *backing_path = xdp_tmp_get_backing_path (tmp);
if (backing_path == NULL)
glnx_fd_close int dir_fd = xdp_entry_open_dir (tmp->entry);
g_autofree char *backing_basename = xdp_tmp_get_backing_basename (tmp);
if (dir_fd == -1 || backing_basename == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
fd = open (backing_path, get_open_flags (fi));
fd = openat (dir_fd, backing_basename, get_open_flags (fi));
if (fd < 0)
{
fuse_reply_err (req, errno);
@@ -1338,7 +1364,6 @@ xdp_fuse_create (fuse_req_t req,
XdpFh *fh;
g_autoptr(XdgAppDbEntry) entry = NULL;
g_autofree char *basename = NULL;
const char *path = NULL;
glnx_fd_close int fd = -1;
int res;
@@ -1363,23 +1388,29 @@ xdp_fuse_create (fuse_req_t req,
return;
}
basename = xdp_dup_basename (entry);
basename = xdp_entry_dup_basename (entry);
if (strcmp (name, basename) == 0)
{
g_autofree char *write_path = NULL;
g_autofree char *tmp_basename = NULL;
glnx_fd_close int write_fd = -1;
guint32 doc_id = xdp_id_from_name (name);
glnx_fd_close int dir_fd = -1;
write_path = create_tmp_for_doc (entry, O_RDWR, &write_fd);
if (write_path == NULL)
dir_fd = xdp_entry_open_dir (entry);
if (dir_fd == -1)
{
fuse_reply_err (req, errno);
return;
}
path = xdp_get_path (entry);
tmp_basename = create_tmp_for_doc (entry, dir_fd, O_RDWR, &write_fd);
if (tmp_basename == NULL)
{
fuse_reply_err (req, errno);
return;
}
fd = open (path, O_CREAT|O_EXCL|O_RDONLY);
fd = openat (dir_fd, basename, O_CREAT|O_EXCL|O_RDONLY|O_NOFOLLOW|O_CLOEXEC);
if (fd < 0)
{
fuse_reply_err (req, errno);
@@ -1389,10 +1420,11 @@ xdp_fuse_create (fuse_req_t req,
e.ino = make_inode (DOC_FILE_INO_CLASS, doc_id);
fh = xdp_fh_new (e.ino, fi, steal_fd (&fd), NULL);
fh->dir_fd = steal_fd (&dir_fd);
fh->truncated = TRUE;
fh->trunc_fd = steal_fd (&write_fd);
fh->trunc_path = g_steal_pointer (&write_path);
fh->real_path = g_strdup (path);
fh->trunc_basename = g_steal_pointer (&tmp_basename);
fh->real_basename = g_strdup (basename);
if (xdp_fh_fstat_locked (fh, &e.attr) != 0)
{
@@ -1422,18 +1454,19 @@ xdp_fuse_create (fuse_req_t req,
if (tmpfile)
{
g_autofree char *backing_path = NULL;
glnx_fd_close int dir_fd = xdp_entry_open_dir (tmpfile->entry);
g_autofree char *backing_basename = NULL;
G_UNLOCK(tmp_files);
backing_path = xdp_tmp_get_backing_path (tmpfile);
if (backing_path == NULL)
backing_basename = xdp_tmp_get_backing_basename (tmpfile);
if (dir_fd == -1 || backing_basename == NULL)
{
fuse_reply_err (req, EINVAL);
return;
}
fd = open (backing_path, get_open_flags (fi));
fd = openat (dir_fd, backing_basename, get_open_flags (fi));
if (fd == -1)
{
fuse_reply_err (req, errno);
@@ -1443,13 +1476,21 @@ xdp_fuse_create (fuse_req_t req,
else
{
int errsv;
g_autofree char *tmp_path = NULL;
g_autofree char *tmp_basename = NULL;
glnx_fd_close int dir_fd = -1;
tmp_path = create_tmp_for_doc (entry, get_open_flags (fi), &fd);
if (tmp_path == NULL)
return NULL;
dir_fd = xdp_entry_open_dir (entry);
if (dir_fd == -1)
{
fuse_reply_err (req, errno);
return;
}
tmpfile = xdp_tmp_new_nolock (parent, name, tmp_path);
tmp_basename = create_tmp_for_doc (entry, dir_fd, get_open_flags (fi), &fd);
if (tmp_basename == NULL)
return;
tmpfile = xdp_tmp_new_nolock (parent, entry, name, tmp_basename);
errsv = errno;
G_UNLOCK(tmp_files);
@@ -1626,17 +1667,24 @@ xdp_fuse_rename (fuse_req_t req,
return;
}
basename = xdp_dup_basename (entry);
basename = xdp_entry_dup_basename (entry);
if (strcmp (newname, basename) == 0)
{
const char *real_path = xdp_get_path (entry);
g_autofree char *backing_path = NULL;
glnx_fd_close int dir_fd = -1;
g_autofree char *backing_basename = NULL;
/* Rename tmpfile to regular file */
dir_fd = xdp_entry_open_dir (entry);
if (dir_fd == -1)
{
fuse_reply_err (req, errno);
return;
}
/* Steal backing path so we don't delete it when unlinking tmp */
backing_path = xdp_tmp_steal_backing_path (tmp);
if (backing_path == NULL)
backing_basename = xdp_tmp_steal_backing_basename (tmp);
if (backing_basename == NULL)
{
fuse_reply_err (req, EINVAL);
return;
@@ -1645,7 +1693,8 @@ xdp_fuse_rename (fuse_req_t req,
/* Stop writes to all outstanding fds to the temp file */
mark_open_tmp_file_readonly (tmp->tmp_id);
if (rename (backing_path, real_path) != 0)
if (renameat (dir_fd, backing_basename,
dir_fd, basename) != 0)
{
fuse_reply_err (req, errno);
return;
@@ -1797,7 +1846,7 @@ xdp_fuse_fsyncdir (fuse_req_t req,
entry = xdp_lookup_doc (doc_id);
if (entry != NULL)
{
g_autofree char *dirname = xdp_dup_dirname (entry);
g_autofree char *dirname = xdp_entry_dup_dirname (entry);
int fd = open (dirname, O_DIRECTORY|O_RDONLY);
if (fd >= 0)
{
@@ -1868,12 +1917,19 @@ xdp_fuse_unlink (fuse_req_t req,
return;
}
basename = xdp_dup_basename (entry);
basename = xdp_entry_dup_basename (entry);
if (strcmp (name, basename) == 0)
{
const char *real_path = xdp_get_path (entry);
glnx_fd_close int dir_fd = -1;
if (unlink (real_path) != 0)
dir_fd = xdp_entry_open_dir (entry);
if (dir_fd == -1)
{
fuse_reply_err (req, errno);
return;
}
if (unlinkat (dir_fd, basename, 0) != 0)
{
fuse_reply_err (req, errno);
return;
+40 -53
View File
@@ -134,7 +134,8 @@ portal_grant_permissions (GDBusMethodInvocation *invocation,
perms = xdp_parse_permissions (permissions);
/* Must have grant-permissions and all the newly granted permissions */
if (!xdp_has_permissions (entry, app_id, XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS | perms))
if (!xdp_entry_has_permissions (entry, app_id,
XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS | perms))
{
g_dbus_method_invocation_return_error (invocation, XDG_APP_ERROR, XDG_APP_ERROR_NOT_ALLOWED,
"Not enough permissions");
@@ -142,7 +143,7 @@ portal_grant_permissions (GDBusMethodInvocation *invocation,
}
do_set_permissions (entry, id, target_app_id,
perms | xdp_get_permissions (entry, target_app_id));
perms | xdp_entry_get_permissions (entry, target_app_id));
g_dbus_method_invocation_return_value (invocation, g_variant_new ("()"));
}
@@ -178,7 +179,8 @@ portal_revoke_permissions (GDBusMethodInvocation *invocation,
perms = xdp_parse_permissions (permissions);
/* Must have grant-permissions, or be itself */
if (!xdp_has_permissions (entry, app_id, XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS) ||
if (!xdp_entry_has_permissions (entry, app_id,
XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS) ||
strcmp (app_id, target_app_id) == 0)
{
g_dbus_method_invocation_return_error (invocation, XDG_APP_ERROR, XDG_APP_ERROR_NOT_ALLOWED,
@@ -187,7 +189,7 @@ portal_revoke_permissions (GDBusMethodInvocation *invocation,
}
do_set_permissions (entry, id, target_app_id,
~perms & xdp_get_permissions (entry, target_app_id));
~perms & xdp_entry_get_permissions (entry, target_app_id));
g_dbus_method_invocation_return_value (invocation, g_variant_new ("()"));
}
@@ -210,7 +212,7 @@ portal_delete (GDBusMethodInvocation *invocation,
return;
}
if (!xdp_has_permissions (entry, app_id, XDP_PERMISSION_FLAGS_DELETE))
if (!xdp_entry_has_permissions (entry, app_id, XDP_PERMISSION_FLAGS_DELETE))
{
g_dbus_method_invocation_return_error (invocation, XDG_APP_ERROR, XDG_APP_ERROR_NOT_ALLOWED,
"Not enough permissions");
@@ -226,13 +228,19 @@ portal_delete (GDBusMethodInvocation *invocation,
}
char *
do_create_doc (const char *path)
do_create_doc (struct stat *parent_st_buf, const char *path)
{
g_autoptr(GVariant) data = g_variant_ref_sink (g_variant_new_bytestring (path));
g_autoptr(GVariant) data = NULL;
g_autoptr (XdgAppDbEntry) entry = NULL;
g_auto(GStrv) ids = NULL;
char *id = NULL;
data =
g_variant_ref_sink (g_variant_new ("(^aytt)",
path,
(guint64)parent_st_buf->st_dev,
(guint64)parent_st_buf->st_ino));
ids = xdg_app_db_list_ids_by_value (db, data);
if (ids[0] != NULL)
@@ -267,48 +275,19 @@ static void
portal_add (GDBusMethodInvocation *invocation,
GVariant *parameters,
const char *app_id)
{
const char *path;
g_autofree char *id = NULL;
if (app_id[0] != '\0')
{
/* don't allow this from within the sandbox */
g_dbus_method_invocation_return_error (invocation,
XDG_APP_ERROR, XDG_APP_ERROR_NOT_ALLOWED,
"Not allowed inside sandbox");
return;
}
g_variant_get (parameters, "(^ay)", &path);
if (!g_path_is_absolute (path))
{
g_dbus_method_invocation_return_error (invocation,
XDG_APP_ERROR, XDG_APP_ERROR_INVALID_ARGUMENT,
"Document paths must be absolute");
return;
}
id = do_create_doc (path);
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(s)", id));
}
static void
portal_add_local (GDBusMethodInvocation *invocation,
GVariant *parameters,
const char *app_id)
{
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;
char path_buffer[PATH_MAX+1];
ssize_t symlink_size;
struct stat st_buf, real_st_buf;
struct stat st_buf, real_st_buf, real_parent_st_buf;
g_autofree char *dirname = NULL;
g_autofree char *name = NULL;
g_variant_get (parameters, "(h)", &fd_id);
@@ -326,15 +305,18 @@ portal_add_local (GDBusMethodInvocation *invocation,
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 get fd flags */
(fd_flags = fcntl (fd, F_GETFL)) == -1 ||
/* Must be able to read */
(fd_flags & O_ACCMODE) == O_WRONLY ||
/* 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)
{
g_dbus_method_invocation_return_error (invocation,
@@ -345,7 +327,15 @@ portal_add_local (GDBusMethodInvocation *invocation,
path_buffer[symlink_size] = 0;
if (lstat (path_buffer, &real_st_buf) < 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)
{
@@ -356,19 +346,17 @@ portal_add_local (GDBusMethodInvocation *invocation,
return;
}
id = do_create_doc (path_buffer);
id = do_create_doc (&real_parent_st_buf, path_buffer);
if (app_id[0] != '\0')
{
/* also grant app-id perms based on file_mode */
guint32 perms = XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS | XDP_PERMISSION_FLAGS_READ;
g_autoptr(XdgAppDbEntry) entry = NULL;
entry = xdg_app_db_lookup (db, id);
if ((fd_flags & O_ACCMODE) == O_RDWR)
perms |= XDP_PERMISSION_FLAGS_WRITE;
do_set_permissions (entry, id, app_id, perms);
do_set_permissions (entry, id, app_id,
XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS |
XDP_PERMISSION_FLAGS_READ |
XDP_PERMISSION_FLAGS_WRITE);
}
g_dbus_method_invocation_return_value (invocation,
@@ -428,7 +416,6 @@ on_bus_acquired (GDBusConnection *connection,
g_signal_connect_swapped (helper, "handle-get-mount-point", G_CALLBACK (handle_get_mount_point), NULL);
g_signal_connect_swapped (helper, "handle-add", G_CALLBACK (handle_method), portal_add);
g_signal_connect_swapped (helper, "handle-add-local", G_CALLBACK (handle_method), portal_add_local);
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);
+73 -9
View File
@@ -50,7 +50,7 @@ xdp_parse_permissions (const char **permissions)
}
XdpPermissionFlags
xdp_get_permissions (XdgAppDbEntry *entry,
xdp_entry_get_permissions (XdgAppDbEntry *entry,
const char *app_id)
{
g_autofree const char **permissions = NULL;
@@ -63,13 +63,13 @@ xdp_get_permissions (XdgAppDbEntry *entry,
}
gboolean
xdp_has_permissions (XdgAppDbEntry *entry,
xdp_entry_has_permissions (XdgAppDbEntry *entry,
const char *app_id,
XdpPermissionFlags perms)
{
XdpPermissionFlags current_perms;
current_perms = xdp_get_permissions (entry, app_id);
current_perms = xdp_entry_get_permissions (entry, app_id);
return (current_perms & perms) == perms;
}
@@ -87,28 +87,92 @@ xdp_name_from_id (guint32 doc_id)
}
const char *
xdp_get_path (XdgAppDbEntry *entry)
xdp_entry_get_path (XdgAppDbEntry *entry)
{
g_autoptr(GVariant) v = xdg_app_db_entry_get_data (entry);
return g_variant_get_bytestring (v);
g_autoptr(GVariant) c = g_variant_get_child_value (v, 0);
return g_variant_get_bytestring (c);
}
char *
xdp_dup_basename (XdgAppDbEntry *entry)
xdp_entry_dup_basename (XdgAppDbEntry *entry)
{
const char *path = xdp_get_path (entry);
const char *path = xdp_entry_get_path (entry);
return g_path_get_basename (path);
}
char *
xdp_dup_dirname (XdgAppDbEntry *entry)
xdp_entry_dup_dirname (XdgAppDbEntry *entry)
{
const char *path = xdp_get_path (entry);
const char *path = xdp_entry_get_path (entry);
return g_path_get_dirname (path);
}
guint64
xdp_entry_get_device (XdgAppDbEntry *entry)
{
g_autoptr(GVariant) v = xdg_app_db_entry_get_data (entry);
g_autoptr(GVariant) c = g_variant_get_child_value (v, 1);
return g_variant_get_uint64 (c);
}
guint64
xdp_entry_get_inode (XdgAppDbEntry *entry)
{
g_autoptr(GVariant) v = xdg_app_db_entry_get_data (entry);
g_autoptr(GVariant) c = g_variant_get_child_value (v, 2);
return g_variant_get_uint64 (c);
}
int
xdp_entry_open_dir (XdgAppDbEntry *entry)
{
g_autofree char *dirname = xdp_entry_dup_dirname (entry);
struct stat st_buf;
int fd;
fd = open (dirname, O_CLOEXEC | O_PATH | O_DIRECTORY);
if (fd == -1)
return -1;
if (fstat (fd, &st_buf) < 0)
{
close (fd);
errno = ENOENT;
return -1;
}
if (st_buf.st_ino != xdp_entry_get_inode (entry) ||
st_buf.st_dev != xdp_entry_get_device (entry))
{
close (fd);
errno = ENOENT;
return -1;
}
return fd;
}
int
xdp_entry_stat (XdgAppDbEntry *entry,
struct stat *buf,
int flags)
{
glnx_fd_close int fd = -1;
g_autofree char *basename = xdp_entry_dup_basename (entry);
fd = xdp_entry_open_dir (entry);
if (fd < 0)
return -1;
if (fstatat (fd, basename, buf, flags) != 0)
return -1;
return 0;
}
static GHashTable *app_ids;
typedef struct {
+19 -10
View File
@@ -9,16 +9,25 @@ G_BEGIN_DECLS
const char ** xdg_unparse_permissions (XdpPermissionFlags permissions);
XdpPermissionFlags xdp_parse_permissions (const char **permissions);
XdpPermissionFlags xdp_get_permissions (XdgAppDbEntry *entry,
const char *app_id);
gboolean xdp_has_permissions (XdgAppDbEntry *entry,
const char *app_id,
XdpPermissionFlags perms);
const char * xdp_get_path (XdgAppDbEntry *entry);
char * xdp_dup_basename (XdgAppDbEntry *entry);
char * xdp_dup_dirname (XdgAppDbEntry *entry);
guint32 xdp_id_from_name (const char *name);
char * xdp_name_from_id (guint32 doc_id);
XdpPermissionFlags xdp_entry_get_permissions (XdgAppDbEntry *entry,
const char *app_id);
gboolean xdp_entry_has_permissions (XdgAppDbEntry *entry,
const char *app_id,
XdpPermissionFlags perms);
const char * xdp_entry_get_path (XdgAppDbEntry *entry);
char * xdp_entry_dup_basename (XdgAppDbEntry *entry);
char * xdp_entry_dup_dirname (XdgAppDbEntry *entry);
guint64 xdp_entry_get_device (XdgAppDbEntry *entry);
guint64 xdp_entry_get_inode (XdgAppDbEntry *entry);
int xdp_entry_open_dir (XdgAppDbEntry *entry);
int xdp_entry_stat (XdgAppDbEntry *entry,
struct stat *buf,
int flags);
guint32 xdp_id_from_name (const char *name);
char * xdp_name_from_id (guint32 doc_id);
void xdp_invocation_lookup_app_id (GDBusMethodInvocation *invocation,
GCancellable *cancellable,