diff --git a/CHANGELOG.md b/CHANGELOG.md index 79da1e15..e0f60901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ * Check mask size in XISelectEvents * XI_RawMotion now uses raw inputs * Track handles of savefiles +* Fill asynchronously fake /dev/urandom for games that read a large number of bytes (#310) ## [1.3.5] - 2019-11-26 ### Added diff --git a/src/library/Makefile.am b/src/library/Makefile.am index 944d588b..c64b0b52 100644 --- a/src/library/Makefile.am +++ b/src/library/Makefile.am @@ -87,6 +87,7 @@ libtas_so_SOURCES = \ fileio/SaveFile.cpp \ fileio/SaveFileList.cpp \ fileio/stdiowrappers.cpp \ + fileio/URandom.cpp \ inputs/evdev.cpp \ inputs/inputevents.cpp \ inputs/inputs.cpp \ diff --git a/src/library/fileio/URandom.cpp b/src/library/fileio/URandom.cpp new file mode 100644 index 00000000..8c85fc65 --- /dev/null +++ b/src/library/fileio/URandom.cpp @@ -0,0 +1,111 @@ +/* + Copyright 2015-2020 Clément Gallet + + This file is part of libTAS. + + libTAS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libTAS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libTAS. If not, see . + */ + +#include "URandom.h" + +#include "FileHandleList.h" +#include "../logging.h" +#include +#include // getpid() + +namespace libtas { + +static int readfd = -1; +static int writefd = -1; +static char* datestr = nullptr; +static FILE* stream = nullptr; + +static void urandom_handler(int signum) +{ + /* TODO: write a simple PRNG which is seeded by shared_config.initial_time_sec + * instead of outputting it directly as a string. + */ + debuglogstdio(LCF_FILEIO | LCF_RANDOM, "Filling urandom fd"); + if (!datestr) { + time_t tsec = static_cast(shared_config.initial_time_sec); + datestr = asctime(gmtime(&tsec)); + } + + /* Fill the pipe with data from initial time */ + int err = write(writefd, datestr, strlen(datestr)); + while (err != -1) { + err = write(writefd, datestr, strlen(datestr)); + } +} + +int urandom_create_fd() +{ + debuglogstdio(LCF_FILEIO | LCF_RANDOM, "Open /dev/urandom"); + + if (readfd == -1) { + std::pair fds = FileHandleList::createPipe(O_NONBLOCK); + readfd = fds.first; + writefd = fds.second; + + /* Set the pipe size to some small value, because we won't need much. + * It should be at least 2*page_size, because on Linux a pipe is + * considered writeable if at least page_size can be written. + */ + MYASSERT(fcntl(writefd, F_SETPIPE_SZ, 2*4096) != -1); + + /* Fill the pipe */ + urandom_handler(0); + + GlobalNative gn; + + /* Add async signal for when the pipe is writeable */ + MYASSERT(fcntl(writefd, F_SETOWN, getpid()) != -1); + MYASSERT(fcntl(writefd, F_SETSIG, 0) != -1); + MYASSERT(fcntl(writefd, F_SETFL, O_ASYNC | O_NONBLOCK) != -1); + + /* Unblock SIGIO signal */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGIO); + MYASSERT(pthread_sigmask(SIG_UNBLOCK, &mask, nullptr) == 0); + + /* Add signal handler for SIGIO signal */ + struct sigaction sigio; + sigfillset(&sigio.sa_mask); + sigio.sa_handler = urandom_handler; + MYASSERT(sigaction(SIGIO, &sigio, nullptr) == 0) + } + + debuglog(LCF_FILEIO | LCF_RANDOM, "Return fd ", readfd); + return readfd; +} + +int urandom_get_fd() { + return readfd; +} + +FILE* urandom_create_file() { + if (!stream) { + int readfd = urandom_create_fd(); + stream = fdopen(readfd, "r"); + setvbuf(stream, nullptr, _IONBF, 0); + } + return stream; +} + +FILE* urandom_get_file() { + return stream; +} + +} diff --git a/src/library/fileio/URandom.h b/src/library/fileio/URandom.h new file mode 100644 index 00000000..aea965d0 --- /dev/null +++ b/src/library/fileio/URandom.h @@ -0,0 +1,41 @@ +/* + Copyright 2015-2020 Clément Gallet + + This file is part of libTAS. + + libTAS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libTAS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libTAS. If not, see . + */ + +#ifndef LIBTAS_URANDOM_H_INCLUDED +#define LIBTAS_URANDOM_H_INCLUDED + +#include + +namespace libtas { + +/* Creates a fd implementing our own deterministic /dev/urandom */ +int urandom_create_fd(); + +/* Returns that fd, or -1 if not created */ +int urandom_get_fd(); + +/* Creates a FILE* stream containing the above fd */ +FILE* urandom_create_file(); + +/* Returns the /dev/urandom FILE* stream */ +FILE* urandom_get_file(); + +} + +#endif diff --git a/src/library/fileio/posixiowrappers.cpp b/src/library/fileio/posixiowrappers.cpp index c422b6d9..4725837d 100644 --- a/src/library/fileio/posixiowrappers.cpp +++ b/src/library/fileio/posixiowrappers.cpp @@ -23,6 +23,7 @@ #include "../hook.h" #include "SaveFileList.h" #include "FileHandleList.h" +#include "URandom.h" #include "../GlobalState.h" #include "../inputs/jsdev.h" #include "../inputs/evdev.h" @@ -75,27 +76,7 @@ int open (const char *file, int oflag, ...) int fd = 0; if ((strcmp(file, "/dev/urandom") == 0) || (strcmp(file, "/dev/random") == 0)) { - if (SaveFileList::getSaveFileFd(file) == 0) { - /* Create a file with memory storage (reusing the savefile code), - * and fill it with values from the initial time, so that, for - * games that use it as PRNG seed, tweaking the initial time will - * change the seed value. - */ - fd = SaveFileList::openSaveFile(file, O_RDWR | O_TRUNC); - - time_t tsec = static_cast(shared_config.initial_time_sec); - char* datestr = asctime(gmtime(&tsec)); - debuglogstdio(LCF_FILEIO, "Creating fake %s with %s", file, datestr); - - write(fd, datestr, strlen(datestr)); - char buf[256]; - sprintf(buf, "%0*d", 255, 0); - write(fd, buf, 255); - lseek(fd, 0, SEEK_SET); - } - else { - fd = SaveFileList::openSaveFile(file, oflag); - } + return urandom_create_fd(); } else if (strcmp(file, "/proc/uptime") == 0) { @@ -175,26 +156,7 @@ int open64 (const char *file, int oflag, ...) int fd = 0; if ((strcmp(file, "/dev/urandom") == 0) || (strcmp(file, "/dev/random") == 0)) { - if (SaveFileList::getSaveFileFd(file) == 0) { - /* Create a file with memory storage (reusing the savefile code), - * and fill it with values from the initial time, so that, for - * games that use it as PRNG seed, tweaking the initial time will - * change the seed value. - */ - fd = SaveFileList::openSaveFile(file, O_RDWR | O_TRUNC); - - time_t tsec = static_cast(shared_config.initial_time_sec); - char* datestr = asctime(gmtime(&tsec)); - debuglogstdio(LCF_FILEIO, "Creating fake %s with %s", file, datestr); - write(fd, datestr, strlen(datestr)); - char buf[256]; - sprintf(buf, "%0*d", 255, 0); - write(fd, buf, 255); - lseek(fd, 0, SEEK_SET); - } - else { - fd = SaveFileList::openSaveFile(file, oflag); - } + return urandom_create_fd(); } else if (strcmp(file, "/proc/uptime") == 0) { @@ -391,6 +353,11 @@ int close (int fd) debuglogstdio(LCF_FILEIO, "%s call", __func__); + /* Check for urandom */ + if (urandom_get_fd() == fd) { + return 0; + } + /* Check if we must actually close the file */ bool doClose = FileHandleList::closeFile(fd); diff --git a/src/library/fileio/stdiowrappers.cpp b/src/library/fileio/stdiowrappers.cpp index 6e2ae822..193c92dc 100644 --- a/src/library/fileio/stdiowrappers.cpp +++ b/src/library/fileio/stdiowrappers.cpp @@ -24,6 +24,7 @@ #include "SaveFileList.h" #include "FileHandleList.h" #include "../GlobalState.h" +#include "URandom.h" namespace libtas { @@ -51,26 +52,7 @@ FILE *fopen (const char *filename, const char *modes) FILE* f = nullptr; if ((strcmp(filename, "/dev/urandom") == 0) || (strcmp(filename, "/dev/random") == 0)) { - if (SaveFileList::getSaveFileFd(filename) == 0) { - /* Create a file with memory storage (reusing the savefile code), - * and fill it with values from the initial time, so that, for - * games that use it as PRNG seed, tweaking the initial time will - * change the seed value. - */ - f = SaveFileList::openSaveFile(filename, "w"); - - time_t tsec = static_cast(shared_config.initial_time_sec); - char* datestr = asctime(gmtime(&tsec)); - debuglogstdio(LCF_FILEIO, "Creating fake %s with %s", filename, datestr); - fwrite(datestr, sizeof(char), strlen(datestr), f); - char buf[256]; - sprintf(buf, "%0*d", 255, 0); - fwrite(buf, sizeof(char), 255, f); - fseek(f, 0, SEEK_SET); - } - else { - f = SaveFileList::openSaveFile(filename, modes); - } + return urandom_create_file(); } else if (strcmp(filename, "/proc/uptime") == 0) { @@ -132,26 +114,7 @@ FILE *fopen64 (const char *filename, const char *modes) FILE* f = nullptr; if ((strcmp(filename, "/dev/urandom") == 0) || (strcmp(filename, "/dev/random") == 0)) { - if (SaveFileList::getSaveFileFd(filename) == 0) { - /* Create a file with memory storage (reusing the savefile code), - * and fill it with values from the initial time, so that, for - * games that use it as PRNG seed, tweaking the initial time will - * change the seed value. - */ - f = SaveFileList::openSaveFile(filename, "w"); - - time_t tsec = static_cast(shared_config.initial_time_sec); - char* datestr = asctime(gmtime(&tsec)); - debuglogstdio(LCF_FILEIO, "Creating fake %s with %s", filename, datestr); - fwrite(datestr, sizeof(char), strlen(datestr), f); - char buf[256]; - sprintf(buf, "%0*d", 255, 0); - fwrite(buf, sizeof(char), 255, f); - fseek(f, 0, SEEK_SET); - } - else { - f = SaveFileList::openSaveFile(filename, modes); - } + return urandom_create_file(); } else if (strcmp(filename, "/proc/uptime") == 0) { @@ -207,6 +170,11 @@ int fclose (FILE *stream) DEBUGLOGCALL(LCF_FILEIO); + /* Check for urandom */ + if (urandom_get_file() == stream) { + return 0; + } + /* Check if we must actually close the file */ bool doClose = FileHandleList::closeFile(fileno(stream));