Merged PR 776479: Add support for parsing argv on exec in ptrace sandbox

- Reports the process command line with ptrace
This commit is contained in:
Pasindu Gunasekara 🍣 2024-03-30 02:40:34 +00:00
Родитель d0844d605f
Коммит a8fe896fb7
4 изменённых файлов: 111 добавлений и 14 удалений

Просмотреть файл

@ -9,6 +9,7 @@
#include <sys/ptrace.h> #include <sys/ptrace.h>
#include <sys/reg.h> #include <sys/reg.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/user.h>
#define SYSCALL_NAME_TO_NUMBER(name) __NR_##name #define SYSCALL_NAME_TO_NUMBER(name) __NR_##name
#define SYSCALL_NAME_STRING(name) #name #define SYSCALL_NAME_STRING(name) #name
@ -341,51 +342,80 @@ void *PTraceSandbox::GetArgumentAddr(int index)
return (void *)addr; return (void *)addr;
} }
unsigned long long PTraceSandbox::ArgumentIndexToRegister(int index, struct user_regs_struct *regs)
{
// Order of the arguments on the stack: %rdi, %rsi, %rdx, %rcx, %r8, and %r9
// Reference: http://6.s081.scripts.mit.edu/sp18/x86-64-architecture-guide.html#:~:text=The%20caller%20uses%20registers%20to,off%20the%20stack%20in%20order.
switch (index)
{
case 0: // Return value
return regs->rax;
case 1:
return regs->rdi;
case 2:
return regs->rsi;
case 3:
return regs->rdx;
case 4:
return regs->r10;
case 5:
return regs->r8;
case 6:
return regs->r9;
default:
// We don't currently support reading more than the 6 arguments above with ptrace
return 0;
}
}
std::string PTraceSandbox::ReadArgumentString(char *syscall, int argumentIndex, bool nullTerminated, int length) std::string PTraceSandbox::ReadArgumentString(char *syscall, int argumentIndex, bool nullTerminated, int length)
{ {
void *addr = GetArgumentAddr(argumentIndex); void *addr = GetArgumentAddr(argumentIndex);
// We are only interested in reading paths from the arguments so PATH_MAX (+1 for null terminator) should be safe to use here
char argument[PATH_MAX + 1];
char *addrRegValue = (char *)ptrace(PTRACE_PEEKUSER, m_traceePid, addr, 0); char *addrRegValue = (char *)ptrace(PTRACE_PEEKUSER, m_traceePid, addr, 0);
return ReadArgumentStringAtAddr(syscall, addrRegValue, nullTerminated, length);
}
std::string PTraceSandbox::ReadArgumentStringAtAddr(char *syscall, char *addr, bool nullTerminated, int length) {
int currentStringLength = 0; int currentStringLength = 0;
std::string argument;
argument.reserve(PATH_MAX); // We are mostly interested in reading paths from the arguments so PATH_MAX should be enough here for most cases
while (true) while (true)
{ {
long addrMemoryLocation = ptrace(PTRACE_PEEKTEXT, m_traceePid, addrRegValue, NULL); long addrMemoryLocation = ptrace(PTRACE_PEEKTEXT, m_traceePid, addr, NULL);
if (addrMemoryLocation == -1) if (addrMemoryLocation == -1)
{ {
BXL_LOG_DEBUG(m_bxl, "[PTrace] Error occured while executing PTRACE_PEEKTEXT for syscall '%s' argument '%d' : '%s'", syscall, argumentIndex, strerror(errno)); BXL_LOG_DEBUG(m_bxl, "[PTrace] Error occured while executing PTRACE_PEEKTEXT for syscall '%s' : '%s'", syscall, strerror(errno));
argument[currentStringLength] = '\0';
break; break;
} }
addrRegValue += sizeof(long); addr += sizeof(long);
char *currentArgReadChar = (char *)&addrMemoryLocation; char *currentArgReadChar = (char *)&addrMemoryLocation;
bool finishedReadingArgument = false; bool finishedReadingArgument = false;
for (int i = 0; i < sizeof(long); i++) for (int i = 0; i < sizeof(long); i++)
{ {
argument[currentStringLength] = *currentArgReadChar;
currentStringLength++;
if ((nullTerminated && *currentArgReadChar == '\0') || (length > 0 && currentStringLength == length)) if ((nullTerminated && *currentArgReadChar == '\0') || (length > 0 && currentStringLength == length))
{ {
finishedReadingArgument = true; finishedReadingArgument = true;
break; break;
} }
argument.push_back(*currentArgReadChar);
currentStringLength++;
currentArgReadChar++; currentArgReadChar++;
} }
if (finishedReadingArgument) if (finishedReadingArgument)
{ {
argument[currentStringLength] = '\0';
break; break;
} }
} }
return std::string(argument); return argument;
} }
unsigned long PTraceSandbox::ReadArgumentLong(int argumentIndex) unsigned long PTraceSandbox::ReadArgumentLong(int argumentIndex)
@ -394,6 +424,40 @@ unsigned long PTraceSandbox::ReadArgumentLong(int argumentIndex)
return ptrace(PTRACE_PEEKUSER, m_traceePid, addr, NULL); return ptrace(PTRACE_PEEKUSER, m_traceePid, addr, NULL);
} }
std::string PTraceSandbox::ReadArgumentVector(char *syscall, int argumentIndex)
{
struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, m_traceePid, 0, &regs);
auto addr = ArgumentIndexToRegister(argumentIndex, &regs); // Pointer to argv
bool firstArgument = true;
std::string arguments;
arguments.reserve(PATH_MAX);
while (true) {
// Pointer to each individual element in the argv array
auto argPtr = ptrace(PTRACE_PEEKTEXT, m_traceePid, addr, NULL);
if (argPtr == -1) {
BXL_LOG_DEBUG(m_bxl, "[PTrace] Error occured while parsing arguments for syscall '%s' with error %s", syscall, strerror(errno));
break;
}
if (argPtr == 0) {
// End of the argv array
break;
}
arguments.append((!firstArgument ? " " : "") + ReadArgumentStringAtAddr(syscall, (char *)argPtr, /* nullTerminated */ true, /* length */ 0));
addr += sizeof(unsigned long long);
if (firstArgument) {
firstArgument = false;
}
}
return arguments;
}
int PTraceSandbox::GetErrno() int PTraceSandbox::GetErrno()
{ {
long returnValue = ReadArgumentLong(0); long returnValue = ReadArgumentLong(0);
@ -563,6 +627,10 @@ HANDLER_FUNCTION(execveat)
UpdateTraceeTableForExec(exePath); UpdateTraceeTableForExec(exePath);
m_bxl->report_exec(SYSCALL_NAME_STRING(execveat), basename(mutableExePath), exePath.c_str(), /* error*/ 0, /* mode */ 0, m_traceePid); m_bxl->report_exec(SYSCALL_NAME_STRING(execveat), basename(mutableExePath), exePath.c_str(), /* error*/ 0, /* mode */ 0, m_traceePid);
if (m_bxl->IsReportingProcessArgs()) {
m_bxl->report_exec_args(m_traceePid, ReadArgumentVector(SYSCALL_NAME_STRING(execveat), /* argumentIndex */ 3).c_str());
}
} }
HANDLER_FUNCTION(execve) HANDLER_FUNCTION(execve)
@ -575,6 +643,9 @@ HANDLER_FUNCTION(execve)
UpdateTraceeTableForExec(file); UpdateTraceeTableForExec(file);
m_bxl->report_exec(SYSCALL_NAME_STRING(execve), basename(mutableFilePath), file.c_str(), /* error */ 0, /* mode */ 0, m_traceePid); m_bxl->report_exec(SYSCALL_NAME_STRING(execve), basename(mutableFilePath), file.c_str(), /* error */ 0, /* mode */ 0, m_traceePid);
if (m_bxl->IsReportingProcessArgs()) {
m_bxl->report_exec_args(m_traceePid, ReadArgumentVector(SYSCALL_NAME_STRING(execve), /* argumentIndex */ 2).c_str());
}
} }
HANDLER_FUNCTION(stat) HANDLER_FUNCTION(stat)

