Skip to content

Commit

Permalink
validate-icon: Read icon from fd instead of a file
Browse files Browse the repository at this point in the history
This reduces the amount of data we need to copy once we allow using
sealable memfd for passing icons, and we can also store bytes icons in a
memfd instead of a temp file.
  • Loading branch information
jsparber committed Aug 27, 2024
1 parent 20634c6 commit c9d436c
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 49 deletions.
2 changes: 1 addition & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ endif
xdp_validate_icon = executable(
'xdg-desktop-portal-validate-icon',
'validate-icon.c',
dependencies: [gdk_pixbuf_dep],
dependencies: [gdk_pixbuf_dep, gio_unix_dep],
c_args: validate_icon_c_args,
install: true,
install_dir: libexecdir,
Expand Down
112 changes: 81 additions & 31 deletions src/validate-icon.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*
* Authors:
* Matthias Clasen <[email protected]>
* Julian Sparber <[email protected]>
*/

/* The canonical copy of this file is in:
Expand All @@ -27,6 +28,7 @@
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <glib/gstdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

#ifdef __FreeBSD__
Expand All @@ -36,37 +38,70 @@
#define ICON_VALIDATOR_GROUP "Icon Validator"
#define MAX_ICON_SIZE 512
#define MAX_SVG_ICON_SIZE 4096
#define BUFFER_SIZE 4096

static int
validate_icon (const char *filename)
validate_icon (int input_fd)
{
GdkPixbufFormat *format;
int max_size;
int width, height;
g_autofree char *name = NULL;
const char *allowed_formats[] = { "png", "jpeg", "svg", NULL };
g_autoptr(GdkPixbuf) pixbuf = NULL;
g_autoptr(GBytes) bytes = NULL;
g_autoptr(GError) error = NULL;
GdkPixbufFormat *format;
g_autoptr(GKeyFile) key_file = NULL;
g_autofree char *key_file_data = NULL;
g_autoptr(GdkPixbufLoader) loader = NULL;
g_autoptr(GMappedFile) mapped = NULL;
int max_size, width, height;
g_autofree char *name = NULL;
GdkPixbuf *pixbuf;

/* Ensure that we read from the beginning of the file */
lseek (input_fd, 0, SEEK_SET);

format = gdk_pixbuf_get_file_info (filename, &width, &height);
if (format == NULL)
if (!(mapped = g_mapped_file_new_from_fd (input_fd, FALSE, &error)))
{
g_printerr ("Format not recognized\n");
g_printerr ("Failed to create mapped file for image: %s\n", error->message);
return 1;
}

if (width != height)
bytes = g_mapped_file_get_bytes (mapped);

if (g_bytes_get_size (bytes) == 0)
{
g_printerr ("Expected a square icon but got: %dx%d\n", width, height);
g_printerr ("Image is 0 bytes\n");
return 1;
}

loader = gdk_pixbuf_loader_new ();

if (!gdk_pixbuf_loader_write_bytes (loader, bytes, &error) ||
!gdk_pixbuf_loader_close (loader, &error) ||
!(pixbuf = gdk_pixbuf_loader_get_pixbuf (loader)))
{
g_printerr ("Failed to load image: %s\n", error->message);
gdk_pixbuf_loader_close (loader, NULL);
return 1;
}

if (!(format = gdk_pixbuf_loader_get_format (loader)))
{
g_printerr ("Image format not recognized\n");
return 1;
}

name = gdk_pixbuf_format_get_name (format);
if (!g_strv_contains (allowed_formats, name))
{
g_printerr ("Format %s not accepted\n", name);
g_printerr ("Image format %s not accepted\n", name);
return 1;
}

width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);

if (width != height)
{
g_printerr ("Expected a square image but got: %dx%d\n", width, height);
return 1;
}

Expand All @@ -80,17 +115,9 @@ validate_icon (const char *filename)
return 1;
}

pixbuf = gdk_pixbuf_new_from_file (filename, &error);
if (pixbuf == NULL)
{
g_printerr ("Failed to load image: %s\n", error->message);
return 1;
}

/* Print the format and size for consumption by (at least) the dynamic
* launcher portal. xdg-desktop-portal has a copy of this file. Use a
* GKeyFile so the output can be easily extended in the future in a backwards
* compatible way.
* launcher portal. Use a GKeyFile so the output can be easily extended
* in the future in a backwards compatible way.
*/
key_file = g_key_file_new ();
g_key_file_set_string (key_file, ICON_VALIDATOR_GROUP, "format", name);
Expand Down Expand Up @@ -147,13 +174,14 @@ flatpak_get_bwrap (void)
}

static int
rerun_in_sandbox (const char *filename)
rerun_in_sandbox (int input_fd)
{
const char * const usrmerged_dirs[] = { "bin", "lib32", "lib64", "lib", "sbin" };
int i;
g_autoptr(GPtrArray) args = g_ptr_array_new_with_free_func (g_free);
char validate_icon[PATH_MAX + 1];
ssize_t symlink_size;
g_autofree char* arg_input_fd = NULL;

symlink_size = readlink ("/proc/self/exe", validate_icon, sizeof (validate_icon) - 1);
if (symlink_size < 0 || (size_t) symlink_size >= sizeof (validate_icon))
Expand Down Expand Up @@ -206,15 +234,15 @@ rerun_in_sandbox (const char *filename)
"--setenv", "GIO_USE_VFS", "local",
"--unsetenv", "TMPDIR",
"--die-with-parent",
"--ro-bind", filename, filename,
NULL);

