From 42eb0a36697c1a87b8544881b113b499a877b18a Mon Sep 17 00:00:00 2001 From: Nick Gregory Date: Tue, 20 Feb 2024 13:56:54 -0500 Subject: [PATCH] ProcessTree: add macOS specific loader and ES adapter (2/4) (#1237) * ProcessTree: add macos-specific loader and event adapter * lingering darwin->macos * lint * remove defunct client id * struct rename * and one last header update * use EndpointSecurityAPI in adapter * expose esapi in message --- .../EventProviders/EndpointSecurity/Message.h | 6 + Source/santad/ProcessTree/BUILD | 20 +++- .../ProcessTree/SNTEndpointSecurityAdapter.h | 33 ++++++ .../ProcessTree/SNTEndpointSecurityAdapter.mm | 73 ++++++++++++ .../santad/ProcessTree/process_tree_macos.h | 26 ++++ .../santad/ProcessTree/process_tree_macos.mm | 111 +++++++++++++++++- .../santad/ProcessTree/process_tree_test.mm | 26 ++++ 7 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 Source/santad/ProcessTree/SNTEndpointSecurityAdapter.h create mode 100644 Source/santad/ProcessTree/SNTEndpointSecurityAdapter.mm create mode 100644 Source/santad/ProcessTree/process_tree_macos.h diff --git a/Source/santad/EventProviders/EndpointSecurity/Message.h b/Source/santad/EventProviders/EndpointSecurity/Message.h index b27febf69..52acda3dd 100644 --- a/Source/santad/EventProviders/EndpointSecurity/Message.h +++ b/Source/santad/EventProviders/EndpointSecurity/Message.h @@ -41,6 +41,12 @@ class Message { const es_message_t* operator->() const { return es_msg_; } const es_message_t& operator*() const { return *es_msg_; } + // Helper to get the API associated with this message. + // Used for things like es_exec_arg_count. + // We should ideally rework this to somehow present these functions as methods on the Message, + // however this would be a bit of a bigger lift. + std::shared_ptr ESAPI() const { return esapi_; } + std::string ParentProcessName() const; private: diff --git a/Source/santad/ProcessTree/BUILD b/Source/santad/ProcessTree/BUILD index a002f93a6..a8193e8ed 100644 --- a/Source/santad/ProcessTree/BUILD +++ b/Source/santad/ProcessTree/BUILD @@ -19,7 +19,10 @@ objc_library( "process_tree.cc", "process_tree_macos.mm", ], - hdrs = ["process_tree.h"], + hdrs = [ + "process_tree.h", + "process_tree_macos.h", + ], sdk_dylibs = [ "bsm", ], @@ -48,6 +51,21 @@ cc_proto_library( deps = [":process_tree_proto"], ) +objc_library( + name = "SNTEndpointSecurityAdapter", + srcs = ["SNTEndpointSecurityAdapter.mm"], + hdrs = ["SNTEndpointSecurityAdapter.h"], + sdk_dylibs = [ + "bsm", + ], + deps = [ + ":process_tree", + "//Source/santad:EndpointSecurityAPI", + "//Source/santad:EndpointSecurityMessage", + "@com_google_absl//absl/status:statusor", + ], +) + objc_library( name = "process_tree_test_helpers", srcs = ["process_tree_test_helpers.mm"], diff --git a/Source/santad/ProcessTree/SNTEndpointSecurityAdapter.h b/Source/santad/ProcessTree/SNTEndpointSecurityAdapter.h new file mode 100644 index 000000000..01ea3523d --- /dev/null +++ b/Source/santad/ProcessTree/SNTEndpointSecurityAdapter.h @@ -0,0 +1,33 @@ +/// Copyright 2023 Google LLC +/// +/// 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 +/// +/// https://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 SANTA__SANTAD_PROCESSTREE_SNTENDPOINTSECURITYADAPTER_H +#define SANTA__SANTAD_PROCESSTREE_SNTENDPOINTSECURITYADAPTER_H + +#include + +#include "Source/santad/EventProviders/EndpointSecurity/Message.h" +#include "Source/santad/ProcessTree/process_tree.h" + +namespace santa::santad::process_tree { + +// Inform the tree of the ES event in msg. +// This is idempotent on the tree, so can be called from multiple places with +// the same msg. +void InformFromESEvent( + ProcessTree &tree, + const santa::santad::event_providers::endpoint_security::Message &msg); + +} // namespace santa::santad::process_tree + +#endif diff --git a/Source/santad/ProcessTree/SNTEndpointSecurityAdapter.mm b/Source/santad/ProcessTree/SNTEndpointSecurityAdapter.mm new file mode 100644 index 000000000..5a3c6dd47 --- /dev/null +++ b/Source/santad/ProcessTree/SNTEndpointSecurityAdapter.mm @@ -0,0 +1,73 @@ +/// Copyright 2023 Google LLC +/// +/// 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 +/// +/// https://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 "Source/santad/ProcessTree/SNTEndpointSecurityAdapter.h" + +#include +#include +#include + +#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h" +#include "Source/santad/EventProviders/EndpointSecurity/Message.h" +#include "Source/santad/ProcessTree/process_tree.h" +#include "Source/santad/ProcessTree/process_tree_macos.h" +#include "absl/status/statusor.h" + +using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI; +using santa::santad::event_providers::endpoint_security::Message; + +namespace santa::santad::process_tree { + +void InformFromESEvent(ProcessTree &tree, const Message &msg) { + struct Pid event_pid = PidFromAuditToken(msg->process->audit_token); + auto proc = tree.Get(event_pid); + + if (!proc) { + return; + } + + std::shared_ptr esapi = msg.ESAPI(); + + switch (msg->event_type) { + case ES_EVENT_TYPE_AUTH_EXEC: + case ES_EVENT_TYPE_NOTIFY_EXEC: { + std::vector args; + args.reserve(esapi->ExecArgCount(&msg->event.exec)); + for (int i = 0; i < esapi->ExecArgCount(&msg->event.exec); i++) { + es_string_token_t arg = esapi->ExecArg(&msg->event.exec, i); + args.push_back(std::string(arg.data, arg.length)); + } + + es_string_token_t executable = msg->event.exec.target->executable->path; + tree.HandleExec( + msg->mach_time, **proc, PidFromAuditToken(msg->event.exec.target->audit_token), + (struct Program){.executable = std::string(executable.data, executable.length), + .arguments = args}, + (struct Cred){ + .uid = audit_token_to_euid(msg->event.exec.target->audit_token), + .gid = audit_token_to_egid(msg->event.exec.target->audit_token), + }); + + break; + } + case ES_EVENT_TYPE_NOTIFY_FORK: { + tree.HandleFork(msg->mach_time, **proc, + PidFromAuditToken(msg->event.fork.child->audit_token)); + break; + } + case ES_EVENT_TYPE_NOTIFY_EXIT: tree.HandleExit(msg->mach_time, **proc); break; + default: return; + } +} + +} // namespace santa::santad::process_tree diff --git a/Source/santad/ProcessTree/process_tree_macos.h b/Source/santad/ProcessTree/process_tree_macos.h new file mode 100644 index 000000000..3d44b7aa8 --- /dev/null +++ b/Source/santad/ProcessTree/process_tree_macos.h @@ -0,0 +1,26 @@ +/// Copyright 2023 Google LLC +/// +/// 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 +/// +/// https://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 SANTA__SANTAD_PROCESSTREE_TREE_MACOS_H +#define SANTA__SANTAD_PROCESSTREE_TREE_MACOS_H + +#include + +namespace santa::santad::process_tree { + +// Create a struct pid from the given audit token. +struct Pid PidFromAuditToken(const audit_token_t &tok); + +} // namespace santa::santad::process_tree + +#endif diff --git a/Source/santad/ProcessTree/process_tree_macos.mm b/Source/santad/ProcessTree/process_tree_macos.mm index c0677ea6f..fc8f36b84 100644 --- a/Source/santad/ProcessTree/process_tree_macos.mm +++ b/Source/santad/ProcessTree/process_tree_macos.mm @@ -13,7 +13,12 @@ /// limitations under the License. #include "Source/santad/ProcessTree/process_tree.h" +#include +#include #include +#include +#include +#include #include #include @@ -25,9 +30,111 @@ namespace santa::santad::process_tree { +namespace { +// Modified from +// https://chromium.googlesource.com/crashpad/crashpad/+/360e441c53ab4191a6fd2472cc57c3343a2f6944/util/posix/process_util_mac.cc +// TODO: https://github.com/apple-oss-distributions/adv_cmds/blob/main/ps/ps.c +absl::StatusOr> ProcessArgumentsForPID(pid_t pid) { + // The format of KERN_PROCARGS2 is explained in 10.9.2 adv_cmds-153/ps/print.c + // getproclline(). It is an int (argc) followed by the executable’s string + // area. The string area consists of NUL-terminated strings, beginning with + // the executable path, and then starting on an aligned boundary, all of the + // elements of argv, envp, and applev. + // It is possible for a process to exec() in between the two sysctl() calls + // below. If that happens, and the string area of the new program is larger + // than that of the old one, args_size_estimate will be too small. To detect + // this situation, the second sysctl() attempts to fetch args_size_estimate + + // 1 bytes, expecting to only receive args_size_estimate. If it gets the extra + // byte, it indicates that the string area has grown, and the sysctl() pair + // will be retried a limited number of times. + size_t args_size_estimate; + size_t args_size; + std::string args; + int tries = 3; + do { + int mib[] = {CTL_KERN, KERN_PROCARGS2, pid}; + int rv = sysctl(mib, 3, nullptr, &args_size_estimate, nullptr, 0); + if (rv != 0) { + return absl::InternalError("KERN_PROCARGS2"); + } + args_size = args_size_estimate + 1; + args.resize(args_size); + rv = sysctl(mib, 3, &args[0], &args_size, nullptr, 0); + if (rv != 0) { + return absl::InternalError("KERN_PROCARGS2"); + } + } while (args_size == args_size_estimate + 1 && tries--); + if (args_size == args_size_estimate + 1) { + return absl::InternalError("Couldn't determine size"); + } + // KERN_PROCARGS2 needs to at least contain argc. + if (args_size < sizeof(int)) { + return absl::InternalError("Bad args_size"); + } + args.resize(args_size); + // Get argc. + int argc; + memcpy(&argc, &args[0], sizeof(argc)); + // Find the end of the executable path. + size_t start_pos = sizeof(argc); + size_t nul_pos = args.find('\0', start_pos); + if (nul_pos == std::string::npos) { + return absl::InternalError("Can't find end of executable path"); + } + // Find the beginning of the string area. + start_pos = args.find_first_not_of('\0', nul_pos); + if (start_pos == std::string::npos) { + return absl::InternalError("Can't find args after executable path"); + } + std::vector local_argv; + while (argc-- && nul_pos != std::string::npos) { + nul_pos = args.find('\0', start_pos); + local_argv.push_back(args.substr(start_pos, nul_pos - start_pos)); + start_pos = nul_pos + 1; + } + return local_argv; +} +} // namespace + +struct Pid PidFromAuditToken(const audit_token_t &tok) { + return (struct Pid){.pid = audit_token_to_pid(tok), + .pidversion = (uint64_t)audit_token_to_pidversion(tok)}; +} + absl::StatusOr LoadPID(pid_t pid) { - // TODO - return absl::UnimplementedError("LoadPID not implemented"); + task_name_t task; + mach_msg_type_number_t size = TASK_AUDIT_TOKEN_COUNT; + audit_token_t token; + + if (task_name_for_pid(mach_task_self(), pid, &task) != KERN_SUCCESS) { + return absl::InternalError("task_name_for_pid"); + } + + if (task_info(task, TASK_AUDIT_TOKEN, (task_info_t)&token, &size) != KERN_SUCCESS) { + return absl::InternalError("task_info(TASK_AUDIT_TOKEN)"); + } + mach_port_deallocate(mach_task_self(), task); + + char path[PROC_PIDPATHINFO_MAXSIZE]; + if (proc_pidpath_audittoken(&token, path, sizeof(path)) <= 0) { + return absl::InternalError("proc_pidpath_audittoken"); + } + + // Don't fail Process creation if args can't be recovered. + std::vector args = + ProcessArgumentsForPID(audit_token_to_pid(token)).value_or(std::vector()); + + return Process((struct Pid){.pid = audit_token_to_pid(token), + .pidversion = (uint64_t)audit_token_to_pidversion(token)}, + (struct Cred){ + .uid = audit_token_to_euid(token), + .gid = audit_token_to_egid(token), + }, + std::make_shared((struct Program){ + .executable = path, + .arguments = args, + }), + nullptr); } absl::Status ProcessTree::Backfill() { diff --git a/Source/santad/ProcessTree/process_tree_test.mm b/Source/santad/ProcessTree/process_tree_test.mm index 524d8a0b1..c852c6150 100644 --- a/Source/santad/ProcessTree/process_tree_test.mm +++ b/Source/santad/ProcessTree/process_tree_test.mm @@ -107,6 +107,32 @@ - (void)testSimpleOps { XCTAssertEqual(child->effective_cred_, self.initProc->effective_cred_); } +// We can't test the full backfill process, as retrieving information on +// processes (with task_name_for_pid) requires privileges. +// Test what we can by LoadPID'ing ourselves. +- (void)testLoadPID { + auto proc = LoadPID(getpid()).value(); + + audit_token_t self_tok; + mach_msg_type_number_t count = TASK_AUDIT_TOKEN_COUNT; + XCTAssertEqual(task_info(mach_task_self(), TASK_AUDIT_TOKEN, (task_info_t)&self_tok, &count), + KERN_SUCCESS); + + XCTAssertEqual(proc.pid_.pid, audit_token_to_pid(self_tok)); + XCTAssertEqual(proc.pid_.pidversion, audit_token_to_pidversion(self_tok)); + + XCTAssertEqual(proc.effective_cred_.uid, geteuid()); + XCTAssertEqual(proc.effective_cred_.gid, getegid()); + + [[[NSProcessInfo processInfo] arguments] + enumerateObjectsUsingBlock:^(NSString *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { + XCTAssertEqualObjects(@(proc.program_->arguments[idx].c_str()), obj); + if (idx == 0) { + XCTAssertEqualObjects(@(proc.program_->executable.c_str()), obj); + } + }]; +} + - (void)testAnnotation { std::vector> annotators{}; annotators.emplace_back(std::make_unique());