Просмотреть файл

@ -61,8 +61,18 @@ private:
void *GetArgumentAddr(int index); void *GetArgumentAddr(int index);
/*
* Given a set of registers from PTRACE_GETREGS, this function will return the value of the argument at the given index.
*/
static unsigned long long ArgumentIndexToRegister(int index, struct user_regs_struct *regs);
// @brief Gets the offset to read an argument at a given index starting from 1 (0 is used for the return value of the function) // @brief Gets the offset to read an argument at a given index starting from 1 (0 is used for the return value of the function)
std::string ReadArgumentString(char *syscall, int argumentIndex, bool nullTerminated, int length = 0); std::string ReadArgumentString(char *syscall, int argumentIndex, bool nullTerminated, int length = 0);
/*
* Gets a string at the provided address.
*/
std::string ReadArgumentStringAtAddr(char *syscall, char *addr, bool nullTerminated, int length);
/* /*
* @brief Reads an argument string at a given address with ptrace * @brief Reads an argument string at a given address with ptrace
* @param argumentIndex Index of the argument to read starting from 1 (or 0 for the return value) * @param argumentIndex Index of the argument to read starting from 1 (or 0 for the return value)
@ -71,6 +81,12 @@ private:
* @return String containing the argument * @return String containing the argument
*/ */
unsigned long ReadArgumentLong(int argumentIndex); unsigned long ReadArgumentLong(int argumentIndex);
/**
* Reads a set of arguments provided to an execve or an execveat call.
*/
std::string ReadArgumentVector(char *syscall, int argumentIndex);
void ReportOpen(std::string path, int oflag, std::string syscallName); void ReportOpen(std::string path, int oflag, std::string syscallName);
void ReportCreate(std::string syscallName, int dirfd, const char *pathname, mode_t mode, long returnValue = 0, bool checkCache = true); void ReportCreate(std::string syscallName, int dirfd, const char *pathname, mode_t mode, long returnValue = 0, bool checkCache = true);
int GetErrno(); int GetErrno();