if (g_getenv ("G_MESSAGES_DEBUG"))
add_args (args, "--setenv", "G_MESSAGES_DEBUG", g_getenv ("G_MESSAGES_DEBUG"), NULL);
if (g_getenv ("G_MESSAGES_PREFIXED"))
add_args (args, "--setenv", "G_MESSAGES_PREFIXED", g_getenv ("G_MESSAGES_PREFIXED"), NULL);

add_args (args, validate_icon, filename, NULL);
arg_input_fd = g_strdup_printf ("%d", input_fd);
add_args (args, validate_icon, "--fd", arg_input_fd, NULL);
g_ptr_array_add (args, NULL);

execvpe (flatpak_get_bwrap (), (char **) args->pdata, NULL);
Expand All @@ -224,10 +252,14 @@ rerun_in_sandbox (const char *filename)
}
#endif

static gboolean opt_sandbox;
static gboolean opt_sandbox;
static gchar *opt_path = NULL;
static gint opt_fd = -1;

static GOptionEntry entries[] = {
{ "sandbox", 0, 0, G_OPTION_ARG_NONE, &opt_sandbox, "Run in a sandbox", NULL },
{ "path", 0, 0, G_OPTION_ARG_FILENAME, &opt_path, "Read icon data from given file path, required to be guaranteed non modifiable", "PATH" },
{ "fd", 0, 0, G_OPTION_ARG_INT, &opt_fd, "Read icon data from given file descriptor, required to be sealed", "FD" },
{ NULL }
};

Expand All @@ -237,24 +269,42 @@ main (int argc, char *argv[])
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GError) error = NULL;

context = g_option_context_new ("PATH");
context = g_option_context_new (NULL);
g_option_context_add_main_entries (context, entries, NULL);
if (!g_option_context_parse (context, &argc, &argv, &error))
{
g_printerr ("Error: %s\n", error->message);
return 1;
}

if (argc != 2)
if (opt_path != NULL && opt_fd != -1)
{
g_printerr ("Usage: %s [OPTION…] PATH\n", argv[0]);
g_printerr ("Error: Only --path or --fd can be given\n");
return 1;
}

if (opt_path)
{
opt_fd = g_open (opt_path, O_RDONLY | O_CLOEXEC, 0);
if (opt_fd == -1)
{
g_printerr ("Error: Couldn't open file\n");
return 1;
}
}
else if (opt_fd == -1)
{
g_autofree char *help = NULL;

help = g_option_context_get_help (context, TRUE, NULL);
g_printerr ("Error: Either --path or --fd needs to be given\n\n%s", help);
return 1;
}

#ifdef HELPER
if (opt_sandbox)
return rerun_in_sandbox (argv[1]);
return rerun_in_sandbox (opt_fd);
else
#endif
return validate_icon (argv[1]);
return validate_icon (opt_fd);
}
27 changes: 10 additions & 17 deletions src/xdp-utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ cleanup_temp_file (void *p)
g_free (*pp);
}

#define VALIDATOR_INPUT_FD 3
#define ICON_VALIDATOR_GROUP "Icon Validator"

gboolean
Expand All @@ -572,16 +573,14 @@ xdp_validate_serialized_icon (GVariant *v,
__attribute__((cleanup(cleanup_temp_file))) char *name = NULL;
xdp_autofd int fd = -1;
g_autoptr(GOutputStream) stream = NULL;
int status;
g_autofree char *format = NULL;
g_autofree char *stdoutlog = NULL;
g_autofree char *stderrlog = NULL;
g_autoptr(GError) error = NULL;
const char *icon_validator = LIBEXECDIR "/xdg-desktop-portal-validate-icon";
const char *args[4];
const char *args[5];
int size;
gconstpointer bytes_data;
gsize bytes_len;
g_autofree char *output = NULL;
g_autoptr(GKeyFile) key_file = NULL;

icon = g_icon_deserialize (v);
Expand Down Expand Up @@ -641,25 +640,19 @@ xdp_validate_serialized_icon (GVariant *v,

args[0] = icon_validator;
args[1] = "--sandbox";
args[2] = name;
args[3] = NULL;
args[2] = "--fd";
args[3] = G_STRINGIFY (VALIDATOR_INPUT_FD);
args[4] = NULL;

if (!g_spawn_sync (NULL, (char **)args, NULL, 0, NULL, NULL, &stdoutlog, &stderrlog, &status, &error))
output = xdp_spawn_full (args, xdp_steal_fd (&fd), VALIDATOR_INPUT_FD, &error);
if (!output)
{
g_warning ("Icon validation: %s", error->message);
g_warning ("stderr:\n%s\n", stderrlog);
return FALSE;
}

if (!g_spawn_check_exit_status (status, &error))
{
g_warning ("Icon validation: %s", error->message);
g_warning ("stderr:\n%s\n", stderrlog);
g_warning ("Icon validation: Rejecting icon because validator failed: %s", error->message);
return FALSE;
}

key_file = g_key_file_new ();
if (!g_key_file_load_from_data (key_file, stdoutlog, -1, G_KEY_FILE_NONE, &error))
if (!g_key_file_load_from_data (key_file, output, -1, G_KEY_FILE_NONE, &error))
{
g_warning ("Icon validation: %s", error->message);
return FALSE;
Expand Down

0 comments on commit c9d436c

Please sign in to comment.