diff --git a/tools/exception_port_tool.ad b/tools/exception_port_tool.ad index 684f2b7a..5a09323d 100644 --- a/tools/exception_port_tool.ad +++ b/tools/exception_port_tool.ad @@ -88,10 +88,14 @@ it is possible to verify that they are set as intended. *-p*, *--pid*='PID':: For operations on the task target, including *--set_handler* with 'TARGET' set to *task*, *--show_task*, and *--show_new_task*, operates on the task associated -with process id 'PID' instead of the current task associated with the tool. This -option may be restricted to use by the superuser or processes permitted by -taskgated(8) to use +task_for_pid()+. When this option is supplied, 'COMMAND' -must not be specified. +with process id 'PID' instead of the current task associated with the tool. When +this option is supplied, 'COMMAND' must not be specified. ++ +This option uses +task_for_pid()+ to access the process’ task port. This +operation may be restricted to use by the superuser or processes permitted by +taskgated(8). Consequently, this program must normally be invoked by root to use +this option. It is possible to install this program as a setuid root executable +to overcome this limitation. *-h*, *--show_host*:: Shows the original host exception ports before making any changes requested by @@ -177,7 +181,7 @@ The program specified by 'COMMAND' could not be found. == See Also -exception_port_tool(1), +catch_exception_tool(1), on_demand_service_tool(1) include::man_footer.ad[] diff --git a/tools/exception_port_tool.cc b/tools/exception_port_tool.cc index c20db73e..bcca29fc 100644 --- a/tools/exception_port_tool.cc +++ b/tools/exception_port_tool.cc @@ -33,6 +33,7 @@ #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/stdlib/string_number_conversion.h" namespace crashpad { @@ -319,7 +320,6 @@ void Usage(const std::string& me) { " --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" -" (must be superuser or permitted by taskgated)\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" @@ -496,12 +496,8 @@ int ExceptionPortToolMain(int argc, char* argv[]) { return kExitFailure; } - // This is only expected to work as root or if taskgated approves. - // taskgated does not normally approve. - kern_return_t kr = - task_for_pid(mach_task_self(), options.pid, &options.alternate_task); - if (kr != KERN_SUCCESS) { - MACH_LOG(ERROR, kr) << "task_for_pid"; + options.alternate_task = TaskForPID(options.pid); + if (options.alternate_task == TASK_NULL) { return kExitFailure; } alternate_task_owner.reset(options.alternate_task); diff --git a/util/mach/task_for_pid.cc b/util/mach/task_for_pid.cc new file mode 100644 index 00000000..1f238b19 --- /dev/null +++ b/util/mach/task_for_pid.cc @@ -0,0 +1,167 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// 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 "util/mach/task_for_pid.h" + +#include +#include + +#include +#include +#include + +#include "base/basictypes.h" +#include "base/mac/mach_logging.h" +#include "base/mac/scoped_mach_port.h" +#include "util/posix/process_info.h" + +namespace crashpad { + +namespace { + +//! \brief Determines whether the groups that \a process_reader belongs to are +//! a subset of the groups that the current process belongs to. +//! +//! This function is similar to 10.9.5 +//! `xnu-2422.115.4/bsd/kern/kern_credential.c` `kauth_cred_gid_subset()`. +bool TaskForPIDGroupCheck(const ProcessInfo& process_info) { + std::set groups = process_info.AllGroups(); + + ProcessInfo process_info_self; + if (!process_info_self.Initialize(getpid())) { + return false; + } + + std::set groups_self = process_info_self.AllGroups(); + + // difference will only contain elements of groups not present in groups_self. + // It will not contain elements of groups_self not present in groups. (That + // would be std::set_symmetric_difference.) + std::set difference; + std::set_difference(groups.begin(), + groups.end(), + groups_self.begin(), + groups_self.end(), + std::inserter(difference, difference.begin())); + if (!difference.empty()) { + LOG(ERROR) << "permission denied (gid)"; + return false; + } + + return true; +} + +//! \brief Determines whether the current process should have permission to +//! access the specified task port. +//! +//! This function is similar to 10.9.5 +//! `xnu-2422.115.4/bsd/vm/vm_unix.c` `task_for_pid_posix_check()`. +//! +//! This function accepts a `task_t` argument instead of a `pid_t` argument, +//! implying that the task send right must be retrieved before it can be +//! checked. This is done because a `pid_t` argument may refer to a different +//! task in between the time that access is checked and its corresponding +//! `task_t` is obtained by `task_for_pid()`. When `task_for_pid()` is called +//! first, any operations requiring the process ID will call `pid_for_task()` +//! and be guaranteed to use the process ID corresponding to the correct task, +//! or to fail if that task is no longer running. If the task dies and the PID +//! is recycled, it is still possible to look up the wrong PID, but falsely +//! granting task access based on the new process’ characteristics is harmless +//! because the task will be a dead name at that point. +bool TaskForPIDCheck(task_t task) { + // If the effective user ID is not 0, then this code is not running as root at + // all, and the kernel’s own checks are sufficient to determine access. The + // point of this function is to simulate the kernel’s own checks when the + // effective user ID is 0 but the real user ID is anything else. + if (geteuid() != 0) { + return true; + } + + // If the real user ID is 0, then this code is not running setuid root, it’s + // genuinely running as root, and it should be allowed maximum access. + uid_t uid = getuid(); + if (uid == 0) { + return true; + } + + // task_for_pid_posix_check() would permit access to the running process’ own + // task here, and would then check the kern.tfp.policy sysctl. If set to + // KERN_TFP_POLICY_DENY, it would deny access. + // + // This behavior is not duplicated here because the point of this function is + // to permit task_for_pid() access for setuid root programs. It is assumed + // that a setuid root program ought to be able to overcome any policy set in + // kern.tfp.policy. + // + // Access to the running process’ own task is not granted outright and is + // instead subjected to the same user/group ID checks as any other process. + // This has the effect of denying access to the running process’ own task when + // it is setuid root. This is intentional, because it prevents the same sort + // of cross-privilege disclosure discussed below at the DidChangePriveleges() + // check. The running process can still access its own task port via + // mach_task_self(), but a non-root user cannot coerce a setuid root tool to + // operate on itself by specifying its own process ID to this TaskForPID() + // interface. + + ProcessInfo process_info; + if (!process_info.InitializeFromTask(task)) { + return false; + } + + // The target process’ real user ID, effective user ID, and saved set-user ID + // must match this process’ own real user ID. task_for_pid_posix_check() + // checks against the current process’ effective user ID, but for the purposes + // of this function, when running setuid root, the real user ID is the correct + // choice. + if (process_info.RealUserID() != uid || + process_info.EffectiveUserID() != uid || + process_info.SavedUserID() != uid) { + LOG(ERROR) << "permission denied (uid)"; + return false; + } + + // The target process must not have changed privileges. The rationale for this + // check is explained in 10.9.5 xnu-2422.115.4/bsd/kern/kern_prot.c + // issetugid(): processes that have changed privileges may have loaded data + // using different credentials than they are currently operating with, and + // allowing other processes access to this data based solely on a check of the + // current credentials could violate confidentiality. + if (process_info.DidChangePrivileges()) { + LOG(ERROR) << "permission denied (P_SUGID)"; + return false; + } + + return TaskForPIDGroupCheck(process_info); +} + +} // namespace + +task_t TaskForPID(pid_t pid) { + task_t task; + kern_return_t kr = task_for_pid(mach_task_self(), pid, &task); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "task_for_pid"; + return TASK_NULL; + } + + base::mac::ScopedMachSendRight task_owner(task); + + if (!TaskForPIDCheck(task)) { + return TASK_NULL; + } + + return task_owner.release(); +} + +} // namespace crashpad diff --git a/util/mach/task_for_pid.h b/util/mach/task_for_pid.h new file mode 100644 index 00000000..2b81fbeb --- /dev/null +++ b/util/mach/task_for_pid.h @@ -0,0 +1,59 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// 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. + +#ifndef CRASHPAD_UTIL_MACH_TASK_FOR_PID_H_ +#define CRASHPAD_UTIL_MACH_TASK_FOR_PID_H_ + +#include +#include + +namespace crashpad { + +//! \brief Wraps `task_for_pid()`. +//! +//! This function exists to support `task_for_pid()` access checks in a setuid +//! environment. Normally, `task_for_pid()` can only return an arbitrary task’s +//! port when running as root or when taskgated(8) approves. When not running as +//! root, a series of access checks are perfomed to ensure that the running +//! process has permission to obtain the other process’ task port. +//! +//! It is possible to make an executable setuid root to give it broader +//! `task_for_pid()` access by bypassing taskgated(8) checks, but this also has +//! the effect of bypassing the access checks, allowing any process’ task port +//! to be obtained. In most situations, these access checks are desirable to +//! prevent security and privacy breaches. +//! +//! When running as setuid root, this function wraps `task_for_pid()`, +//! reimplementing those access checks. A process whose effective user ID is 0 +//! and whose real user ID is nonzero is understood to be running setuid root. +//! In this case, the requested task’s real, effective, and saved set-user IDs +//! must all equal the running process’ real user ID, the requested task must +//! not have changed privileges, and the requested task’s set of all group IDs +//! (including its real, effective, and saved set-group IDs and supplementary +//! group list) must be a subset of the running process’ set of all group IDs. +//! These access checks mimic those that the kernel performs. +//! +//! When not running as setuid root, `task_for_pid()` is called directly, +//! without imposing any additional checks beyond what the kernel does. +//! +//! \param[in] pid The process ID of the task whose task port is desired. +//! +//! \return A send right to the task port if it could be obtained, or +//! `TASK_NULL` otherwise, with an error message logged. If a send right is +//! returned, the caller takes ownership of it. +task_t TaskForPID(pid_t pid); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MACH_TASK_FOR_PID_H_ diff --git a/util/util.gyp b/util/util.gyp index d60be7fe..05dcb6d3 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -56,6 +56,8 @@ 'mach/scoped_task_suspend.h', 'mach/symbolic_constants_mach.cc', 'mach/symbolic_constants_mach.h', + 'mach/task_for_pid.cc', + 'mach/task_for_pid.h', 'mach/task_memory.cc', 'mach/task_memory.h', 'misc/clock.cc',