Add TaskForPID().
This also transitions exception_port_tool to use TaskForPID(), so that it can be safely used as a setuid executable without giving permission to operate on any process on the system. It is difficult to provide a test for this function, because it must be running setuid root in order to do anything interesting. R=rsesek@chromium.org Review URL: https://codereview.chromium.org/728973002
This commit is contained in:
Родитель
6812cec67e
Коммит
de3c46c6b3
|
@ -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[]
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <sys/sysctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <set>
|
||||
|
||||
#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<gid_t> groups = process_info.AllGroups();
|
||||
|
||||
ProcessInfo process_info_self;
|
||||
if (!process_info_self.Initialize(getpid())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::set<gid_t> 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<gid_t> 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
|
|
@ -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 <mach/mach.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
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_
|
|
@ -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',
|
||||
|
|
Загрузка…
Ссылка в новой задаче