Reimplement wayland message box function with execvp.

Previous version used 'popen' which required to sanitize user provided text. Not
sanitizing text could cause failure if user provided text included a " or command
injection with `cmd`.
This commit is contained in:
meyraud705 2021-04-20 17:49:21 +02:00 committed by Ryan C. Gordon
parent 99ef03b96e
commit 0cd0e9ba98
1 changed files with 142 additions and 117 deletions

View File

@ -24,142 +24,167 @@
#if SDL_VIDEO_DRIVER_WAYLAND #if SDL_VIDEO_DRIVER_WAYLAND
#include "SDL.h" #include "SDL.h"
#include <stdlib.h> /* popen/pclose/fgets */ #include <stdlib.h> /* fgets */
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#define MAX_BUTTONS 8 /* Maximum number of buttons supported */
int int
Wayland_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) Wayland_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
{ {
#define ZENITY_CONST(name, str) \ int fd_pipe[2]; /* fd_pipe[0]: read end of pipe, fd_pipe[1]: write end of pipe */
const char *name = str; \ pid_t pid1;
const size_t name##_len = SDL_strlen(name);
ZENITY_CONST(zenity, "zenity --question --switch --no-wrap --icon-name=dialog-")
ZENITY_CONST(title, "--title=")
ZENITY_CONST(message, "--text=")
ZENITY_CONST(extrabutton, "--extra-button=")
ZENITY_CONST(icon_info, "information")
ZENITY_CONST(icon_warn, "warning")
ZENITY_CONST(icon_error, "error")
#undef ZENITY_CONST
char *command, *output; if (messageboxdata->numbuttons > MAX_BUTTONS) {
size_t command_len, output_len; return SDL_SetError("Too many buttons (%d max allowed)", MAX_BUTTONS);
const char *icon;
char *tmp;
FILE *process;
int i;
/* Start by calculating the lengths of the strings. These commands can get
* pretty long, so we need to dynamically allocate this.
*/
command_len = zenity_len;
output_len = 0;
switch (messageboxdata->flags)
{
case SDL_MESSAGEBOX_ERROR:
icon = icon_error;
command_len += icon_error_len;
break;
case SDL_MESSAGEBOX_WARNING:
icon = icon_warn;
command_len += icon_warn_len;
break;
case SDL_MESSAGEBOX_INFORMATION:
default:
icon = icon_info;
command_len += icon_info_len;
break;
} }
#define ADD_ARGUMENT(arg, value) \
command_len += arg + 3; /* Two " and a space */ \ if (pipe(fd_pipe) != 0) { /* create a pipe */
if (messageboxdata->value != NULL) { \ return SDL_SetError("pipe() failed: %s", strerror(errno));
command_len += SDL_strlen(messageboxdata->value); \ }
pid1 = fork();
if (pid1 == 0) { /* child process */
int argc = 4, i;
const char* argv[4 + 2/* icon name */ + 2/* title */ + 2/* message */ + 2*MAX_BUTTONS + 1/* NULL */] = {
"zenity", "--question", "--switch", "--no-wrap",
};
close(fd_pipe[0]); /* no reading from pipe */
/* write stdout in pipe */
if (dup2(fd_pipe[1], STDOUT_FILENO) == -1) {
_exit(EXIT_FAILURE);
} }
ADD_ARGUMENT(title_len, title)
ADD_ARGUMENT(message_len, message) argv[argc++] = "--icon-name";
#undef ADD_ARGUMENT switch (messageboxdata->flags) {
for (i = 0; i < messageboxdata->numbuttons; i += 1) { case SDL_MESSAGEBOX_ERROR:
command_len += extrabutton_len + 3; /* Two " and a space */ argv[argc++] = "error";
if (messageboxdata->buttons[i].text != NULL) { break;
const size_t button_len = SDL_strlen(messageboxdata->buttons[i].text); case SDL_MESSAGEBOX_WARNING:
command_len += button_len; argv[argc++] = "warning";
if (button_len > output_len) { break;
output_len = button_len; case SDL_MESSAGEBOX_INFORMATION:
default:
argv[argc++] = "information";
break;
}
if (messageboxdata->title && messageboxdata->title[0]) {
argv[argc++] = "--title";
argv[argc++] = messageboxdata->title;
} else {
argv[argc++] = "--title=\"\"";
}
if (messageboxdata->message && messageboxdata->message[0]) {
argv[argc++] = "--text";
argv[argc++] = messageboxdata->message;
} else {
argv[argc++] = "--text=\"\"";
}
for (i = 0; i < messageboxdata->numbuttons; ++i) {
if (messageboxdata->buttons[i].text && messageboxdata->buttons[i].text[0]) {
argv[argc++] = "--extra-button";
argv[argc++] = messageboxdata->buttons[i].text;
} else {
argv[argc++] = "--extra-button=\"\"";
} }
} }
} argv[argc] = NULL;
/* Don't forget null terminators! */ /* const casting argv is fine:
command_len += 1; * https://pubs.opengroup.org/onlinepubs/9699919799/functions/fexecve.html -> rational
output_len += 1; */
execvp("zenity", argv);
_exit(EXIT_FAILURE);
} else if (pid1 < 0) {
close(fd_pipe[0]);
close(fd_pipe[1]);
return SDL_SetError("fork() failed: %s", strerror(errno));
} else {
int status;
if (waitpid(pid1, &status, 0) == pid1) {
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) >= 0) {
int i;
size_t output_len = 1;
char* output = NULL;
char* tmp = NULL;
FILE* stdout = NULL;
/* Now that we know the length of the command, allocate! */ close(fd_pipe[1]); /* no writing to pipe */
command = (char*) SDL_malloc(command_len + output_len); /* At this point, if no button ID is needed, we can just bail as soon as the
if (command == NULL) { * process has completed.
return SDL_OutOfMemory(); */
} if (buttonid == NULL) {
output = command + command_len; close(fd_pipe[0]);
command[0] = '\0'; return 0;
output[0] = '\0'; }
*buttonid = -1;
/* Now we can build the command... */ /* find button with longest text */
SDL_strlcpy(command, zenity, command_len); for (i = 0; i < messageboxdata->numbuttons; ++i) {
SDL_strlcat(command, icon, command_len); if (messageboxdata->buttons[i].text != NULL) {
#define ADD_ARGUMENT(arg, value) \ const size_t button_len = SDL_strlen(messageboxdata->buttons[i].text);
SDL_strlcat(command, " ", command_len); \ if (button_len > output_len) {
SDL_strlcat(command, arg, command_len); \ output_len = button_len;
SDL_strlcat(command, "\"", command_len); \ }
if (value != NULL) { \ }
SDL_strlcat(command, value, command_len); \ }
} \ output = SDL_malloc(output_len + 1);
SDL_strlcat(command, "\"", command_len) if (!output) {
ADD_ARGUMENT(title, messageboxdata->title); close(fd_pipe[0]);
ADD_ARGUMENT(message, messageboxdata->message); return SDL_OutOfMemory();
for (i = 0; i < messageboxdata->numbuttons; i += 1) { }
ADD_ARGUMENT(extrabutton, messageboxdata->buttons[i].text); output[0] = '\0';
}
#undef ADD_ARGUMENT
/* ... then run it, finally. */ stdout = fdopen(fd_pipe[0], "r");
process = popen(command, "r"); if (!stdout) {
if (process == NULL) { SDL_free(output);
SDL_free(command); close(fd_pipe[0]);
return SDL_SetError("zenity failed to run"); return SDL_SetError("Couldn't open pipe for reading: %s", strerror(errno));
} }
tmp = fgets(output, output_len + 1, stdout);
fclose(stdout);
/* At this point, if no button ID is needed, we can just bail as soon as the if ((tmp == NULL) || (*tmp == '\0') || (*tmp == '\n')) {
* process has completed. SDL_free(output);
*/ return 0; /* User simply closed the dialog */
if (buttonid == NULL) { }
pclose(process);
goto end;
}
*buttonid = -1;
/* Read the result from stdout */ /* It likes to add a newline... */
tmp = fgets(output, output_len, process); tmp = SDL_strrchr(output, '\n');
pclose(process); if (tmp != NULL) {
if ((tmp == NULL) || (*tmp == '\0') || (*tmp == '\n')) { *tmp = '\0';
goto end; /* User simply closed the dialog */ }
}
/* It likes to add a newline... */ /* Check which button got pressed */
tmp = SDL_strrchr(output, '\n'); for (i = 0; i < messageboxdata->numbuttons; i += 1) {
if (tmp != NULL) { if (messageboxdata->buttons[i].text != NULL) {
*tmp = '\0'; if (SDL_strcmp(output, messageboxdata->buttons[i].text) == 0) {
} *buttonid = i;
break;
}
}
}
/* Check which button got pressed */ SDL_free(output);
for (i = 0; i < messageboxdata->numbuttons; i += 1) { return 0; /* success! */
if (messageboxdata->buttons[i].text != NULL) { } else {
if (SDL_strcmp(output, messageboxdata->buttons[i].text) == 0) { return SDL_SetError("zenity reported error or failed to launch: %d", WEXITSTATUS(status));
*buttonid = i; }
break; } else {
} return SDL_SetError("zenity failed for some reason");
}
} else {
return SDL_SetError("Waiting on zenity failed: %s", strerror(errno));
} }
} }
end:
SDL_free(command);
return 0; return 0;
} }