diff options
Diffstat (limited to 'ipc/chromium/src/base/set_process_title_linux.cc')
-rw-r--r-- | ipc/chromium/src/base/set_process_title_linux.cc | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/ipc/chromium/src/base/set_process_title_linux.cc b/ipc/chromium/src/base/set_process_title_linux.cc new file mode 100644 index 0000000000..fed166a524 --- /dev/null +++ b/ipc/chromium/src/base/set_process_title_linux.cc @@ -0,0 +1,206 @@ +// Copyright 2009 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file implements BSD-style setproctitle() for Linux. +// It is written such that it can easily be compiled outside Chromium. +// +// (This copy has been modified for use in the Mozilla codebase.) +// +// The Linux kernel sets up two locations in memory to pass arguments and +// environment variables to processes. First, there are two char* arrays stored +// one after another: argv and environ. A pointer to argv is passed to main(), +// while glibc sets the global variable |environ| to point at the latter. Both +// of these arrays are terminated by a null pointer; the environment array is +// also followed by some empty space to allow additional variables to be added. +// +// These arrays contain pointers to a second location in memory, where the +// strings themselves are stored one after another: first all the arguments, +// then the environment variables. +// +// When the kernel reads the command line arguments for a process, it looks at +// the range of memory that it initially used for the argument list. If the +// terminating '\0' character is still where it expects, nothing further is +// done. If it has been overwritten, the kernel will scan up to the size of +// a page looking for another. +// +// Thus to change the process title, we must move any arguments and environment +// variables out of the way to make room for a potentially longer title, and +// then overwrite the memory pointed to by argv[0] with a single replacement +// string, making sure its size does not exceed the available space. +// +// See the following kernel commit for the details of the contract between +// kernel and setproctitle: +// https://github.com/torvalds/linux/commit/2954152298c37804dab49d630aa959625b50cf64 +// +// It is perhaps worth noting that patches to add a system call to Linux for +// this, like in BSD, have never made it in: this is the "official" way to do +// this on Linux. Presumably it is not in glibc due to some disagreement over +// this position within the glibc project, leaving applications caught in the +// middle. (Also, only a very few applications need or want this anyway.) + +#include "base/set_process_title_linux.h" + +#include "mozilla/UniquePtrExtensions.h" + +#include <fcntl.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <string> +#include <vector> + +extern char** environ; + +// g_orig_argv0 is the original process name found in argv[0]. +// It is set to a copy of argv[0] in setproctitle_init. It is nullptr if +// setproctitle_init was unsuccessful or not called. +static const char* g_orig_argv0 = nullptr; + +// Following pointers hold the initial argv/envp memory range. +// They are initialized in setproctitle_init and are used to overwrite the +// argv/envp memory range with a new process title to be read by the kernel. +// They are nullptr if setproctitle_init was unsuccessful or not called. +// Note that g_envp_start is not necessary because it is the same as g_argv_end. +static char* g_argv_start = nullptr; +static char* g_argv_end = nullptr; +static char* g_envp_end = nullptr; + +void setproctitle(const char* fmt, ...) { + va_list ap; + + // Sanity check before we try and set the process title. + // The BSD version allows a null fmt to restore the original title. + if (!g_orig_argv0 || !fmt) { + return; + } + + // The title can be up to the end of envp. + const size_t avail_size = g_envp_end - g_argv_start - 1; + + // Linux 4.18--5.2 have a bug where we can never set a process title + // shorter than the initial argv. Check if the bug exists in the current + // kernel on the first call of setproctitle. + static const bool buggy_kernel = [avail_size]() { + // Attempt to set an empty title. This will set cmdline to: + // "" (on Linux --4.17) + // "\0\0\0...\0\0\0.\0" (on Linux 4.18--5.2) + // "\0" (on Linux 5.3--) + memset(g_argv_start, 0, avail_size + 1); + g_argv_end[-1] = '.'; + + mozilla::UniqueFileHandle fd( + open("/proc/self/cmdline", O_RDONLY | O_CLOEXEC)); + if (!fd) { + return false; + } + + // We just want to see if there are at least 2 bytes in the file; + // we don't need to read the whole contents. Short reads probably + // aren't possible given how this procfs node is implemented, but + // it's not much more code to handle it anyway. + char buf[2]; + ssize_t total_read = 0; + while (total_read < 2) { + ssize_t rd = read(fd.get(), buf, 2); + if (rd <= 0) { + return false; + } + total_read += rd; + } + return true; + }(); + + memset(g_argv_start, 0, avail_size + 1); + + size_t size; + va_start(ap, fmt); + if (fmt[0] == '-') { + size = vsnprintf(g_argv_start, avail_size, &fmt[1], ap); + } else { + size = snprintf(g_argv_start, avail_size, "%s ", g_orig_argv0); + if (size < avail_size) { + size += vsnprintf(&g_argv_start[size], avail_size - size, fmt, ap); + } + } + va_end(ap); + + // Kernel looks for a null terminator instead of the initial argv space + // when the end of the space is not terminated with a null. + // https://github.com/torvalds/linux/commit/d26d0cd97c88eb1a5704b42e41ab443406807810 + // + // If the length of the new title is shorter than the original argv space, + // set the last byte of the space to an arbitrary non-null character to tell + // the kernel that setproctitle was called. + // + // On buggy kernels we can never make the process title shorter than the + // initial argv. In that case, just leave the remaining bytes filled with + // null characters. + const size_t argv_size = g_argv_end - g_argv_start - 1; + if (!buggy_kernel && size < argv_size) { + g_argv_end[-1] = '.'; + } +} + +// A version of this built into glibc would not need this function, since +// it could stash the argv pointer in __libc_start_main(). But we need it. +void setproctitle_init(char** main_argv) { + static bool init_called = false; + if (init_called) { + return; + } + init_called = true; + + if (!main_argv) { + return; + } + + // Verify that the memory layout matches expectation. + char** const argv = main_argv; + char* argv_start = argv[0]; + char* p = argv_start; + for (size_t i = 0; argv[i]; ++i) { + if (p != argv[i]) { + return; + } + p += strlen(p) + 1; + } + char* argv_end = p; + size_t environ_size = 0; + for (size_t i = 0; environ[i]; ++i, ++environ_size) { + if (p != environ[i]) { + return; + } + p += strlen(p) + 1; + } + char* envp_end = p; + + // Copy the arg and env strings into the heap. Leak Sanitizer + // doesn't seem to object to these strdup()s; if it ever does, we + // can always ensure the pointers are reachable from globals or add + // a suppresion for this function. + // + // Note that Chromium's version of this code didn't copy the + // arguments; this is probably because they access args via the + // CommandLine class, which copies into a std::vector<std::string>, + // but in general that's not a safe assumption for Gecko. + for (size_t i = 0; argv[i]; ++i) { + argv[i] = strdup(argv[i]); + } + for (size_t i = 0; environ[i]; ++i) { + environ[i] = strdup(environ[i]); + } + + if (!argv[0]) { + return; + } + + g_orig_argv0 = argv[0]; + g_argv_start = argv_start; + g_argv_end = argv_end; + g_envp_end = envp_end; +} |