Skip to content

[Backtracing] Security improvements. #65067

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions stdlib/public/SwiftShims/swift/shims/_SwiftBacktracing.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down
59 changes: 33 additions & 26 deletions stdlib/public/libexec/swift-backtrace/Target.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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")

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)
Expand Down
20 changes: 0 additions & 20 deletions stdlib/public/runtime/CrashHandlerMacOS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,26 +340,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:
Expand Down
3 changes: 2 additions & 1 deletion test/lit.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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 - --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')
Expand Down
8 changes: 8 additions & 0 deletions utils/get-task-allow.plist
Original file line number Diff line number Diff line change
@@ -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>
5 changes: 4 additions & 1 deletion utils/swift-darwin-postprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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):
Expand Down Expand Up @@ -91,7 +92,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', '-',
'--entitlements', get_task_allow_plist,
filename]
subprocess.check_call(command)


Expand Down