OLD | NEW |
1 // Copyright 2014 The Crashpad Authors. All rights reserved. | 1 // Copyright 2014 The Crashpad Authors. All rights reserved. |
2 // | 2 // |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
6 // | 6 // |
7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
8 // | 8 // |
9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
13 // limitations under the License. | 13 // limitations under the License. |
14 | 14 |
15 #include "client/crashpad_client.h" | 15 #include "client/crashpad_client.h" |
16 | 16 |
17 #include <mach/mach.h> | 17 #include <mach/mach.h> |
18 #include <sys/wait.h> | 18 #include <sys/wait.h> |
19 #include <unistd.h> | 19 #include <unistd.h> |
20 | 20 |
21 #include "base/logging.h" | 21 #include "base/logging.h" |
| 22 #include "base/mac/mach_logging.h" |
22 #include "base/posix/eintr_wrapper.h" | 23 #include "base/posix/eintr_wrapper.h" |
23 #include "base/strings/stringprintf.h" | 24 #include "base/strings/stringprintf.h" |
24 #include "util/mach/child_port_handshake.h" | 25 #include "util/mach/child_port_handshake.h" |
25 #include "util/mach/exception_ports.h" | 26 #include "util/mach/exception_ports.h" |
26 #include "util/mach/mach_extensions.h" | 27 #include "util/mach/mach_extensions.h" |
| 28 #include "util/misc/implicit_cast.h" |
27 #include "util/posix/close_multiple.h" | 29 #include "util/posix/close_multiple.h" |
28 | 30 |
29 namespace crashpad { | 31 namespace crashpad { |
30 | 32 |
31 namespace { | 33 namespace { |
32 | 34 |
33 std::string FormatArgumentString(const std::string& name, | 35 std::string FormatArgumentString(const std::string& name, |
34 const std::string& value) { | 36 const std::string& value) { |
35 return base::StringPrintf("--%s=%s", name.c_str(), value.c_str()); | 37 return base::StringPrintf("--%s=%s", name.c_str(), value.c_str()); |
36 } | 38 } |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
70 // so AND them with ExcMaskValid(). EXC_MASK_CRASH is always supported. | 72 // so AND them with ExcMaskValid(). EXC_MASK_CRASH is always supported. |
71 bool SetCrashExceptionPorts(exception_handler_t exception_handler) { | 73 bool SetCrashExceptionPorts(exception_handler_t exception_handler) { |
72 ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); | 74 ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); |
73 return exception_ports.SetExceptionPort( | 75 return exception_ports.SetExceptionPort( |
74 (EXC_MASK_CRASH | EXC_MASK_RESOURCE | EXC_MASK_GUARD) & ExcMaskValid(), | 76 (EXC_MASK_CRASH | EXC_MASK_RESOURCE | EXC_MASK_GUARD) & ExcMaskValid(), |
75 exception_handler, | 77 exception_handler, |
76 EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, | 78 EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, |
77 MACHINE_THREAD_STATE); | 79 MACHINE_THREAD_STATE); |
78 } | 80 } |
79 | 81 |
| 82 //! \brief Starts a Crashpad handler. |
| 83 class HandlerStarter final { |
| 84 public: |
| 85 //! \brief Starts a Crashpad handler. |
| 86 //! |
| 87 //! All parameters are as in CrashpadClient::StartHandler(). |
| 88 //! |
| 89 //! \return On success, a send right to the Crashpad handler that has been |
| 90 //! started. On failure, `MACH_PORT_NULL` with a message logged. |
| 91 static base::mac::ScopedMachSendRight Start( |
| 92 const base::FilePath& handler, |
| 93 const base::FilePath& database, |
| 94 const std::string& url, |
| 95 const std::map<std::string, std::string>& annotations, |
| 96 const std::vector<std::string>& arguments) { |
| 97 base::mac::ScopedMachReceiveRight receive_right( |
| 98 NewMachPort(MACH_PORT_RIGHT_RECEIVE)); |
| 99 if (receive_right == kMachPortNull) { |
| 100 return base::mac::ScopedMachSendRight(); |
| 101 } |
| 102 |
| 103 mach_port_t port; |
| 104 mach_msg_type_name_t right_type; |
| 105 kern_return_t kr = mach_port_extract_right(mach_task_self(), |
| 106 receive_right.get(), |
| 107 MACH_MSG_TYPE_MAKE_SEND, |
| 108 &port, |
| 109 &right_type); |
| 110 if (kr != KERN_SUCCESS) { |
| 111 MACH_LOG(ERROR, kr) << "mach_port_extract_right"; |
| 112 return base::mac::ScopedMachSendRight(); |
| 113 } |
| 114 base::mac::ScopedMachSendRight send_right(port); |
| 115 DCHECK_EQ(port, receive_right.get()); |
| 116 DCHECK_EQ(right_type, |
| 117 implicit_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND)); |
| 118 |
| 119 if (!CommonStart(handler, |
| 120 database, |
| 121 url, |
| 122 annotations, |
| 123 arguments, |
| 124 receive_right.Pass())) { |
| 125 return base::mac::ScopedMachSendRight(); |
| 126 } |
| 127 |
| 128 return send_right; |
| 129 } |
| 130 |
| 131 private: |
| 132 static bool CommonStart(const base::FilePath& handler, |
| 133 const base::FilePath& database, |
| 134 const std::string& url, |
| 135 const std::map<std::string, std::string>& annotations, |
| 136 const std::vector<std::string>& arguments, |
| 137 base::mac::ScopedMachReceiveRight receive_right) { |
| 138 // Set up the arguments for execve() first. These aren’t needed until |
| 139 // execve() is called, but it’s dangerous to do this in a child process |
| 140 // after fork(). |
| 141 ChildPortHandshake child_port_handshake; |
| 142 base::ScopedFD server_write_fd = child_port_handshake.ServerWriteFD(); |
| 143 |
| 144 // Use handler as argv[0], followed by arguments directed by this method’s |
| 145 // parameters and a --handshake-fd argument. |arguments| are added first so |
| 146 // that if it erroneously contains an argument such as --url, the actual |
| 147 // |url| argument passed to this method will supersede it. In normal |
| 148 // command-line processing, the last parameter wins in the case of a |
| 149 // conflict. |
| 150 std::vector<std::string> argv(1, handler.value()); |
| 151 argv.reserve(1 + arguments.size() + 2 + annotations.size() + 1); |
| 152 for (const std::string& argument : arguments) { |
| 153 argv.push_back(argument); |
| 154 } |
| 155 if (!database.value().empty()) { |
| 156 argv.push_back(FormatArgumentString("database", database.value())); |
| 157 } |
| 158 if (!url.empty()) { |
| 159 argv.push_back(FormatArgumentString("url", url)); |
| 160 } |
| 161 for (const auto& kv : annotations) { |
| 162 argv.push_back( |
| 163 FormatArgumentString("annotation", kv.first + '=' + kv.second)); |
| 164 } |
| 165 argv.push_back(FormatArgumentInt("handshake-fd", server_write_fd.get())); |
| 166 |
| 167 const char* handler_c = handler.value().c_str(); |
| 168 |
| 169 // argv_c contains const char* pointers and is terminated by nullptr. argv |
| 170 // is required because the pointers in argv_c need to point somewhere, and |
| 171 // they can’t point to temporaries such as those returned by |
| 172 // FormatArgumentString(). |
| 173 std::vector<const char*> argv_c; |
| 174 argv_c.reserve(argv.size() + 1); |
| 175 for (const std::string& argument : argv) { |
| 176 argv_c.push_back(argument.c_str()); |
| 177 } |
| 178 argv_c.push_back(nullptr); |
| 179 |
| 180 // Double-fork(). The three processes involved are parent, child, and |
| 181 // grandchild. The grandchild will become the handler process. The child |
| 182 // exits immediately after spawning the grandchild, so the grandchild |
| 183 // becomes an orphan and its parent process ID becomes 1. This relieves the |
| 184 // parent and child of the responsibility for reaping the grandchild with |
| 185 // waitpid() or similar. The handler process is expected to outlive the |
| 186 // parent process, so the parent shouldn’t be concerned with reaping it. |
| 187 // This approach means that accidental early termination of the handler |
| 188 // process will not result in a zombie process. |
| 189 pid_t pid = fork(); |
| 190 if (pid < 0) { |
| 191 PLOG(ERROR) << "fork"; |
| 192 return false; |
| 193 } |
| 194 |
| 195 if (pid == 0) { |
| 196 // Child process. |
| 197 |
| 198 // Call setsid(), creating a new process group and a new session, both led |
| 199 // by this process. The new process group has no controlling terminal. |
| 200 // This disconnects it from signals generated by the parent process’ |
| 201 // terminal. |
| 202 // |
| 203 // setsid() is done in the child instead of the grandchild so that the |
| 204 // grandchild will not be a session leader. If it were a session leader, |
| 205 // an accidental open() of a terminal device without O_NOCTTY would make |
| 206 // that terminal the controlling terminal. |
| 207 // |
| 208 // It’s not desirable for the handler to have a controlling terminal. The |
| 209 // handler monitors clients on its own and manages its own lifetime, |
| 210 // exiting when it loses all clients and when it deems it appropraite to |
| 211 // do so. It may serve clients in different process groups or sessions |
| 212 // than its original client, and receiving signals intended for its |
| 213 // original client’s process group could be harmful in that case. |
| 214 PCHECK(setsid() != -1) << "setsid"; |
| 215 |
| 216 pid = fork(); |
| 217 if (pid < 0) { |
| 218 PLOG(FATAL) << "fork"; |
| 219 } |
| 220 |
| 221 if (pid > 0) { |
| 222 // Child process. |
| 223 |
| 224 // _exit() instead of exit(), because fork() was called. |
| 225 _exit(EXIT_SUCCESS); |
| 226 } |
| 227 |
| 228 // Grandchild process. |
| 229 |
| 230 CloseMultipleNowOrOnExec(STDERR_FILENO + 1, server_write_fd.get()); |
| 231 |
| 232 // &argv_c[0] is a pointer to a pointer to const char data, but because of |
| 233 // how C (not C++) works, execvp() wants a pointer to a const pointer to |
| 234 // char data. It modifies neither the data nor the pointers, so the |
| 235 // const_cast is safe. |
| 236 execvp(handler_c, const_cast<char* const*>(&argv_c[0])); |
| 237 PLOG(FATAL) << "execvp " << handler_c; |
| 238 } |
| 239 |
| 240 // Parent process. |
| 241 |
| 242 // Close the write side of the pipe, so that the handler process is the only |
| 243 // process that can write to it. |
| 244 server_write_fd.reset(); |
| 245 |
| 246 // waitpid() for the child, so that it does not become a zombie process. The |
| 247 // child normally exits quickly. |
| 248 int status; |
| 249 pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0)); |
| 250 PCHECK(wait_pid != -1) << "waitpid"; |
| 251 DCHECK_EQ(wait_pid, pid); |
| 252 |
| 253 if (WIFSIGNALED(status)) { |
| 254 LOG(WARNING) << "intermediate process: signal " << WTERMSIG(status); |
| 255 } else if (!WIFEXITED(status)) { |
| 256 DLOG(WARNING) << "intermediate process: unknown termination " << status; |
| 257 } else if (WEXITSTATUS(status) != EXIT_SUCCESS) { |
| 258 LOG(WARNING) << "intermediate process: exit status " |
| 259 << WEXITSTATUS(status); |
| 260 } |
| 261 |
| 262 // Rendezvous with the handler running in the grandchild process. |
| 263 if (!child_port_handshake.RunClient(receive_right.get(), |
| 264 MACH_MSG_TYPE_MOVE_RECEIVE)) { |
| 265 return false; |
| 266 } |
| 267 |
| 268 ignore_result(receive_right.release()); |
| 269 return true; |
| 270 } |
| 271 |
| 272 DISALLOW_IMPLICIT_CONSTRUCTORS(HandlerStarter); |
| 273 }; |
| 274 |
80 } // namespace | 275 } // namespace |
81 | 276 |
82 CrashpadClient::CrashpadClient() | 277 CrashpadClient::CrashpadClient() |
83 : exception_port_() { | 278 : exception_port_() { |
84 } | 279 } |
85 | 280 |
86 CrashpadClient::~CrashpadClient() { | 281 CrashpadClient::~CrashpadClient() { |
87 } | 282 } |
88 | 283 |
89 bool CrashpadClient::StartHandler( | 284 bool CrashpadClient::StartHandler( |
90 const base::FilePath& handler, | 285 const base::FilePath& handler, |
91 const base::FilePath& database, | 286 const base::FilePath& database, |
92 const std::string& url, | 287 const std::string& url, |
93 const std::map<std::string, std::string>& annotations, | 288 const std::map<std::string, std::string>& annotations, |
94 const std::vector<std::string>& arguments) { | 289 const std::vector<std::string>& arguments) { |
95 DCHECK(!exception_port_.is_valid()); | 290 DCHECK(!exception_port_.is_valid()); |
96 | 291 |
97 // Set up the arguments for execve() first. These aren’t needed until execve() | 292 exception_port_ = HandlerStarter::Start( |
98 // is called, but it’s dangerous to do this in a child process after fork(). | 293 handler, database, url, annotations, arguments); |
99 ChildPortHandshake child_port_handshake; | 294 if (!exception_port_.is_valid()) { |
100 base::ScopedFD client_read_fd = child_port_handshake.ClientReadFD(); | |
101 | |
102 // Use handler as argv[0], followed by arguments directed by this method’s | |
103 // parameters and a --handshake-fd argument. |arguments| are added first so | |
104 // that if it erroneously contains an argument such as --url, the actual |url| | |
105 // argument passed to this method will supersede it. In normal command-line | |
106 // processing, the last parameter wins in the case of a conflict. | |
107 std::vector<std::string> argv(1, handler.value()); | |
108 argv.reserve(1 + arguments.size() + 2 + annotations.size() + 1); | |
109 for (const std::string& argument : arguments) { | |
110 argv.push_back(argument); | |
111 } | |
112 if (!database.value().empty()) { | |
113 argv.push_back(FormatArgumentString("database", database.value())); | |
114 } | |
115 if (!url.empty()) { | |
116 argv.push_back(FormatArgumentString("url", url)); | |
117 } | |
118 for (const auto& kv : annotations) { | |
119 argv.push_back( | |
120 FormatArgumentString("annotation", kv.first + '=' + kv.second)); | |
121 } | |
122 argv.push_back(FormatArgumentInt("handshake-fd", client_read_fd.get())); | |
123 | |
124 // argv_c contains const char* pointers and is terminated by nullptr. argv | |
125 // is required because the pointers in argv_c need to point somewhere, and | |
126 // they can’t point to temporaries such as those returned by | |
127 // FormatArgumentString(). | |
128 std::vector<const char*> argv_c; | |
129 argv_c.reserve(argv.size() + 1); | |
130 for (const std::string& argument : argv) { | |
131 argv_c.push_back(argument.c_str()); | |
132 } | |
133 argv_c.push_back(nullptr); | |
134 | |
135 // Double-fork(). The three processes involved are parent, child, and | |
136 // grandchild. The grandchild will become the handler process. The child exits | |
137 // immediately after spawning the grandchild, so the grandchild becomes an | |
138 // orphan and its parent process ID becomes 1. This relieves the parent and | |
139 // child of the responsibility for reaping the grandchild with waitpid() or | |
140 // similar. The handler process is expected to outlive the parent process, so | |
141 // the parent shouldn’t be concerned with reaping it. This approach means that | |
142 // accidental early termination of the handler process will not result in a | |
143 // zombie process. | |
144 pid_t pid = fork(); | |
145 if (pid < 0) { | |
146 PLOG(ERROR) << "fork"; | |
147 return false; | 295 return false; |
148 } | 296 } |
149 | 297 |
150 if (pid == 0) { | 298 return true; |
151 // Child process. | |
152 | |
153 // Call setsid(), creating a new process group and a new session, both led | |
154 // by this process. The new process group has no controlling terminal. This | |
155 // disconnects it from signals generated by the parent process’ terminal. | |
156 // | |
157 // setsid() is done in the child instead of the grandchild so that the | |
158 // grandchild will not be a session leader. If it were a session leader, an | |
159 // accidental open() of a terminal device without O_NOCTTY would make that | |
160 // terminal the controlling terminal. | |
161 // | |
162 // It’s not desirable for the handler to have a controlling terminal. The | |
163 // handler monitors clients on its own and manages its own lifetime, exiting | |
164 // when it loses all clients and when it deems it appropraite to do so. It | |
165 // may serve clients in different process groups or sessions than its | |
166 // original client, and receiving signals intended for its original client’s | |
167 // process group could be harmful in that case. | |
168 PCHECK(setsid() != -1) << "setsid"; | |
169 | |
170 pid = fork(); | |
171 if (pid < 0) { | |
172 PLOG(FATAL) << "fork"; | |
173 } | |
174 | |
175 if (pid > 0) { | |
176 // Child process. | |
177 | |
178 // _exit() instead of exit(), because fork() was called. | |
179 _exit(EXIT_SUCCESS); | |
180 } | |
181 | |
182 // Grandchild process. | |
183 | |
184 CloseMultipleNowOrOnExec(STDERR_FILENO + 1, client_read_fd.get()); | |
185 | |
186 // &argv_c[0] is a pointer to a pointer to const char data, but because of | |
187 // how C (not C++) works, execvp() wants a pointer to a const pointer to | |
188 // char data. It modifies neither the data nor the pointers, so the | |
189 // const_cast is safe. | |
190 execvp(handler.value().c_str(), const_cast<char* const*>(&argv_c[0])); | |
191 PLOG(FATAL) << "execvp " << handler.value(); | |
192 } | |
193 | |
194 // Parent process. | |
195 | |
196 client_read_fd.reset(); | |
197 | |
198 // waitpid() for the child, so that it does not become a zombie process. The | |
199 // child normally exits quickly. | |
200 int status; | |
201 pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0)); | |
202 PCHECK(wait_pid != -1) << "waitpid"; | |
203 DCHECK_EQ(wait_pid, pid); | |
204 | |
205 if (WIFSIGNALED(status)) { | |
206 LOG(WARNING) << "intermediate process: signal " << WTERMSIG(status); | |
207 } else if (!WIFEXITED(status)) { | |
208 DLOG(WARNING) << "intermediate process: unknown termination " << status; | |
209 } else if (WEXITSTATUS(status) != EXIT_SUCCESS) { | |
210 LOG(WARNING) << "intermediate process: exit status " << WEXITSTATUS(status); | |
211 } | |
212 | |
213 // Rendezvous with the handler running in the grandchild process. | |
214 exception_port_.reset(child_port_handshake.RunServer( | |
215 ChildPortHandshake::PortRightType::kSendRight)); | |
216 | |
217 return exception_port_.is_valid(); | |
218 } | 299 } |
219 | 300 |
220 bool CrashpadClient::UseHandler() { | 301 bool CrashpadClient::UseHandler() { |
221 DCHECK(exception_port_.is_valid()); | 302 DCHECK(exception_port_.is_valid()); |
222 | 303 |
223 return SetCrashExceptionPorts(exception_port_.get()); | 304 return SetCrashExceptionPorts(exception_port_.get()); |
224 } | 305 } |
225 | 306 |
226 // static | 307 // static |
227 void CrashpadClient::UseSystemDefaultHandler() { | 308 void CrashpadClient::UseSystemDefaultHandler() { |
228 base::mac::ScopedMachSendRight | 309 base::mac::ScopedMachSendRight |
229 system_crash_reporter_handler(SystemCrashReporterHandler()); | 310 system_crash_reporter_handler(SystemCrashReporterHandler()); |
230 | 311 |
231 // Proceed even if SystemCrashReporterHandler() failed, setting MACH_PORT_NULL | 312 // Proceed even if SystemCrashReporterHandler() failed, setting MACH_PORT_NULL |
232 // to clear the current exception ports. | 313 // to clear the current exception ports. |
233 if (!SetCrashExceptionPorts(system_crash_reporter_handler.get())) { | 314 if (!SetCrashExceptionPorts(system_crash_reporter_handler.get())) { |
234 SetCrashExceptionPorts(MACH_PORT_NULL); | 315 SetCrashExceptionPorts(MACH_PORT_NULL); |
235 } | 316 } |
236 } | 317 } |
237 | 318 |
238 } // namespace crashpad | 319 } // namespace crashpad |
OLD | NEW |