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 }