tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

set_process_title_linux.cc (8704B)


      1 // Copyright 2009 The Chromium Authors
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 // This file implements BSD-style setproctitle() for Linux.
      6 // It is written such that it can easily be compiled outside Chromium.
      7 //
      8 // (This copy has been modified for use in the Mozilla codebase.)
      9 //
     10 // The Linux kernel sets up two locations in memory to pass arguments and
     11 // environment variables to processes. First, there are two char* arrays stored
     12 // one after another: argv and environ. A pointer to argv is passed to main(),
     13 // while glibc sets the global variable |environ| to point at the latter. Both
     14 // of these arrays are terminated by a null pointer; the environment array is
     15 // also followed by some empty space to allow additional variables to be added.
     16 //
     17 // These arrays contain pointers to a second location in memory, where the
     18 // strings themselves are stored one after another: first all the arguments,
     19 // then the environment variables.
     20 //
     21 // When the kernel reads the command line arguments for a process, it looks at
     22 // the range of memory that it initially used for the argument list. If the
     23 // terminating '\0' character is still where it expects, nothing further is
     24 // done. If it has been overwritten, the kernel will scan up to the size of
     25 // a page looking for another.
     26 //
     27 // Thus to change the process title, we must move any arguments and environment
     28 // variables out of the way to make room for a potentially longer title, and
     29 // then overwrite the memory pointed to by argv[0] with a single replacement
     30 // string, making sure its size does not exceed the available space.
     31 //
     32 // See the following kernel commit for the details of the contract between
     33 // kernel and setproctitle:
     34 // https://github.com/torvalds/linux/commit/2954152298c37804dab49d630aa959625b50cf64
     35 //
     36 // It is perhaps worth noting that patches to add a system call to Linux for
     37 // this, like in BSD, have never made it in: this is the "official" way to do
     38 // this on Linux. Presumably it is not in glibc due to some disagreement over
     39 // this position within the glibc project, leaving applications caught in the
     40 // middle. (Also, only a very few applications need or want this anyway.)
     41 
     42 #include "base/set_process_title_linux.h"
     43 
     44 #include "mozilla/UniquePtrExtensions.h"
     45 
     46 #include <fcntl.h>
     47 #include <stdarg.h>
     48 #include <stddef.h>
     49 #include <stdio.h>
     50 #include <string.h>
     51 #include <unistd.h>
     52 
     53 #include <string>
     54 #include <vector>
     55 
     56 extern char** environ;
     57 
     58 // g_orig_argv0 is the original process name found in argv[0].
     59 // It is set to a copy of argv[0] in setproctitle_init. It is nullptr if
     60 // setproctitle_init was unsuccessful or not called.
     61 static const char* g_orig_argv0 = nullptr;
     62 
     63 // Following pointers hold the initial argv/envp memory range.
     64 // They are initialized in setproctitle_init and are used to overwrite the
     65 // argv/envp memory range with a new process title to be read by the kernel.
     66 // They are nullptr if setproctitle_init was unsuccessful or not called.
     67 // Note that g_envp_start is not necessary because it is the same as g_argv_end.
     68 static char* g_argv_start = nullptr;
     69 static char* g_argv_end = nullptr;
     70 static char* g_envp_end = nullptr;
     71 
     72 void setproctitle(const char* fmt, ...) {
     73  va_list ap;
     74 
     75  // Sanity check before we try and set the process title.
     76  // The BSD version allows a null fmt to restore the original title.
     77  if (!g_orig_argv0 || !fmt) {
     78    return;
     79  }
     80 
     81  // The title can be up to the end of envp.
     82  const size_t avail_size = g_envp_end - g_argv_start - 1;
     83 
     84  // Linux 4.18--5.2 have a bug where we can never set a process title
     85  // shorter than the initial argv. Check if the bug exists in the current
     86  // kernel on the first call of setproctitle.
     87  static const bool buggy_kernel = [avail_size]() {
     88    // Attempt to set an empty title. This will set cmdline to:
     89    // ""                   (on Linux --4.17)
     90    // "\0\0\0...\0\0\0.\0" (on Linux 4.18--5.2)
     91    // "\0"                 (on Linux 5.3--)
     92    memset(g_argv_start, 0, avail_size + 1);
     93    g_argv_end[-1] = '.';
     94 
     95    mozilla::UniqueFileHandle fd(
     96        open("/proc/self/cmdline", O_RDONLY | O_CLOEXEC));
     97    if (!fd) {
     98      return false;
     99    }
    100 
    101    // We just want to see if there are at least 2 bytes in the file;
    102    // we don't need to read the whole contents.  Short reads probably
    103    // aren't possible given how this procfs node is implemented, but
    104    // it's not much more code to handle it anyway.
    105    char buf[2];
    106    ssize_t total_read = 0;
    107    while (total_read < 2) {
    108      ssize_t rd = read(fd.get(), buf, 2);
    109      if (rd <= 0) {
    110        return false;
    111      }
    112      total_read += rd;
    113    }
    114    return true;
    115  }();
    116 
    117  memset(g_argv_start, 0, avail_size + 1);
    118 
    119  size_t size;
    120  va_start(ap, fmt);
    121  if (fmt[0] == '-') {
    122    size = vsnprintf(g_argv_start, avail_size, &fmt[1], ap);
    123  } else {
    124    size = snprintf(g_argv_start, avail_size, "%s ", g_orig_argv0);
    125    if (size < avail_size) {
    126      size += vsnprintf(&g_argv_start[size], avail_size - size, fmt, ap);
    127    }
    128  }
    129  va_end(ap);
    130 
    131  // Kernel looks for a null terminator instead of the initial argv space
    132  // when the end of the space is not terminated with a null.
    133  // https://github.com/torvalds/linux/commit/d26d0cd97c88eb1a5704b42e41ab443406807810
    134  //
    135  // If the length of the new title is shorter than the original argv space,
    136  // set the last byte of the space to an arbitrary non-null character to tell
    137  // the kernel that setproctitle was called.
    138  //
    139  // On buggy kernels we can never make the process title shorter than the
    140  // initial argv. In that case, just leave the remaining bytes filled with
    141  // null characters.
    142  const size_t argv_size = g_argv_end - g_argv_start - 1;
    143  if (!buggy_kernel && size < argv_size) {
    144    g_argv_end[-1] = '.';
    145  }
    146 
    147  const size_t previous_size = g_argv_end - g_argv_start - 1;
    148  ssize_t need_to_save = static_cast<ssize_t>(size - previous_size);
    149 
    150  // The argv part has grown so there is less room for the environ part.
    151  // Selectively removing a few environment variables so this can fit.
    152  //
    153  // The goal is trying to make sure that the environment in the
    154  // /proc/PID/environ content for crashes is useful
    155  const char* kEnvSkip[] = {"HOME=", "LS_COLORS=", "PATH=", "XDG_DATA_DIRS="};
    156  const size_t kEnvElems = sizeof(kEnvSkip) / sizeof(kEnvSkip[0]);
    157 
    158  size_t environ_size = 0;
    159  for (size_t i = 0; environ[i]; ++i) {
    160    bool skip = false;
    161    const size_t var_size = strlen(environ[i]) + 1;
    162 
    163    for (size_t remI = 0; need_to_save > 0 && remI < kEnvElems; ++remI) {
    164      const char* thisEnv = kEnvSkip[remI];
    165      int diff = strncmp(environ[i], thisEnv, strlen(thisEnv));
    166      if (diff == 0) {
    167        need_to_save -= static_cast<ssize_t>(var_size);
    168        skip = true;
    169        break;
    170      }
    171    }
    172 
    173    if (skip) {
    174      continue;
    175    }
    176 
    177    char* env_start = g_argv_start + size + 1 + environ_size;
    178    if ((env_start + var_size) < g_envp_end) {
    179      const size_t var_size_copied =
    180          snprintf(env_start, var_size, "%s", environ[i]);
    181      environ_size += var_size_copied + 1 /* account for null */;
    182    }
    183  }
    184 }
    185 
    186 // A version of this built into glibc would not need this function, since
    187 // it could stash the argv pointer in __libc_start_main(). But we need it.
    188 void setproctitle_init(char** main_argv) {
    189  static bool init_called = false;
    190  if (init_called) {
    191    return;
    192  }
    193  init_called = true;
    194 
    195  if (!main_argv) {
    196    return;
    197  }
    198 
    199  // Verify that the memory layout matches expectation.
    200  char** const argv = main_argv;
    201  char* argv_start = argv[0];
    202  char* p = argv_start;
    203  for (size_t i = 0; argv[i]; ++i) {
    204    if (p != argv[i]) {
    205      return;
    206    }
    207    p += strlen(p) + 1;
    208  }
    209  char* argv_end = p;
    210  size_t environ_size = 0;
    211  for (size_t i = 0; environ[i]; ++i, ++environ_size) {
    212    if (p != environ[i]) {
    213      return;
    214    }
    215    p += strlen(p) + 1;
    216  }
    217  char* envp_end = p;
    218 
    219  // Copy the arg and env strings into the heap.  Leak Sanitizer
    220  // doesn't seem to object to these strdup()s; if it ever does, we
    221  // can always ensure the pointers are reachable from globals or add
    222  // a suppresion for this function.
    223  //
    224  // Note that Chromium's version of this code didn't copy the
    225  // arguments; this is probably because they access args via the
    226  // CommandLine class, which copies into a std::vector<std::string>,
    227  // but in general that's not a safe assumption for Gecko.
    228  for (size_t i = 0; argv[i]; ++i) {
    229    argv[i] = strdup(argv[i]);
    230  }
    231  for (size_t i = 0; environ[i]; ++i) {
    232    environ[i] = strdup(environ[i]);
    233  }
    234 
    235  if (!argv[0]) {
    236    return;
    237  }
    238 
    239  g_orig_argv0 = argv[0];
    240  g_argv_start = argv_start;
    241  g_argv_end = argv_end;
    242  g_envp_end = envp_end;
    243 }