mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +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);
|
||||
}
|
||||
|
||||
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
|
||||
not work how you expect, and are generally dangerous to use. */
|
||||
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_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. */
|
||||
typedef __swift_int32_t cpu_type_t;
|
||||
typedef __swift_int32_t cpu_subtype_t;
|
||||
|
||||
@@ -101,31 +101,13 @@ class Target {
|
||||
|
||||
var mcontext: MContext
|
||||
|
||||
static func getParentTask() -> task_t? {
|
||||
var ports: mach_port_array_t? = nil
|
||||
var portCount: mach_msg_type_number_t = 0
|
||||
|
||||
// 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)
|
||||
static func getTask(pid: pid_t) -> task_t? {
|
||||
var port: task_t = 0
|
||||
let kr = task_read_for_pid(mach_task_self_, pid, &port)
|
||||
if kr != KERN_SUCCESS {
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
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) {
|
||||
pid = getppid()
|
||||
if let parentTask = Self.getParentTask() {
|
||||
task = parentTask
|
||||
} else {
|
||||
print("swift-backtrace: couldn't fetch parent task", to: &standardError)
|
||||
|
||||
if Self.isPlatformBinary(pid: pid) {
|
||||
/* Exit silently in this case; either
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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))
|
||||
|
||||
name = Self.getProcessName(pid: pid)
|
||||
|
||||
@@ -342,26 +342,6 @@ trueOrFalse(OnOffTty oot) {
|
||||
bool
|
||||
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
|
||||
switch (_swift_backtraceSettings.algorithm) {
|
||||
case UnwindAlgorithm::Fast:
|
||||
|
||||
@@ -1024,7 +1024,8 @@ if run_vendor == 'apple':
|
||||
if 'use_os_stdlib' not in lit_config.params:
|
||||
config.target_codesign = make_path(config.swift_utils, "swift-darwin-postprocess.py")
|
||||
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_runtime = "objc"
|
||||
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
|
||||
|
||||
utils = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
get_task_allow_plist = os.path.join(utils, 'get-task-allow.plist')
|
||||
|
||||
def main(arguments):
|
||||
parser = argparse.ArgumentParser(
|
||||
@@ -91,7 +91,9 @@ def unrpathize(filename):
|
||||
|
||||
def codesign(filename):
|
||||
# "-" 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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user