/**
@file
@brief 'exec' event
@details Copyright (c) 2024 Acronis International GmbH
@author Denis Kopyrin (denis.kopyrin@acronis.com)
@since $Id: $
*/
#include "exec_event.h"
#include "debug.h"
#include "message.h"
#include "path_tools.h"
#include "si_templates.h"
#include "si_writer_common.h"
#include "task_info_map.h"
#include "task_tools.h"
#include <linux/fs_struct.h>
#include <linux/rcupdate.h>
#include <linux/uaccess.h>
// This is a limit for the command line length in the exec event put in cmdline field.
// Otherwise command line is going to be truncated.
#define COMMAND_LINE_LIMIT 32768
#define COMMON_EXEC_FIELDS \
SI_COMMON_FIELDS, \
SI_COMMON_CRED_FIELDS, \
FP_SI_PI_PARENT_PROCESS_ID, \
FP_SI_PI_PARENT_THREAD_ID, \
FP_SI_PI_PARENT_PROCESS_UID, \
FP_SI_PI_PARENT_ARTIFICIAL_PROCESS_START_TIMESTAMP, \
FP_SI_PI_PARENT_PROCESS_ID_VERSION, \
FP_SI_PI_PARENT_PROCESS_START_TIMESTAMP, \
FP_SI_PI_PROCESS_START_TIMESTAMP, \
FP_SI_PI_ARTIFICIAL_PROCESS_START_TIMESTAMP, \
FP_SI_PI_UNIX_EXEC_TYPE, \
FP_SI_PI_CURRENT_WORKING_DIRECTORY, \
FP_SI_PI_CGROUP_NAME
static const uint8_t k_exec_fields_without_exe_fields[] = {
COMMON_EXEC_FIELDS,
SI_COMMON_IMAGE_NAME_FIELDS,
};
static const uint8_t k_exec_fields[] = {
COMMON_EXEC_FIELDS,
SI_COMMON_EXE_FILE_FIELDS,
FP_SI_PI_COMMAND_LINE,
FP_SI_PI_COMMAND_LINE_TRUNCATED,
};
static void exec_event_with_alloc_flags(struct task_struct *p, bool nowait)
{
msg_t* msg;
uint32_t event_size;
bool has_parent = false;
pid_t parent_tgid = 0;
pid_t parent_pid = 0;
uint64_t parent_upid = 0;
SiTimeMicroseconds parent_start_time = {0};
pid_t tgid = p->tgid;
pid_t pid = p->pid;
file_handle_info_t handle_info = (file_handle_info_t){0};
path_info_t path_info = (path_info_t){0};
char comm[TASK_COMM_LEN];
struct path* exe;
bool has_command_line = false;
unsigned long command_line_length = 0;
bool command_line_length_truncated = false;
path_info_t cwd_path_info = (path_info_t){0};
SiTimeMicroseconds start_time = make_process_start_time(p);
// mm allowed to be accessed without locks because child has not started to execute yet and it has a separate mm
// Notice that some children do not have mm. That is true for kernel tasks, for example.
struct mm_struct *mm = p->mm;
struct file* exe_file = mm ? mm->exe_file : NULL;
exe = exe_file ? &exe_file->f_path : NULL;
if (exe && !path_is_usable(exe)) {
exe = NULL;
}
if (exe) {
if (!nowait && mm->arg_end) {
has_command_line = true;
command_line_length = mm->arg_end - mm->arg_start;
if (command_line_length > COMMAND_LINE_LIMIT) {
command_line_length = COMMAND_LINE_LIMIT;
command_line_length_truncated = true;
}
}
file_handle_info_make_with_alloc_flags(&handle_info, exe, nowait);
path_info_make_with_alloc_flags(&path_info, exe, false /*not dir*/, nowait);
event_size = SI_ESTIMATE_SIZE_CONST_PARAMS(k_exec_fields)
+ SI_ESTIMATE_SIZE_PATH_INFO(path_info)
+ SI_ESTIMATE_SIZE_FILE_HANDLE_INFO(handle_info)
+ command_line_length;
} else {
get_task_comm(comm, p);
event_size = SI_ESTIMATE_SIZE_CONST_PARAMS(k_exec_fields_without_exe_fields) + TASK_COMM_LEN;
}
event_size += SI_CONTAINER_NAME_LIMIT;
// I cannot see this happening ever but just in case
if (p != current)
has_command_line = false;
if (!nowait && p->fs) {
// 'path_put' may sleep so have to do this only in '!nowait' mode
struct path cwd = (struct path){};
get_fs_pwd(p->fs, &cwd);
if (path_is_valid(&cwd)) {
path_info_make_from_valid(&cwd_path_info, &cwd);
}
path_put(&cwd);
event_size += SI_ESTIMATE_SIZE_PATH_INFO(cwd_path_info);
}
{
struct task_struct* parent;
rcu_read_lock();
parent = rcu_dereference(p->real_parent);
parent_tgid = parent ? parent->tgid : 0;
has_parent = 0 != parent_tgid;
if (has_parent) {
parent_pid = parent->pid;
parent_upid = make_unique_pid(parent);
parent_start_time = make_process_start_time(parent);
}
rcu_read_unlock();
}
msg = msg_new_with_alloc_flags(FP_SI_OT_NOTIFY_PROCESS_EXEC, 0, SI_CT_POST_CALLBACK, make_unique_pid(p), event_size, nowait);
if (msg) {
si_property_writer_t writer;
task_info_t* parent_task_info = has_parent ? task_info_map_add_with_alloc_flags(parent_tgid, parent_upid, NULL /*no exe update*/, parent_start_time.microseconds, nowait, false /*no force advance pv*/) : NULL;
task_info_t* task_info = task_info_map_add_with_alloc_flags(tgid, make_unique_pid(p), exe, start_time.microseconds, nowait, true /*force advance pv*/);
uint64_t pid_version = task_info ? READ_ONCE(task_info->pid_version) : 0;
uint64_t parent_pid_version = parent_task_info ? READ_ONCE(parent_task_info->pid_version) : 0;
uint64_t event_uid = transport_global_sequence_next();
si_event_writer_init(&writer, &msg->event, event_size);
si_property_writer_write_common(&writer, event_uid, pid, tgid, task_info);
si_property_writer_write_process_start_timestamp(&writer, start_time);
if (has_parent) {
si_property_writer_write_parent_process_id(&writer, parent_tgid);
si_property_writer_write_parent_thread_id(&writer, parent_pid);
si_property_writer_write_parent_process_uid(&writer, parent_upid);
si_property_writer_write_parent_process_id_version(&writer, parent_pid_version);
si_property_writer_write_parent_process_start_timestamp(&writer, parent_start_time);
}
if (exe)
si_property_writer_write_exe_file(&writer, &path_info, exe, &handle_info);
else
si_property_writer_write_exe_comm(&writer, comm);
si_property_writer_write_unix_exec_type(&writer, SI_UNIX_EXEC_TYPE_EXEC);
if (task_info) {
SiTimeMicroseconds t;
t.microseconds = READ_ONCE(task_info->artificial_start_time_us);
si_property_writer_write_artificial_process_start_timestamp(&writer, t);
}
if (parent_task_info) {
SiTimeMicroseconds t;
t.microseconds = READ_ONCE(parent_task_info->artificial_start_time_us);
si_property_writer_write_parent_artificial_process_start_timestamp(&writer, t);
task_info_put(parent_task_info);
}
si_property_writer_write_task_creds(&writer, p);
si_property_writer_write_cgroup(&writer, p, SI_CONTAINER_NAME_LIMIT);
if (has_command_line) {
void* data = si_property_writer_peek(&writer, FP_SI_PI_COMMAND_LINE, command_line_length);
if (data) {
if (0 != command_line_length) {
int acquired_command_line_length = command_line_length - copy_from_user(data, (void*)mm->arg_start, command_line_length);
if (acquired_command_line_length)
{
si_property_writer_commit(&writer, FP_SI_PI_COMMAND_LINE, acquired_command_line_length);
}
} else {
si_property_writer_commit(&writer, FP_SI_PI_COMMAND_LINE, 0);
}
}
si_property_writer_write_command_line_cache_truncated(&writer, command_line_length_truncated);
}
if (cwd_path_info.buf)
si_property_writer_write_current_working_directory(&writer, cwd_path_info.str);
si_event_writer_finalize(&msg->event, &writer);
msg->task_info = task_info;
msg->exec.pid_version = pid_version;
}
file_handle_info_free(&handle_info);
path_info_free(&path_info);
path_info_free(&cwd_path_info);
if (msg) {
send_msg_async(msg);
msg_unref(msg);
}
}
void exec_event_nowait(struct task_struct *p, struct linux_binprm *bprm)
{
(void) bprm;
exec_event_with_alloc_flags(p, true /*nowait*/);
}
void exec_event(struct task_struct *p)
{
exec_event_with_alloc_flags(p, false /*nowait*/);
}
|