xi-desktop-portals

Simpler specifications for Linux desktop APIs.
git clone https://git.ce9e.org/xi-desktop-portals.git

commit
345a7f2a5ef179d85f3e6b96a3b1fade58910e90
parent
7735fb5cb73c477e663ef6a43e37cde4e2a62f1c
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2026-03-08 10:34
[OpenURI] only allow to open files that exist under the same path on the host

Diffstat

M OpenURI/README.md 3 ++-
M OpenURI/open-uri.c 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

2 files changed, 96 insertions, 1 deletions


diff --git a/OpenURI/README.md b/OpenURI/README.md

@@ -26,7 +26,8 @@ $ printf "https://example.com" | nc -U "$XDG_RUNTIME_DIR/xi.portal.OpenURI"`
   26    26 -   How to pass [activation tokens](https://wayland.app/protocols/xdg-activation-v1)?
   27    27 -   It is not clear how support for `file://` URIs should be implemented. The
   28    28     namespace is available at `/proc/{pid}/root/`, but that will go away when
   29    -1     the calling process exits.
   -1    29     the calling process exits. The reference implementation currently only
   -1    30     allows to open files that also exist on the same location on the host.
   30    31 
   31    32 ## Compatibility Considerations
   32    33 

diff --git a/OpenURI/open-uri.c b/OpenURI/open-uri.c

@@ -1,10 +1,94 @@
   -1     1 #define _GNU_SOURCE
   -1     2 #include <linux/openat2.h>
    1     3 #include <stdio.h>
   -1     4 #include <stdlib.h>
   -1     5 #include <string.h>
   -1     6 #include <sys/pidfd.h>
   -1     7 #include <sys/select.h>
    2     8 #include <sys/socket.h>
   -1     9 #include <sys/stat.h>
   -1    10 #include <sys/syscall.h>
   -1    11 #include <unistd.h>
   -1    12 
    3    13 #include <gio/gio.h>
    4    14 
    5    15 #define MAX_URI_LENGTH 1024
    6    16 #define SOCK 3
    7    17 
   -1    18 int get_pidfd(int sock) {
   -1    19     int pidfd;
   -1    20     socklen_t pidfd_len = sizeof(pidfd);
   -1    21     if (getsockopt(sock, SOL_SOCKET, SO_PEERPIDFD, &pidfd, &pidfd_len) < 0) {
   -1    22         perror("getsockopt");
   -1    23         return -1;
   -1    24     }
   -1    25     return pidfd;
   -1    26 }
   -1    27 
   -1    28 int is_readable(int fd) {
   -1    29     fd_set fds;
   -1    30     struct timeval timeout;
   -1    31 
   -1    32     FD_ZERO(&fds);
   -1    33     FD_SET(fd, &fds);
   -1    34 
   -1    35     timeout.tv_sec = 0;
   -1    36     timeout.tv_usec = 0;
   -1    37 
   -1    38     return select(fd + 1, &fds, NULL, NULL, &timeout);
   -1    39 }
   -1    40 
   -1    41 int stat_at_root(const char *path, const char *root, struct stat *statbuf) {
   -1    42     struct open_how how = {0};
   -1    43     how.flags = O_RDONLY;
   -1    44     how.resolve = RESOLVE_IN_ROOT;
   -1    45 
   -1    46     int root_fd = open(root, O_RDONLY | O_DIRECTORY);
   -1    47     if (root_fd < 0) {
   -1    48         perror("open root");
   -1    49         return -1;
   -1    50     }
   -1    51 
   -1    52     int fd = syscall(SYS_openat2, root_fd, path, &how, sizeof(how));
   -1    53     close(root_fd);
   -1    54     if (fd < 0) {
   -1    55         perror("openat");
   -1    56         return -1;
   -1    57     }
   -1    58 
   -1    59     if (fstat(fd, statbuf) != 0) {
   -1    60         perror("fstat");
   -1    61         close(fd);
   -1    62         return -1;
   -1    63     }
   -1    64 
   -1    65     close(fd);
   -1    66     return 0;
   -1    67 }
   -1    68 
   -1    69 int same_on_host(char *uri, int pidfd) {
   -1    70     char root_sender[32];
   -1    71     struct stat s_sender, s_host;
   -1    72 
   -1    73     snprintf(
   -1    74         root_sender, sizeof(root_sender), "/proc/%i/root/", pidfd_getpid(pidfd)
   -1    75     );
   -1    76     if (stat_at_root(uri + 7, root_sender, &s_sender) != 0) {
   -1    77         return -1;
   -1    78     }
   -1    79     if (stat_at_root(uri + 7, "/", &s_host) != 0) {
   -1    80         return -1;
   -1    81     }
   -1    82     if (s_sender.st_dev != s_host.st_dev || s_sender.st_ino != s_host.st_ino) {
   -1    83         return -1;
   -1    84     }
   -1    85     if (is_readable(pidfd) != 0) {
   -1    86         return -1;
   -1    87     }
   -1    88 
   -1    89     return 0;
   -1    90 }
   -1    91 
    8    92 int main() {
    9    93     char uri[MAX_URI_LENGTH];
   10    94 
@@ -16,6 +100,16 @@ int main() {
   16   100     uri[n] = '\0';
   17   101     g_strchomp(uri);
   18   102 
   -1   103     if (strncmp(uri, "file://", 7) == 0) {
   -1   104         int pidfd = get_pidfd(SOCK);
   -1   105         if (pidfd < 0) {
   -1   106             return EXIT_FAILURE;
   -1   107         }
   -1   108         if (same_on_host(uri, pidfd) != 0) {
   -1   109             return EXIT_FAILURE;
   -1   110         }
   -1   111     }
   -1   112 
   19   113     GError *error = NULL;
   20   114     if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
   21   115         send(SOCK, error->message, strlen(error->message), 0);