Просмотреть файл

@ -584,6 +584,13 @@ void BxlObserver::report_exec_args(pid_t pid)
} }
close(fd); close(fd);
report_exec_args(pid, cmdLine.c_str());
}
}
void BxlObserver::report_exec_args(pid_t pid, const char *args) {
if (IsReportingProcessArgs())
{
AccessReport report = AccessReport report =
{ {
.operation = kOpProcessCommandLine, .operation = kOpProcessCommandLine,
@ -600,7 +607,8 @@ void BxlObserver::report_exec_args(pid_t pid)
.shouldReport = true, .shouldReport = true,
}; };
strlcpy(report.path, cmdLine.c_str(), sizeof(report.path)); // This may truncate the path but that's okay because it's the process command line.
strlcpy(report.path, args, sizeof(report.path));
SendReport(report, /* isDebugMessage */ false, /* useSecondaryPipe */ false); SendReport(report, /* isDebugMessage */ false, /* useSecondaryPipe */ false);
} }
} }

Просмотреть файл

@ -297,7 +297,6 @@ private:
bool IsMonitoringChildProcesses() const { return !pip_ || CheckMonitorChildProcesses(pip_->GetFamFlags()); } bool IsMonitoringChildProcesses() const { return !pip_ || CheckMonitorChildProcesses(pip_->GetFamFlags()); }
bool IsPTraceEnabled() const { return pip_ && (CheckEnableLinuxPTraceSandbox(pip_->GetFamExtraFlags()) || CheckUnconditionallyEnableLinuxPTraceSandbox(pip_->GetFamExtraFlags())); } bool IsPTraceEnabled() const { return pip_ && (CheckEnableLinuxPTraceSandbox(pip_->GetFamExtraFlags()) || CheckUnconditionallyEnableLinuxPTraceSandbox(pip_->GetFamExtraFlags())); }
bool IsPTraceForced(const char *path); bool IsPTraceForced(const char *path);
bool IsReportingProcessArgs() const { return !pip_ || CheckReportProcessArgs(pip_->GetFamFlags()); }
inline bool IsValid() const { return sandbox_ != NULL; } inline bool IsValid() const { return sandbox_ != NULL; }
inline bool IsEnabled(pid_t pid) const inline bool IsEnabled(pid_t pid) const
@ -378,8 +377,11 @@ public:
const char* GetSecondaryReportsPath() { return secondaryReportPath_; } const char* GetSecondaryReportsPath() { return secondaryReportPath_; }
const char* GetDetoursLibPath() { return detoursLibFullPath_; } const char* GetDetoursLibPath() { return detoursLibFullPath_; }
bool IsReportingProcessArgs() const { return !pip_ || CheckReportProcessArgs(pip_->GetFamFlags()); }
void report_exec(const char *syscallName, const char *procName, const char *file, int error, mode_t mode = 0, pid_t associatedPid = 0); void report_exec(const char *syscallName, const char *procName, const char *file, int error, mode_t mode = 0, pid_t associatedPid = 0);
void report_exec_args(pid_t pid); void report_exec_args(pid_t pid);
void report_exec_args(pid_t pid, const char *args);
void report_audit_objopen(const char *fullpath) void report_audit_objopen(const char *fullpath)
{ {
IOEvent event(ES_EVENT_TYPE_NOTIFY_OPEN, ES_ACTION_TYPE_NOTIFY, fullpath, progFullPath_, S_IFREG); IOEvent event(ES_EVENT_TYPE_NOTIFY_OPEN, ES_ACTION_TYPE_NOTIFY, fullpath, progFullPath_, S_IFREG);