Skip to content

Commit

Permalink
ProcessTree: add macOS specific loader and ES adapter (2/4) (#1237)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kallsyms committed Feb 20, 2024
1 parent 1ea26f0 commit 42eb0a3
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 3 deletions.
6 changes: 6 additions & 0 deletions Source/santad/EventProviders/EndpointSecurity/Message.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<EndpointSecurityAPI> ESAPI() const { return esapi_; }

std::string ParentProcessName() const;

private:
Expand Down
20 changes: 19 additions & 1 deletion Source/santad/ProcessTree/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
Expand Down Expand Up @@ -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"],
Expand Down
33 changes: 33 additions & 0 deletions Source/santad/ProcessTree/SNTEndpointSecurityAdapter.h
Original file line number Diff line number Diff line change
@@ -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 <EndpointSecurity/EndpointSecurity.h>

#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
73 changes: 73 additions & 0 deletions Source/santad/ProcessTree/SNTEndpointSecurityAdapter.mm
Original file line number Diff line number Diff line change
@@ -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 <EndpointSecurity/EndpointSecurity.h>
#include <Foundation/Foundation.h>
#include <bsm/libbsm.h>

#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<EndpointSecurityAPI> esapi = msg.ESAPI();

switch (msg->event_type) {
case ES_EVENT_TYPE_AUTH_EXEC:
case ES_EVENT_TYPE_NOTIFY_EXEC: {
std::vector<std::string> 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
26 changes: 26 additions & 0 deletions Source/santad/ProcessTree/process_tree_macos.h
Original file line number Diff line number Diff line change
@@ -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 <bsm/libbsm.h>

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
111 changes: 109 additions & 2 deletions Source/santad/ProcessTree/process_tree_macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
/// limitations under the License.
#include "Source/santad/ProcessTree/process_tree.h"

#include <Foundation/Foundation.h>
#include <bsm/libbsm.h>
#include <libproc.h>
#include <mach/message.h>
#include <string.h>
#include <sys/sysctl.h>

#include <memory>
#include <vector>
Expand All @@ -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<std::vector<std::string>> 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<std::string> 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<Process> 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<std::string> args =
ProcessArgumentsForPID(audit_token_to_pid(token)).value_or(std::vector<std::string>());

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>((struct Program){
.executable = path,
.arguments = args,
}),
nullptr);
}

absl::Status ProcessTree::Backfill() {
Expand Down
26 changes: 26 additions & 0 deletions Source/santad/ProcessTree/process_tree_test.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::unique_ptr<Annotator>> annotators{};
annotators.emplace_back(std::make_unique<TestAnnotator>());
Expand Down

0 comments on commit 42eb0a3

Please sign in to comment.