mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[Backtracing] Security improvements.
Use `task_read_for_pid()` rather than having the crashing program pass its own task port through. This opts us in to additional OS security measures surrounding the use of this call. rdar://107362003
This commit is contained in:
@@ -252,6 +252,8 @@ _swift_backtrace_thread_get_state(__swift_thread_t target_act,
|
|||||||
return thread_get_state(target_act, flavor, old_state, old_stateCnt);
|
return thread_get_state(target_act, flavor, old_state, old_stateCnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern __swift_kern_return_t task_read_for_pid(__swift_task_t task, int pid, __swift_task_t *ptask);
|
||||||
|
|
||||||
/* DANGER! These are SPI. They may change (or vanish) at short notice, may
|
/* DANGER! These are SPI. They may change (or vanish) at short notice, may
|
||||||
not work how you expect, and are generally dangerous to use. */
|
not work how you expect, and are generally dangerous to use. */
|
||||||
struct dyld_process_cache_info {
|
struct dyld_process_cache_info {
|
||||||
@@ -270,6 +272,11 @@ extern void _dyld_process_info_get_cache(dyld_process_info info, dyld_process_c
|
|||||||
extern void _dyld_process_info_for_each_image(dyld_process_info info, void (^callback)(__swift_uint64_t machHeaderAddress, const __swift_uuid_t uuid, const char* path));
|
extern void _dyld_process_info_for_each_image(dyld_process_info info, void (^callback)(__swift_uint64_t machHeaderAddress, const __swift_uuid_t uuid, const char* path));
|
||||||
extern void _dyld_process_info_for_each_segment(dyld_process_info info, __swift_uint64_t machHeaderAddress, void (^callback)(__swift_uint64_t segmentAddress, __swift_uint64_t segmentSize, const char* segmentName));
|
extern void _dyld_process_info_for_each_segment(dyld_process_info info, __swift_uint64_t machHeaderAddress, void (^callback)(__swift_uint64_t segmentAddress, __swift_uint64_t segmentSize, const char* segmentName));
|
||||||
|
|
||||||
|
#define CS_OPS_STATUS 0
|
||||||
|
#define CS_PLATFORM_BINARY 0x04000000
|
||||||
|
#define CS_PLATFORM_PATH 0x08000000
|
||||||
|
extern int csops(int, unsigned int, void *, __swift_size_t);
|
||||||
|
|
||||||
/* DANGER! CoreSymbolication is a private framework. This is all SPI. */
|
/* DANGER! CoreSymbolication is a private framework. This is all SPI. */
|
||||||
typedef __swift_int32_t cpu_type_t;
|
typedef __swift_int32_t cpu_type_t;
|
||||||
typedef __swift_int32_t cpu_subtype_t;
|
typedef __swift_int32_t cpu_subtype_t;
|
||||||
|
|||||||
@@ -101,31 +101,13 @@ class Target {
|
|||||||
|
|
||||||
var mcontext: MContext
|
var mcontext: MContext
|
||||||
|
|
||||||
static func getParentTask() -> task_t? {
|
static func getTask(pid: pid_t) -> task_t? {
|
||||||
var ports: mach_port_array_t? = nil
|
var port: task_t = 0
|
||||||
var portCount: mach_msg_type_number_t = 0
|
let kr = task_read_for_pid(mach_task_self_, pid, &port)
|
||||||
|
|
||||||
// For some reason, we can't pass a task read port this way, but we
|
|
||||||
// *can* pass the control port. So do that and then ask for a read port
|
|
||||||
// before immediately dropping the control port from this process.
|
|
||||||
|
|
||||||
let kr = mach_ports_lookup(mach_task_self_, &ports, &portCount)
|
|
||||||
if kr != KERN_SUCCESS {
|
if kr != KERN_SUCCESS {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return port
|
||||||
if let ports = ports, portCount != 0 {
|
|
||||||
var taskPort: mach_port_t = 0
|
|
||||||
let kr = task_get_special_port(ports[0], TASK_READ_PORT, &taskPort)
|
|
||||||
if kr != KERN_SUCCESS {
|
|
||||||
mach_port_deallocate(mach_task_self_, ports[0])
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
mach_port_deallocate(mach_task_self_, ports[0])
|
|
||||||
return task_t(taskPort)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func getProcessName(pid: pid_t) -> String {
|
static func getProcessName(pid: pid_t) -> String {
|
||||||
@@ -141,15 +123,40 @@ class Target {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func isPlatformBinary(pid: pid_t) -> Bool {
|
||||||
|
var flags = UInt32(0)
|
||||||
|
|
||||||
|
return csops(pid,
|
||||||
|
UInt32(CS_OPS_STATUS),
|
||||||
|
&flags,
|
||||||
|
MemoryLayout<UInt32>.size) != 0 ||
|
||||||
|
(flags & UInt32(CS_PLATFORM_BINARY | CS_PLATFORM_PATH)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
init(crashInfoAddr: UInt64, limit: Int?, top: Int, cache: Bool) {
|
init(crashInfoAddr: UInt64, limit: Int?, top: Int, cache: Bool) {
|
||||||
pid = getppid()
|
pid = getppid()
|
||||||
if let parentTask = Self.getParentTask() {
|
|
||||||
task = parentTask
|
if Self.isPlatformBinary(pid: pid) {
|
||||||
} else {
|
/* Exit silently in this case; either
|
||||||
print("swift-backtrace: couldn't fetch parent task", to: &standardError)
|
|
||||||
|
1. We can't call csops(), because we're sandboxed, or
|
||||||
|
2. The target is a platform binary.
|
||||||
|
|
||||||
|
If we get killed, that is also fine. */
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will normally only succeed if the parent process has
|
||||||
|
// the com.apple.security.get-task-allow privilege. That gets set
|
||||||
|
// automatically if you're developing in Xcode; if you're developing
|
||||||
|
// on the command line or using SwiftPM, you will need to code sign
|
||||||
|
// your binary with that entitlement to get this to work.
|
||||||
|
guard let parentTask = Self.getTask(pid: pid) else {
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
task = parentTask
|
||||||
|
|
||||||
reader = RemoteMemoryReader(task: __swift_task_t(task))
|
reader = RemoteMemoryReader(task: __swift_task_t(task))
|
||||||
|
|
||||||
name = Self.getProcessName(pid: pid)
|
name = Self.getProcessName(pid: pid)
|
||||||
|
|||||||
@@ -342,26 +342,6 @@ trueOrFalse(OnOffTty oot) {
|
|||||||
bool
|
bool
|
||||||
run_backtracer()
|
run_backtracer()
|
||||||
{
|
{
|
||||||
// Forward our task port to the backtracer; we use the same technique that
|
|
||||||
// libxpc uses to forward one of its ports on fork(), except that we aren't
|
|
||||||
// going to call fork() so libxpc's atfork handler won't run and we'll get
|
|
||||||
// to send the task port to the child.
|
|
||||||
//
|
|
||||||
// I would very much like to send a task *read* port, but for some reason
|
|
||||||
// that doesn't work here. As a result, what we do instead is send the
|
|
||||||
// control port but have the backtracer use it to get the read port and
|
|
||||||
// immediately drop the control port.
|
|
||||||
//
|
|
||||||
// That *should* be safe enough in practice; if someone could replace the
|
|
||||||
// backtracer, then they can also replace libswiftCore, and since we do
|
|
||||||
// this early on in backtracer start-up, the control port won't be valid
|
|
||||||
// by the time anyone gets to try anything nefarious.
|
|
||||||
mach_port_t ports[] = {
|
|
||||||
mach_task_self(),
|
|
||||||
};
|
|
||||||
|
|
||||||
mach_ports_register(mach_task_self(), ports, 1);
|
|
||||||
|
|
||||||
// Set-up the backtracer's command line arguments
|
// Set-up the backtracer's command line arguments
|
||||||
switch (_swift_backtraceSettings.algorithm) {
|
switch (_swift_backtraceSettings.algorithm) {
|
||||||
case UnwindAlgorithm::Fast:
|
case UnwindAlgorithm::Fast:
|
||||||
|
|||||||
@@ -1024,7 +1024,8 @@ if run_vendor == 'apple':
|
|||||||
if 'use_os_stdlib' not in lit_config.params:
|
if 'use_os_stdlib' not in lit_config.params:
|
||||||
config.target_codesign = make_path(config.swift_utils, "swift-darwin-postprocess.py")
|
config.target_codesign = make_path(config.swift_utils, "swift-darwin-postprocess.py")
|
||||||
else:
|
else:
|
||||||
config.target_codesign = "codesign -f -s -"
|
config.target_codesign = "codesign -f -s - --options=runtime --entitlements {}".format(os.path.join(config.swift_utils, 'get-task-allow.plist'))
|
||||||
|
|
||||||
config.target_library_path_var = "DYLD_LIBRARY_PATH"
|
config.target_library_path_var = "DYLD_LIBRARY_PATH"
|
||||||
config.target_runtime = "objc"
|
config.target_runtime = "objc"
|
||||||
config.target_sdk_libcxx_path = os.path.join(config.variant_sdk, 'usr', 'include', 'c++', 'v1')
|
config.target_sdk_libcxx_path = os.path.join(config.variant_sdk, 'usr', 'include', 'c++', 'v1')
|
||||||
|
|||||||
8
utils/get-task-allow.plist
Normal file
8
utils/get-task-allow.plist
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.get-task-allow</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -10,7 +10,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
utils = os.path.dirname(os.path.realpath(__file__))
|
utils = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
get_task_allow_plist = os.path.join(utils, 'get-task-allow.plist')
|
||||||
|
|
||||||
def main(arguments):
|
def main(arguments):
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
@@ -91,7 +91,9 @@ def unrpathize(filename):
|
|||||||
|
|
||||||
def codesign(filename):
|
def codesign(filename):
|
||||||
# "-" is the signing identity for ad-hoc signing.
|
# "-" is the signing identity for ad-hoc signing.
|
||||||
command = ["/usr/bin/codesign", "--force", "--sign", "-", filename]
|
command = ['/usr/bin/codesign', '--force', '--sign', '-',
|
||||||
|
'--options=runtime', '--entitlements', get_task_allow_plist,
|
||||||
|
filename]
|
||||||
subprocess.check_call(command)
|
subprocess.check_call(command)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user