// Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include #include "base/apple/mach_logging.h" #include "base/apple/scoped_mach_port.h" #include "tools/tool_support.h" #include "util/mach/bootstrap.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" #include "util/mach/symbolic_constants_mach.h" #include "util/mach/task_for_pid.h" #include "util/posix/drop_privileges.h" #include "util/stdlib/string_number_conversion.h" namespace crashpad { namespace { //! \brief Manages a pool of Mach send rights, deallocating all send rights upon //! destruction. //! //! This class effectively implements what a vector of //! base::apple::ScopedMachSendRight objects would be. //! //! The various “show” operations performed by this program display Mach ports //! by their names as they are known in this task. For this to be useful, rights //! to the same ports must have consistent names across successive calls. This //! cannot be guaranteed if the rights are deallocated as soon as they are used, //! because if that deallocation causes the task to lose its last right to a //! port, subsequently regaining a right to the same port would cause it to be //! known by a new name in this task. //! //! Instead of immediately deallocating send rights that are used for display, //! they can be added to this pool. The pool collects send rights, ensuring that //! they remain alive in this task, and that subsequent calls that obtain the //! same rights cause them to be known by the same name. All rights are //! deallocated upon destruction. class MachSendRightPool { public: MachSendRightPool() : send_rights_() { } MachSendRightPool(const MachSendRightPool&) = delete; MachSendRightPool& operator=(const MachSendRightPool&) = delete; ~MachSendRightPool() { for (mach_port_t send_right : send_rights_) { kern_return_t kr = mach_port_deallocate(mach_task_self(), send_right); MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) << "mach_port_deallocate"; } } //! \brief Adds a send right to the pool. //! //! \param[in] send_right The send right to be added. The pool object takes //! its own reference to the send right, which remains valid until the //! pool object is destroyed. The caller remains responsible for its //! reference to the send right. //! //! It is possible and in fact likely that one pool will wind up owning the //! same send right multiple times. This is acceptable, because send rights //! are reference-counted. void AddSendRight(mach_port_t send_right) { kern_return_t kr = mach_port_mod_refs(mach_task_self(), send_right, MACH_PORT_RIGHT_SEND, 1); MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_mod_refs"; send_rights_.push_back(send_right); } private: std::vector send_rights_; }; struct ExceptionHandlerDescription { ExceptionPorts::TargetType target_type; exception_mask_t mask; exception_behavior_t behavior; thread_state_flavor_t flavor; std::string handler; }; constexpr char kHandlerNull[] = "NULL"; constexpr char kHandlerBootstrapColon[] = "bootstrap:"; // Populates |description| based on a textual representation in // |handler_string_ro|, returning true on success and false on failure (parse // error). The --help string describes the format of |handler_string_ro|. // Briefly, it is a comma-separated string that allows the members of // |description| to be specified as "field=value". Values for "target" can be // "host", "task", or "thread"; values for "handler" are of the form // "bootstrap:service_name" where service_name will be looked up with the // bootstrap server; and values for the other fields are interpreted by // SymbolicConstantsMach. bool ParseHandlerString(const char* handler_string_ro, ExceptionHandlerDescription* description) { static constexpr char kTargetEquals[] = "target="; static constexpr char kMaskEquals[] = "mask="; static constexpr char kBehaviorEquals[] = "behavior="; static constexpr char kFlavorEquals[] = "flavor="; static constexpr char kHandlerEquals[] = "handler="; std::string handler_string(handler_string_ro); char* handler_string_c = &handler_string[0]; char* token; while ((token = strsep(&handler_string_c, ",")) != nullptr) { if (strncmp(token, kTargetEquals, strlen(kTargetEquals)) == 0) { const char* value = token + strlen(kTargetEquals); if (strcmp(value, "host") == 0) { description->target_type = ExceptionPorts::kTargetTypeHost; } else if (strcmp(value, "task") == 0) { description->target_type = ExceptionPorts::kTargetTypeTask; } else if (strcmp(value, "thread") == 0) { description->target_type = ExceptionPorts::kTargetTypeThread; } else { return false; } } else if (strncmp(token, kMaskEquals, strlen(kMaskEquals)) == 0) { const char* value = token + strlen(kMaskEquals); if (!StringToExceptionMask( value, kAllowFullName | kAllowShortName | kAllowNumber | kAllowOr, &description->mask)) { return false; } } else if (strncmp(token, kBehaviorEquals, strlen(kBehaviorEquals)) == 0) { const char* value = token + strlen(kBehaviorEquals); if (!StringToExceptionBehavior( value, kAllowFullName | kAllowShortName | kAllowNumber, &description->behavior)) { return false; } } else if (strncmp(token, kFlavorEquals, strlen(kFlavorEquals)) == 0) { const char* value = token + strlen(kFlavorEquals); if (!StringToThreadStateFlavor( value, kAllowFullName | kAllowShortName | kAllowNumber, &description->flavor)) { return false; } } else if (strncmp(token, kHandlerEquals, strlen(kHandlerEquals)) == 0) { const char* value = token + strlen(kHandlerEquals); if (strcmp(value, kHandlerNull) != 0 && strncmp(value, kHandlerBootstrapColon, strlen(kHandlerBootstrapColon)) != 0) { return false; } description->handler = std::string(value); } else { return false; } } return true; } // ShowExceptionPorts() shows handlers as numeric mach_port_t values, which are // opaque and meaningless on their own. ShowBootstrapService() can be used to // look up a service with the bootstrap server by name and show its mach_port_t // value, which can then be associated with handlers shown by // ShowExceptionPorts(). Any send rights obtained by this function are added to // |mach_send_right_pool|. void ShowBootstrapService(const std::string& service_name, MachSendRightPool* mach_send_right_pool) { base::apple::ScopedMachSendRight service_port(BootstrapLookUp(service_name)); if (service_port == kMachPortNull) { return; } mach_send_right_pool->AddSendRight(service_port.get()); printf("service %s %#x\n", service_name.c_str(), service_port.get()); } // Prints information about all exception ports known for |exception_ports|. If // |numeric| is true, all information is printed in numeric form, otherwise, it // will be converted to symbolic constants where possible by // SymbolicConstantsMach. If |is_new| is true, information will be presented as // “new exception ports”, indicating that they show the state of the exception // ports after SetExceptionPort() has been called. Any send rights obtained by // this function are added to |mach_send_right_pool|. void ShowExceptionPorts(const ExceptionPorts& exception_ports, bool numeric, bool is_new, MachSendRightPool* mach_send_right_pool) { const char* target_name = exception_ports.TargetTypeName(); ExceptionPorts::ExceptionHandlerVector handlers; if (!exception_ports.GetExceptionPorts(ExcMaskValid(), &handlers)) { return; } const char* age_name = is_new ? "new " : ""; if (handlers.empty()) { printf("no %s%s exception ports\n", age_name, target_name); } for (size_t port_index = 0; port_index < handlers.size(); ++port_index) { mach_send_right_pool->AddSendRight(handlers[port_index].port); if (numeric) { printf( "%s%s exception port %zu, mask %#x, port %#x, " "behavior %#x, flavor %u\n", age_name, target_name, port_index, handlers[port_index].mask, handlers[port_index].port, handlers[port_index].behavior, handlers[port_index].flavor); } else { std::string mask_string = ExceptionMaskToString( handlers[port_index].mask, kUseShortName | kUnknownIsEmpty | kUseOr); if (mask_string.empty()) { mask_string.assign("?"); } std::string behavior_string = ExceptionBehaviorToString( handlers[port_index].behavior, kUseShortName | kUnknownIsEmpty); if (behavior_string.empty()) { behavior_string.assign("?"); } std::string flavor_string = ThreadStateFlavorToString( handlers[port_index].flavor, kUseShortName | kUnknownIsEmpty); if (flavor_string.empty()) { flavor_string.assign("?"); } printf( "%s%s exception port %zu, mask %#x (%s), port %#x, " "behavior %#x (%s), flavor %u (%s)\n", age_name, target_name, port_index, handlers[port_index].mask, mask_string.c_str(), handlers[port_index].port, handlers[port_index].behavior, behavior_string.c_str(), handlers[port_index].flavor, flavor_string.c_str()); } } } // Sets the exception port for |target_port|, a send right to a thread, task, or // host port, to |description|, which identifies what type of port |target_port| // is and describes an exception port to be set. Returns true on success. // // This function may be called more than once if setting different handlers is // desired. bool SetExceptionPort(const ExceptionHandlerDescription* description, mach_port_t target_port) { base::apple::ScopedMachSendRight service_port; if (description->handler.compare( 0, strlen(kHandlerBootstrapColon), kHandlerBootstrapColon) == 0) { const char* service_name = description->handler.c_str() + strlen(kHandlerBootstrapColon); service_port = BootstrapLookUp(service_name); if (service_port == kMachPortNull) { return false; } // The service port doesn’t need to be added to a MachSendRightPool because // it’s not used for display at all. ScopedMachSendRight is sufficient. } else if (description->handler != kHandlerNull) { return false; } ExceptionPorts exception_ports(description->target_type, target_port); if (!exception_ports.SetExceptionPort(description->mask, service_port.get(), description->behavior, description->flavor)) { return false; } return true; } void Usage(const std::string& me) { // clang-format off fprintf(stderr, "Usage: %s [OPTION]... [COMMAND [ARG]...]\n" "View and change Mach exception ports, and run COMMAND if supplied.\n" "\n" " -s, --set-handler=DESCRIPTION set an exception port to DESCRIPTION, see below\n" " --show-bootstrap=SERVICE look up and display a service registered with\n" " the bootstrap server\n" " -p, --pid=PID operate on PID instead of the current task\n" " -h, --show-host display original host exception ports\n" " -t, --show-task display original task exception ports\n" " --show-thread display original thread exception ports\n" " -H, --show-new-host display modified host exception ports\n" " -T, --show-new-task display modified task exception ports\n" " --show-new-thread display modified thread exception ports\n" " -n, --numeric display values numerically, not symbolically\n" " --help display this help and exit\n" " --version output version information and exit\n" "\n" "Any operations on host exception ports require superuser permissions.\n" "\n" "DESCRIPTION is formatted as a comma-separated sequence of tokens, where each\n" "token consists of a key and value separated by an equals sign. Available keys:\n" " target which target's exception ports to set: host, task, or thread\n" " mask the mask of exception types to handle: CRASH, ALL, or others\n" " behavior the specific exception handler routine to call: DEFAULT, STATE,\n" " or STATE_IDENTITY, possibly with MACH_EXCEPTION_CODES.\n" " flavor the thread state flavor passed to the handler: architecture-specific\n" " handler the exception handler: NULL or bootstrap:SERVICE, indicating that\n" " the handler should be looked up with the bootstrap server\n" "The default DESCRIPTION is\n" " target=task,mask=CRASH,behavior=DEFAULT|MACH,flavor=NONE,handler=NULL\n", me.c_str()); // clang-format on ToolSupport::UsageTail(me); } int ExceptionPortToolMain(int argc, char* argv[]) { const std::string me(basename(argv[0])); enum ExitCode { kExitSuccess = EXIT_SUCCESS, // To differentiate this tool’s errors from errors in the programs it execs, // use a high exit code for ordinary failures instead of EXIT_FAILURE. This // is the same rationale for using the distinct exit codes for exec // failures. kExitFailure = 125, // Like env, use exit code 126 if the program was found but could not be // invoked, and 127 if it could not be found. // http://pubs.opengroup.org/onlinepubs/9699919799/utilities/env.html kExitExecFailure = 126, kExitExecENOENT = 127, }; enum OptionFlags { // “Short” (single-character) options. kOptionSetPort = 's', kOptionPid = 'p', kOptionShowHost = 'h', kOptionShowTask = 't', kOptionShowNewHost = 'H', kOptionShowNewTask = 'T', kOptionNumeric = 'n', // Long options without short equivalents. kOptionLastChar = 255, kOptionShowBootstrap, kOptionShowThread, kOptionShowNewThread, // Standard options. kOptionHelp = -2, kOptionVersion = -3, }; struct { std::vector show_bootstrap; std::vector set_handler; pid_t pid; task_t alternate_task; bool show_host; bool show_task; bool show_thread; bool show_new_host; bool show_new_task; bool show_new_thread; bool numeric; } options = {}; static constexpr option long_options[] = { {"set-handler", required_argument, nullptr, kOptionSetPort}, {"show-bootstrap", required_argument, nullptr, kOptionShowBootstrap}, {"pid", required_argument, nullptr, kOptionPid}, {"show-host", no_argument, nullptr, kOptionShowHost}, {"show-task", no_argument, nullptr, kOptionShowTask}, {"show-thread", no_argument, nullptr, kOptionShowThread}, {"show-new-host", no_argument, nullptr, kOptionShowNewHost}, {"show-new-task", no_argument, nullptr, kOptionShowNewTask}, {"show-new-thread", no_argument, nullptr, kOptionShowNewThread}, {"numeric", no_argument, nullptr, kOptionNumeric}, {"help", no_argument, nullptr, kOptionHelp}, {"version", no_argument, nullptr, kOptionVersion}, {nullptr, 0, nullptr, 0}, }; int opt; while ((opt = getopt_long(argc, argv, "+s:p:htHTn", long_options, nullptr)) != -1) { switch (opt) { case kOptionSetPort: { options.set_handler.push_back({}); ExceptionHandlerDescription* description = &options.set_handler.back(); description->target_type = ExceptionPorts::kTargetTypeTask; description->mask = EXC_MASK_CRASH; description->behavior = EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES; description->flavor = THREAD_STATE_NONE; description->handler = "NULL"; if (!ParseHandlerString(optarg, description)) { fprintf(stderr, "%s: invalid exception handler: %s\n", me.c_str(), optarg); return kExitFailure; } break; } case kOptionShowBootstrap: options.show_bootstrap.push_back(optarg); break; case kOptionPid: if (!StringToNumber(optarg, &options.pid)) { fprintf(stderr, "%s: invalid pid: %s\n", me.c_str(), optarg); return kExitFailure; } break; case kOptionShowHost: options.show_host = true; break; case kOptionShowTask: options.show_task = true; break; case kOptionShowThread: options.show_thread = true; break; case kOptionShowNewHost: options.show_new_host = true; break; case kOptionShowNewTask: options.show_new_task = true; break; case kOptionShowNewThread: options.show_new_thread = true; break; case kOptionNumeric: options.numeric = true; break; case kOptionHelp: Usage(me); return kExitSuccess; case kOptionVersion: ToolSupport::Version(me); return kExitSuccess; default: ToolSupport::UsageHint(me, nullptr); return kExitFailure; } } argc -= optind; argv += optind; if (options.show_bootstrap.empty() && !options.show_host && !options.show_task && !options.show_thread && options.set_handler.empty() && argc == 0) { ToolSupport::UsageHint(me, "nothing to do"); return kExitFailure; } base::apple::ScopedMachSendRight alternate_task_owner; if (options.pid) { if (argc) { ToolSupport::UsageHint(me, "cannot combine -p with COMMAND"); return kExitFailure; } options.alternate_task = TaskForPID(options.pid); if (options.alternate_task == TASK_NULL) { return kExitFailure; } alternate_task_owner.reset(options.alternate_task); } // This tool may have been installed as a setuid binary so that TaskForPID() // could succeed. Drop any privileges now that they’re no longer necessary. DropPrivileges(); MachSendRightPool mach_send_right_pool; // Show bootstrap services requested. for (const char* service : options.show_bootstrap) { ShowBootstrapService(service, &mach_send_right_pool); } // Show the original exception ports. if (options.show_host) { ShowExceptionPorts( ExceptionPorts(ExceptionPorts::kTargetTypeHost, HOST_NULL), options.numeric, false, &mach_send_right_pool); } if (options.show_task) { ShowExceptionPorts( ExceptionPorts(ExceptionPorts::kTargetTypeTask, options.alternate_task), options.numeric, false, &mach_send_right_pool); } if (options.show_thread) { ShowExceptionPorts( ExceptionPorts(ExceptionPorts::kTargetTypeThread, THREAD_NULL), options.numeric, false, &mach_send_right_pool); } if (!options.set_handler.empty()) { // Set new exception handlers. for (ExceptionHandlerDescription description : options.set_handler) { if (!SetExceptionPort( &description, description.target_type == ExceptionPorts::kTargetTypeTask ? options.alternate_task : TASK_NULL)) { return kExitFailure; } } // Show changed exception ports. if (options.show_new_host) { ShowExceptionPorts( ExceptionPorts(ExceptionPorts::kTargetTypeHost, HOST_NULL), options.numeric, true, &mach_send_right_pool); } if (options.show_new_task) { ShowExceptionPorts( ExceptionPorts(ExceptionPorts::kTargetTypeTask, options.alternate_task), options.numeric, true, &mach_send_right_pool); } if (options.show_new_thread) { ShowExceptionPorts( ExceptionPorts(ExceptionPorts::kTargetTypeThread, THREAD_NULL), options.numeric, true, &mach_send_right_pool); } } if (argc) { // Using the remaining arguments, start a new program with the new set of // exception ports in effect. execvp(argv[0], argv); PLOG(ERROR) << "execvp " << argv[0]; return errno == ENOENT ? kExitExecENOENT : kExitExecFailure; } return kExitSuccess; } } // namespace } // namespace crashpad int main(int argc, char* argv[]) { return crashpad::ExceptionPortToolMain(argc, argv); }