OMS-Auditd-Plugin/AuditRules.cpp

2251 строка
74 KiB
C++

/*
microsoft-oms-auditd-plugin
Copyright (c) Microsoft Corporation
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "AuditRules.h"
#include "Translate.h"
#include "StringUtils.h"
#include "KernelInfo.h"
#include "FileUtils.h"
#include "UserDB.h"
#include <algorithm>
#include <sstream>
#include <unistd.h>
#include <climits>
#include <system_error>
#include <pwd.h>
#include <fcntl.h>
#include <iostream>
// Character that seperates key in AUDIT_FILTERKEY field in rules
// This value mirrors what is defined for AUDIT_KEY_SEPARATOR in libaudit.h
#define KEY_SEP 0x01
// This value mirrors what is defined for AUDIT_FILTER_MASK in libaudit.h
// It is a mask of AUDIT_FILTER_* values defined in /usr/include/linux/audit.h
// AUDIT_FILTER_PREPEND is excluded from thye mask.
#define FILTER_MASK 0x7
#define AUDITD_RULES_FILE "/etc/audit/audit.rules"
#define AUDITD_RULES_DIR "/etc/audit/rules.d"
#define AUOMS_RULES_FILE_NAME "auoms.rules"
#define AUOMS_AUDITD_RULES_FILE "/etc/audit/rules.d/auoms.rules"
#define AUGENRULES_HEADER "## This file is automatically generated from"
#define AUOMS_AUDITD_RULES_FILE_START_MARKER "#### START OF OMS AUDIT RULES ####"
#define AUOMS_AUDITD_RULES_FILE_END_MARKER "#### END OF OMS AUDIT RULES ####"
static std::string clean_path(const std::string& path) {
std::string ret = path;
while(ret.back() == '/') {
ret.resize(ret.size()-1);
}
return ret;
}
static bool check_path(const std::string& path, std::string error)
{
if (path.size() >= PATH_MAX) {
error = "path is too big";
return false;
}
if (path[0] != '/') {
error = "path must start with '/'";
return false;
}
auto idx = path.rfind('/');
if (idx == std::string::npos) {
return true;
}
if (path.size()-idx > NAME_MAX) {
error = "The base name of the path is too big";
return false;
}
if (path.find("..") != std::string::npos) {
error = "relative path notation is not supported";
return false;
}
if (path.find("*") != std::string::npos || path.find("?") != std::string::npos) {
error = "wildcard notation is not supported";
return false;
}
return true;
}
bool AuditRule::IsDataValid(const void* data, size_t len) {
auto rule = reinterpret_cast<const audit_rule_data*>(data);
if (len < sizeof(audit_rule_data) || len < sizeof(audit_rule_data)+rule->buflen || rule->field_count >= AUDIT_MAX_FIELDS) {
return false;
}
uint32_t offset = 0;
for (int i = 0; i < rule->field_count; ++i) {
switch (rule->fields[i] & ~AUDIT_OPERATORS) {
case AUDIT_SUBJ_USER:
/* fallthrough */
case AUDIT_SUBJ_ROLE:
/* fallthrough */
case AUDIT_SUBJ_TYPE:
/* fallthrough */
case AUDIT_SUBJ_SEN:
/* fallthrough */
case AUDIT_SUBJ_CLR:
/* fallthrough */
case AUDIT_OBJ_USER:
/* fallthrough */
case AUDIT_OBJ_ROLE:
/* fallthrough */
case AUDIT_OBJ_TYPE:
/* fallthrough */
case AUDIT_OBJ_LEV_LOW:
/* fallthrough */
case AUDIT_OBJ_LEV_HIGH:
/* fallthrough */
case AUDIT_WATCH:
/* fallthrough */
case AUDIT_DIR:
/* fallthrough */
case AUDIT_EXE:
/* fallthrough */
case AUDIT_FILTERKEY:
offset += rule->values[i];
break;
}
}
if (offset > len) {
return false;
}
return true;
}
bool AuditRule::Parse(const std::string& text, std::string& error) {
error.clear();
auto clean_text = trim_whitespace(text);
if (clean_text.empty() || clean_text[0] == '#') {
return false;
}
auto args = split(text, ' ');
if (args.empty()) {
return false;
}
if (args[0].size() < 2 || args[0][0] != '-') {
error.append("Invalid option '");
error.append(args[0]);
error.append("'");
return false;
}
bool watch = false;
char rule_opt = args[0][1];
if (rule_opt == 'w' || rule_opt == 'W') {
watch = true;
SetSyscallAll();
if (rule_opt == 'W') {
is_delete_rule = true;
}
if (!parse_add_w_arg(args[1], error)) {
return false;
}
} else if (rule_opt != 'a' && rule_opt != 'A' && rule_opt != 'd') {
return false;
} else {
if (rule_opt == 'A') {
ruleptr()->flags |= AUDIT_FILTER_PREPEND;
} else if (rule_opt == 'd') {
is_delete_rule = true;
}
if (!parse_add_a_arg(args[1], error)) {
return false;
}
}
size_t idx = 2;
while(idx < args.size()) {
if (args[idx].size() < 2 || args[idx][0] != '-') {
error.append("Invalid option '");
error.append(args[idx]);
error.append("'");
return false;
}
if (idx+1 >= args.size()) {
error.append("Missing value for option '");
error.append(args[idx]);
error.append("'");
return false;
}
switch (args[idx][1]) {
case 'A':
/* fallthrough */
case 'd':
/* fallthrough */
case 'a':
error.append("The '-");
error.push_back(args[idx][1]);
error.append("' option is not allowed with the '-");
error.push_back(rule_opt);
error.append("' option");
return false;
case 'W':
/* fallthrough */
case 'w':
error.append("The '-");
error.push_back(args[idx][1]);
error.append("' option is not allowed with the '-");
error.push_back(rule_opt);
error.append("' option");
return false;
case 'p':
if (!watch) {
error.append("The '-p' option is not allowed with the '-");
error.push_back(rule_opt);
error.append("' option");
return false;
}
if (!parse_add_p_arg(args[idx+1], error)) {
return false;
}
idx += 2;
break;
case 'k':
if (!parse_add_k_arg(args[idx+1], error)) {
return false;
}
idx += 2;
break;
case 'S':
if (watch) {
error.append("The '-S' option is not allowed with the '-");
error.push_back(rule_opt);
error.append("' option");
return false;
}
if ((ruleptr()->flags & FILTER_MASK) == AUDIT_FILTER_TASK) {
error.append("The '-S' option is not allowed with the task rules");
return false;
}
if (!parse_add_S_arg(args[idx+1], error)) {
return false;
}
idx += 2;
break;
case 'F':
if (watch) {
error.append("The '-F' option is not allowed with the '-");
error.push_back(rule_opt);
error.append("' option");
return false;
}
if (args[idx].size() > 2) {
if (!parse_add_F_arg(args[idx].substr(2), error)) {
return false;
}
idx += 1;
} else {
if (!parse_add_F_arg(args[idx+1], error)) {
return false;
}
idx += 2;
}
break;
case 'C':
if (watch) {
error.append("The '-C' option is not allowed with the '-");
error.push_back(rule_opt);
error.append("' option");
return false;
}
if (!parse_add_C_arg(args[idx+1], error)) {
return false;
}
idx += 2;
break;
default:
error.append("Unsupported option '");
error.append(args[idx]);
error.append("'");
return false;
}
}
return true;
}
std::unordered_map<std::string, uint32_t> s_a_actions(
{
{"never", AUDIT_NEVER},
// The value "possible" while present in the kernel code will result in a deprecation error.
{"always", AUDIT_ALWAYS},
}
);
std::unordered_map<std::string, uint32_t> s_a_flags(
{
{"task", AUDIT_FILTER_TASK},
{"exit", AUDIT_FILTER_EXIT},
{"user", AUDIT_FILTER_USER},
{"exclude", AUDIT_FILTER_TYPE},
}
);
bool AuditRule::parse_add_a_arg(const std::string& val, std::string& error) {
if (ruleptr()->action != 0 || (ruleptr()->flags & FILTER_MASK) != 0) {
error.append("Multiple '-a' not allowed");
return false;
}
auto parts = split(val, ',');
if (parts.size() != 2) {
error.append("Invalid value for option '-a': '");
error.append(val);
error.append("'");
return false;
}
bool action_found = false;
for (auto& part: parts) {
auto itr = s_a_actions.find(part);
if (itr != s_a_actions.end()) {
ruleptr()->action = itr->second;
action_found = true;
}
}
if (!action_found) {
error.append("Invalid or missing action value for option '-a': '");
error.append(val);
error.append("'");
return false;
}
bool flags_found = false;
for (auto& part: parts) {
auto itr = s_a_flags.find(part);
if (itr != s_a_flags.end()) {
ruleptr()->flags |= itr->second;
flags_found = true;
}
}
if (!flags_found) {
error.append("Invalid or missing flags value for option '-a': '");
error.append(val);
error.append("'");
return false;
}
return true;
}
bool AuditRule::parse_add_w_arg(const std::string& val, std::string& error) {
if (has_field(AUDIT_WATCH) || has_field(AUDIT_DIR)) {
error.append("Multiple '-w' not allowed");
return false;
}
auto path = clean_path(val);
if (!check_path(path, error)) {
error = "Invalid path for -w option: " + error;
return false;
}
ruleptr()->flags = AUDIT_FILTER_EXIT;
ruleptr()->action = AUDIT_ALWAYS;
if (IsDir(path)) {
add_str_field(AUDIT_DIR, AUDIT_EQUAL, path);
} else {
add_str_field(AUDIT_WATCH, AUDIT_EQUAL, path);
}
return true;
}
bool AuditRule::parse_add_p_arg(const std::string& val, std::string& error) {
uint32_t perms = 0;
for (auto c: val) {
switch (c) {
case 'a':
perms |= AUDIT_PERM_ATTR;
break;
case 'r':
perms |= AUDIT_PERM_READ;
break;
case 'w':
perms |= AUDIT_PERM_WRITE;
break;
case 'x':
perms |= AUDIT_PERM_EXEC;
break;
default:
error.append("Invalid value for option '-p': '");
error.append(val);
error.append("'");
return false;
}
}
add_field(AUDIT_PERM, AUDIT_EQUAL, perms);
return true;
}
bool AuditRule::parse_add_S_arg(const std::string& val, std::string& error) {
if (val == "all") {
SetSyscallAll();
return true;
}
auto arch = get_arch();
if (arch == 0 ) {
error.append("Missing arch: Specify an arch value (via -F) prior to specifying any -S");
return false;
}
auto mtype = ArchToMachine(arch);
if (mtype == MachineType::UNKNOWN) {
error.append("Previously specified arch (via -F) is invalid");
return false;
}
auto parts = split(val, ',');
for (auto& part: parts) {
if (part.find_first_not_of("0123456789") == std::string::npos) {
auto scall = std::atoi(part.c_str());
AddSyscall(scall);
} else {
auto scall = SyscallNameToNumber(mtype, part);
if (scall < 0) {
error.append("Invalid value for option '-S': Invalid syscall name: '");
error.append(part);
error.append("'");
}
AddSyscall(scall);
}
}
return true;
}
std::unordered_map<std::string, uint32_t> s_F_ops(
{
{"=", AUDIT_EQUAL},
{"!=", AUDIT_NOT_EQUAL},
{">", AUDIT_GREATER_THAN},
{">=", AUDIT_GREATER_THAN_OR_EQUAL},
{"<", AUDIT_LESS_THAN},
{"<=", AUDIT_LESS_THAN_OR_EQUAL},
{"&", AUDIT_BIT_MASK},
{"&=", AUDIT_BIT_TEST},
}
);
std::unordered_map<std::string, uint32_t> s_F_ftypes(
{
{"socket", S_IFSOCK},
{"link", S_IFLNK},
{"file", S_IFREG},
{"block", S_IFBLK},
{"dir", S_IFDIR},
{"character", S_IFCHR},
{"fifo", S_IFIFO},
}
);
bool AuditRule::parse_add_F_arg(const std::string& val, std::string& error) {
auto idx = val.find_first_of("=!<>&");
if (idx == 0) {
error.append("Invalid value for option '-F': Missing field name: '");
error.append(val);
error.append("'");
return false;
}
if (idx == std::string::npos) {
error.append("Invalid value for option '-F': Missing operator: '");
error.append(val);
error.append("'");
return false;
}
auto field_name = val.substr(0, idx);
auto eidx = val.find_first_not_of("=!<>&", idx);
if (eidx == std::string::npos) {
error.append("Invalid value for option '-F': Missing value: '");
error.append(val);
error.append("'");
return false;
}
auto op_str = val.substr(idx, eidx-idx);
auto itr = s_F_ops.find(op_str);
if (itr == s_F_ops.end()) {
error.append("Invalid value for option '-F': Invalid operator: '");
error.append(val);
error.append("'");
return false;
}
uint32_t op = itr->second;
auto value = val.substr(eidx);
if (value.empty()) {
error.append("Invalid value for option '-F': Missing value: '");
error.append(val);
error.append("'");
return false;
}
auto field = FieldNameToId(field_name);
if (field < 0) {
error.append("Invalid value for option '-F': Invalid field name: '");
error.append(val);
error.append("'");
return false;
}
switch (field) {
case AUDIT_UID:
case AUDIT_EUID:
case AUDIT_SUID:
case AUDIT_FSUID:
case AUDIT_LOGINUID:
case AUDIT_OBJ_UID:
try {
if (std::isdigit(value[0])) {
uint32_t v = static_cast<uint32_t>(stoul(value, 0, 0));
add_field(field, op, v);
} else if (value.size() > 1 && value[0] == '-' && std::isdigit(value[1])) {
uint32_t v = static_cast<uint32_t>(stol(value, 0, 0));
add_field(field, op, v);
} else {
if (value == "unset") {
add_field(field, op, 4294967295);
} else {
auto uid = UserDB::UserNameToUid(value);
if (uid < 0) {
error.append("Invalid value for option '-F': Unknown username: '");
error.append(val);
error.append("'");
return false;
}
add_field(field, op, uid);
}
}
} catch (std::exception &) {
error.append("Invalid value for option '-F': Invalid numeric value: '");
error.append(val);
error.append("'");
return false;
}
break;
case AUDIT_GID:
case AUDIT_EGID:
case AUDIT_SGID:
case AUDIT_FSGID:
case AUDIT_OBJ_GID:
try {
if (std::isdigit(value[0])) {
uint32_t v = static_cast<uint32_t>(stoul(value, 0, 0));
add_field(field, op, v);
} else if (value.size() > 1 && value[0] == '-' && std::isdigit(value[1])) {
uint32_t v = static_cast<uint32_t>(stol(value, 0, 0));
add_field(field, op, v);
} else {
if (value == "unset") {
add_field(field, op, 4294967295);
} else {
auto gid = UserDB::GroupNameToGid(value);
if (gid < 0) {
error.append("Invalid value for option '-F': Unknown group name: '");
error.append(val);
error.append("'");
return false;
}
add_field(field, op, gid);
}
}
} catch (std::exception &) {
error.append("Invalid value for option '-F': Invalid numeric value: '");
error.append(val);
error.append("'");
return false;
}
break;
case AUDIT_EXIT:
if ((ruleptr()->flags & FILTER_MASK) != AUDIT_FILTER_EXIT) {
error.append("Invalid value for option '-F': Cannot filter on exit field unless flags (-a option) == 'exit'");
return false;
}
try {
if (std::isdigit(value[0])) {
uint32_t v = static_cast<uint32_t>(stoul(value, 0, 0));
add_field(field, op, v);
} else if (value.size() > 1 && value[0] == '-' && std::isdigit(value[1])) {
uint32_t v = static_cast<uint32_t>(stol(value, 0, 0));
add_field(field, op, v);
} else {
auto v = NameToErrno(value);
if (v == 0) {
error.append("Invalid value for option '-F': Invalid errno name: '");
error.append(val);
error.append("'");
return false;
}
add_field(field, op, v);
}
} catch (std::exception &) {
error.append("Invalid value for option '-F': Invalid numeric value: '");
error.append(val);
error.append("'");
return false;
}
break;
case AUDIT_MSGTYPE:
if ((ruleptr()->flags & FILTER_MASK) != AUDIT_FILTER_TYPE) {
error.append("Invalid value for option '-F': Cannot filter on msg type unless flags (-a option) == 'exclude'");
return false;
}
if (std::isdigit(value[0])) {
try {
uint32_t v = static_cast<uint32_t>(stoul(value, 0, 0));
add_field(field, op, v);
} catch (std::exception &) {
error.append("Invalid value for option '-F': Invalid numeric value: '");
error.append(val);
error.append("'");
return false;
}
} else {
auto rt = RecordNameToType(value);
if (rt == RecordType::UNKNOWN) {
error.append("Invalid value for option '-F': Invalid record type name: '");
error.append(val);
error.append("'");
return false;
}
add_field(field, op, static_cast<uint32_t>(rt));
}
break;
case AUDIT_ARCH: {
auto arch = ArchNameToArch(value);
if (arch == 0) {
error.append("Invalid value for option '-F': Invalid arch value: '");
error.append(val);
error.append("'");
}
if (op != AUDIT_EQUAL && op != AUDIT_NOT_EQUAL) {
error.append("Invalid op(");
error.append(op_str);
error.append(") for option '-F': arch field only allows '=' and '!='");
}
add_field(field, op, arch);
break;
}
case AUDIT_PERM: {
uint32_t perms = 0;
for (auto c: value) {
switch (c) {
case 'a':
perms |= AUDIT_PERM_ATTR;
break;
case 'r':
perms |= AUDIT_PERM_READ;
break;
case 'w':
perms |= AUDIT_PERM_WRITE;
break;
case 'x':
perms |= AUDIT_PERM_EXEC;
break;
default:
error.append("Invalid value for option '-F': Invalid permission character: '");
error.append(val);
error.append("'");
return false;
}
}
add_field(AUDIT_PERM, op, perms);
break;
}
case AUDIT_OBJ_USER:
/* fallthrough */
case AUDIT_OBJ_ROLE:
/* fallthrough */
case AUDIT_OBJ_TYPE:
/* fallthrough */
case AUDIT_OBJ_LEV_LOW:
/* fallthrough */
case AUDIT_OBJ_LEV_HIGH:
if (ruleptr()->flags != AUDIT_FILTER_EXIT) {
error = "Field '" + field_name + "' can only be used with 'exit' filter";
return false;
}
add_str_field(field, op, value);
break;
case AUDIT_WATCH:
/* fallthrough */
case AUDIT_DIR: {
if (ruleptr()->flags != AUDIT_FILTER_EXIT) {
error = "Field '" + field_name + "' can only be used with 'exit' filter";
return false;
}
auto path = clean_path(value);
if (!check_path(path, error)) {
error = "Invalid path option: " + error;
return false;
}
add_str_field(field, op, path);
break;
}
case AUDIT_SUBJ_USER:
/* fallthrough */
case AUDIT_SUBJ_ROLE:
/* fallthrough */
case AUDIT_SUBJ_TYPE:
/* fallthrough */
case AUDIT_SUBJ_SEN:
/* fallthrough */
case AUDIT_SUBJ_CLR:
/* fallthrough */
case AUDIT_EXE:
add_str_field(field, op, value);
break;
case AUDIT_FILTERKEY:
AddKey(value);
break;
case AUDIT_FILETYPE: {
if (ruleptr()->flags != AUDIT_FILTER_EXIT) {
error = "Field '" + field_name + "' can only be used with 'exit' filter";
return false;
}
auto ft = s_F_ftypes.find(value);
if (ft != s_F_ftypes.end()) {
add_field(field, op, ft->second);
} else {
error.append("Invalid value for option '-F': Invalid filetype name: '");
error.append(val);
error.append("'");
return false;
}
break;
}
default:
try {
uint32_t v = static_cast<uint32_t>(stol(value, 0, 0));
add_field(field, op, v);
} catch (std::exception&) {
error.append("Invalid value for option '-F': Invalid numeric value: '");
error.append(val);
error.append("'");
return false;
}
}
return true;
}
std::unordered_map<uint64_t, uint32_t> s_C_fields(
{
{(static_cast<uint64_t>(AUDIT_UID)<<32)|AUDIT_OBJ_UID, AUDIT_COMPARE_UID_TO_OBJ_UID},
{(static_cast<uint64_t>(AUDIT_GID)<<32)|AUDIT_OBJ_GID, AUDIT_COMPARE_GID_TO_OBJ_GID},
{(static_cast<uint64_t>(AUDIT_EUID)<<32)|AUDIT_OBJ_UID, AUDIT_COMPARE_EUID_TO_OBJ_UID},
{(static_cast<uint64_t>(AUDIT_EGID)<<32)|AUDIT_OBJ_GID, AUDIT_COMPARE_EGID_TO_OBJ_GID},
{(static_cast<uint64_t>(AUDIT_LOGINUID)<<32)|AUDIT_OBJ_UID, AUDIT_COMPARE_AUID_TO_OBJ_UID},
{(static_cast<uint64_t>(AUDIT_SUID)<<32)|AUDIT_OBJ_UID, AUDIT_COMPARE_SUID_TO_OBJ_UID},
{(static_cast<uint64_t>(AUDIT_SGID)<<32)|AUDIT_OBJ_GID, AUDIT_COMPARE_SGID_TO_OBJ_GID},
{(static_cast<uint64_t>(AUDIT_FSUID)<<32)|AUDIT_OBJ_UID, AUDIT_COMPARE_FSUID_TO_OBJ_UID},
{(static_cast<uint64_t>(AUDIT_FSGID)<<32)|AUDIT_OBJ_GID, AUDIT_COMPARE_FSGID_TO_OBJ_GID},
{(static_cast<uint64_t>(AUDIT_UID)<<32)|AUDIT_LOGINUID, AUDIT_COMPARE_UID_TO_AUID},
{(static_cast<uint64_t>(AUDIT_UID)<<32)|AUDIT_EUID, AUDIT_COMPARE_UID_TO_EUID},
{(static_cast<uint64_t>(AUDIT_UID)<<32)|AUDIT_FSUID, AUDIT_COMPARE_UID_TO_FSUID},
{(static_cast<uint64_t>(AUDIT_UID)<<32)|AUDIT_SUID, AUDIT_COMPARE_UID_TO_SUID},
{(static_cast<uint64_t>(AUDIT_LOGINUID)<<32)|AUDIT_FSUID, AUDIT_COMPARE_AUID_TO_FSUID},
{(static_cast<uint64_t>(AUDIT_LOGINUID)<<32)|AUDIT_SUID, AUDIT_COMPARE_AUID_TO_SUID},
{(static_cast<uint64_t>(AUDIT_LOGINUID)<<32)|AUDIT_EUID, AUDIT_COMPARE_AUID_TO_EUID},
{(static_cast<uint64_t>(AUDIT_EUID)<<32)|AUDIT_SUID, AUDIT_COMPARE_EUID_TO_SUID},
{(static_cast<uint64_t>(AUDIT_EUID)<<32)|AUDIT_FSUID, AUDIT_COMPARE_EUID_TO_FSUID},
{(static_cast<uint64_t>(AUDIT_SUID)<<32)|AUDIT_FSUID, AUDIT_COMPARE_SUID_TO_FSUID},
{(static_cast<uint64_t>(AUDIT_GID)<<32)|AUDIT_EGID, AUDIT_COMPARE_GID_TO_EGID},
{(static_cast<uint64_t>(AUDIT_GID)<<32)|AUDIT_FSGID, AUDIT_COMPARE_GID_TO_FSGID},
{(static_cast<uint64_t>(AUDIT_GID)<<32)|AUDIT_SGID, AUDIT_COMPARE_GID_TO_SGID},
{(static_cast<uint64_t>(AUDIT_EGID)<<32)|AUDIT_FSGID, AUDIT_COMPARE_EGID_TO_FSGID},
{(static_cast<uint64_t>(AUDIT_EGID)<<32)|AUDIT_SGID, AUDIT_COMPARE_EGID_TO_SGID},
{(static_cast<uint64_t>(AUDIT_SGID)<<32)|AUDIT_FSGID, AUDIT_COMPARE_SGID_TO_FSGID},
}
);
bool AuditRule::parse_add_C_arg(const std::string& val, std::string& error) {
auto idx = val.find_first_of("=!<>&");
if (idx == 0) {
error.append("Invalid value for option '-C': Missing field name: '");
error.append(val);
error.append("'");
return false;
}
if (idx == std::string::npos) {
error.append("Invalid value for option '-C': Missing operator: '");
error.append(val);
error.append("'");
return false;
}
auto field_name = val.substr(0, idx);
auto eidx = val.find_first_not_of("=!<>&", idx);
if (eidx == std::string::npos) {
error.append("Invalid value for option '-C': Missing field2: '");
error.append(val);
error.append("'");
return false;
}
auto op_str = val.substr(idx, eidx-idx);
auto itr = s_F_ops.find(op_str);
if (itr == s_F_ops.end()) {
error.append("Invalid value for option '-C': Invalid operator: '");
error.append(val);
error.append("'");
return false;
}
uint32_t op = itr->second;
if (op != AUDIT_EQUAL && op != AUDIT_NOT_EQUAL) {
error.append("Invalid value for option '-C': Invalid operator: '");
error.append(val);
error.append("'");
return false;
}
auto field2_name = val.substr(eidx);
if (field2_name.empty()) {
error.append("Invalid value for option '-C': Missing field2: '");
error.append(val);
error.append("'");
return false;
}
auto field1 = FieldNameToId(field_name);
if (field1 < 0) {
error.append("Invalid value for option '-C': Invalid field name: '");
error.append(val);
error.append("'");
return false;
}
auto field2 = FieldNameToId(field2_name);
if (field2 < 0) {
error.append("Invalid value for option '-C': Invalid field name: '");
error.append(val);
error.append("'");
return false;
}
auto citr = s_C_fields.find((static_cast<uint64_t>(field1)<<32)|field2);
if (citr == s_C_fields.end()) {
error.append("Invalid value for option '-C': Invalid field name combination: '");
error.append(val);
error.append("'");
return false;
}
add_field(AUDIT_FIELD_COMPARE, op, citr->second);
return true;
}
bool AuditRule::parse_add_k_arg(const std::string& val, std::string& error) {
if (val.find(KEY_SEP) != std::string::npos) {
error = "Invalid character in key";
return false;
}
AddKey(val);
return true;
}
std::string AuditRule::CanonicalMergeKey() const {
std::string text;
auto watch = IsWatch();
if (!watch) {
if (is_delete_rule) {
text.append("-d ");
} else if (ruleptr()->flags & AUDIT_FILTER_PREPEND) {
text.append("-A ");
} else {
text.append("-a ");
}
append_action(text);
text.append(",");
append_flag(text);
for (int i = 0; i < ruleptr()->field_count; ++i) {
if ((ruleptr()->fields[i] & ~AUDIT_OPERATORS) == AUDIT_ARCH) {
append_field(text, i, watch);
}
}
}
for (int i = 0; i < ruleptr()->field_count; ++i) {
int field = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
if (field != AUDIT_ARCH && field != AUDIT_PERM && field != AUDIT_FILTERKEY) {
append_field(text, i, watch);
}
}
return text;
}
std::string AuditRule::CanonicalText() const {
std::string text;
auto watch = IsWatch();
if (!watch) {
if (is_delete_rule) {
text.append("-d ");
} else if (ruleptr()->flags & AUDIT_FILTER_PREPEND) {
text.append("-A ");
} else {
text.append("-a ");
}
append_action(text);
text.append(",");
append_flag(text);
for (int i = 0; i < ruleptr()->field_count; ++i) {
if ((ruleptr()->fields[i] & ~AUDIT_OPERATORS) == AUDIT_ARCH) {
append_field(text, i, watch);
}
}
append_syscalls(text);
}
for (int i = 0; i < ruleptr()->field_count; ++i) {
int field = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
if (field != AUDIT_ARCH) {
append_field(text, i, watch);
}
}
return text;
}
std::string AuditRule::RawText() const {
std::stringstream str;
str << "Flags: " << std::hex << ruleptr()->flags << std::endl;
str << "Action: " << std::hex << ruleptr()->action << std::endl;
str << "Syscall: " << std::endl;
for (int j = 0, i = 0; i < AUDIT_BITMASK_SIZE && j < 8; ++j) {
str << " ";
for (int x = 0; x < 8 && i < AUDIT_BITMASK_SIZE; i++, x++) {
str << " " << std::hex << ruleptr()->mask[i];
}
str << std::endl;
}
str << "FieldCount: " << std::dec << ruleptr()->field_count << std::endl;
for (int i = 0; i < ruleptr()->field_count; ++i) {
str << "Field[" << i << "]: " << std::dec << ruleptr()->fields[i] << ", " << std::hex << ruleptr()->values[i] << ", " << ruleptr()->fieldflags[i] << std::endl;
}
str << "Buflen: " << std::dec << ruleptr()->buflen << std::endl;
auto bufstr = std::string(ruleptr()->buf, ruleptr()->buflen);
auto idx = bufstr.find_first_of(0x1);
while (idx != std::string::npos) {
bufstr.replace(idx, 1, "\\0x01");
idx = bufstr.find_first_of(0x1);
}
str << "Buf: " << bufstr << std::endl;
return str.str();
}
bool AuditRule::IsValid() const {
if (!IsDataValid(_data.data(), ruleptr()->buflen+sizeof(audit_rule_data))) {
return false;
}
for (int i = 0; i < ruleptr()->field_count; ++i) {
int field = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
if (field == AUDIT_FILTERKEY && ruleptr()->values[i] == 0) {
return false;
}
}
return true;
}
bool AuditRule::IsWatch() const {
bool has_perm = false;
for (int i = 0; i < ruleptr()->field_count; ++i) {
int field = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
if (field == AUDIT_PERM) {
has_perm = true;
} else if (field != AUDIT_WATCH && field != AUDIT_DIR && field != AUDIT_FILTERKEY) {
return false;
}
}
if (!has_perm) {
return false;
}
auto filter = ruleptr()->flags & FILTER_MASK;
if (filter != AUDIT_FILTER_USER && filter != AUDIT_FILTER_TASK && filter != AUDIT_FILTER_TYPE) {
return IsSyscallAll();
}
return false;
}
bool AuditRule::IsLoadable() const {
bool is_64bit = false;
bool has_interfield_compare = false;
bool has_exe_field = false;
bool has_sessionid_field = false;
bool has_dir_field = false;
bool has_path_field = false;
for (int i = 0; i < ruleptr()->field_count; ++i) {
int field = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
switch (field) {
case AUDIT_ARCH:
if (__AUDIT_ARCH_64BIT & ruleptr()->values[i]) {
is_64bit = true;
}
break;
case AUDIT_SESSIONID:
has_sessionid_field = true;
break;
case AUDIT_FIELD_COMPARE:
has_interfield_compare = true;
break;
case AUDIT_EXE:
has_exe_field = true;
break;
case AUDIT_DIR:
has_dir_field = true;
break;
case AUDIT_WATCH:
has_path_field = true;
}
}
if (!KernelInfo::Is64bit() && is_64bit) {
return false;
}
if (!KernelInfo::HasAuditSyscall() && !IsSyscallAll()) {
return false;
}
if (!KernelInfo::HasAuditInterfieldCompare() && has_interfield_compare) {
return false;
}
if (!KernelInfo::HasAuditExeField() && has_exe_field) {
return false;
}
if (!KernelInfo::HasAuditSessionIdField() && has_sessionid_field) {
return false;
}
// The kernel audit code will refuse to load a rule if the specified dir doesn't exist.
if (has_dir_field) {
auto str = get_str_field(AUDIT_DIR);
if (!IsDir(str)) {
return false;
}
}
// The kernel audit code will refuse to load a rule if the specified path's parent dir doesn't exist.
if (has_path_field) {
auto str = get_str_field(AUDIT_WATCH);
if (!IsDir(Dirname(str))) {
return false;
}
}
return true;
}
std::unordered_set<char> AuditRule::GetPerms() const {
std::unordered_set<char> perms;
for (int i = 0; i < ruleptr()->field_count; ++i) {
int field = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
auto value = ruleptr()->values[i];
if (field == AUDIT_PERM) {
if (value & AUDIT_PERM_READ) {
perms.insert('r');
}
if (value & AUDIT_PERM_WRITE) {
perms.insert('w');
}
if (value & AUDIT_PERM_EXEC) {
perms.insert('x');
}
if (value & AUDIT_PERM_ATTR) {
perms.insert('a');
}
}
}
return perms;
}
void AuditRule::AddPerm(char perm) {
for (int i = 0; i < ruleptr()->field_count; ++i) {
int field = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
auto value = ruleptr()->values[i];
if (field == AUDIT_PERM) {
switch (perm) {
case 'a':
ruleptr()->values[i] |= AUDIT_PERM_ATTR;
break;
case 'r':
ruleptr()->values[i] |= AUDIT_PERM_READ;
break;
case 'w':
ruleptr()->values[i] |= AUDIT_PERM_WRITE;
break;
case 'x':
ruleptr()->values[i] |= AUDIT_PERM_EXEC;
break;
}
}
}
}
void AuditRule::AddPerms(const std::unordered_set<char>& perms) {
for (int i = 0; i < ruleptr()->field_count; ++i) {
int field = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
auto value = ruleptr()->values[i];
if (field == AUDIT_PERM) {
for (auto perm: perms) {
switch (perm) {
case 'a':
ruleptr()->values[i] |= AUDIT_PERM_ATTR;
break;
case 'r':
ruleptr()->values[i] |= AUDIT_PERM_READ;
break;
case 'w':
ruleptr()->values[i] |= AUDIT_PERM_WRITE;
break;
case 'x':
ruleptr()->values[i] |= AUDIT_PERM_EXEC;
break;
}
}
}
}
}
void AuditRule::SetPerms(const std::unordered_set<char>& perms) {
for (int i = 0; i < ruleptr()->field_count; ++i) {
int field = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
auto value = ruleptr()->values[i];
if (field == AUDIT_PERM) {
ruleptr()->values[i] = 0;
for (auto perm: perms) {
switch (perm) {
case 'a':
ruleptr()->values[i] |= AUDIT_PERM_ATTR;
break;
case 'r':
ruleptr()->values[i] |= AUDIT_PERM_READ;
break;
case 'w':
ruleptr()->values[i] |= AUDIT_PERM_WRITE;
break;
case 'x':
ruleptr()->values[i] |= AUDIT_PERM_EXEC;
break;
}
}
}
}
}
// Will return empty set if no syscalls, or syscall ALL
std::unordered_set<int> AuditRule::GetSyscalls() const {
std::unordered_set<int> syscalls;
for (int i = 0; i < (AUDIT_BITMASK_SIZE-1); ++i) {
if (ruleptr()->mask[i] != 0) {
for (int x = 0; x < 32; ++x) {
int s = (i*32)+x;
if (ruleptr()->mask[i] & AUDIT_BIT(s)) {
syscalls.insert(s);
}
}
}
}
return syscalls;
}
bool AuditRule::IsSyscallAll() const {
for (int i = 0; i < (AUDIT_BITMASK_SIZE-1); ++i) {
if (ruleptr()->mask[i] != 0xFFFFFFFF) {
return false;
}
}
return true;
}
void AuditRule::SetSyscallAll() {
for (auto& mask : ruleptr()->mask) {
mask = 0xFFFFFFFF;
}
}
void AuditRule::AddSyscall(int syscall) {
int idx = AUDIT_WORD(syscall);
if (idx >= AUDIT_BITMASK_SIZE) {
return;
}
ruleptr()->mask[idx] |= AUDIT_BIT(syscall);
}
void AuditRule::AddSyscalls(const std::unordered_set<int>& syscalls) {
for (auto s: syscalls) {
int idx = AUDIT_WORD(s);
if (idx < AUDIT_BITMASK_SIZE) {
ruleptr()->mask[idx] |= AUDIT_BIT(s);
}
}
}
void AuditRule::SetSyscalls(const std::unordered_set<int>& syscalls) {
::memset(ruleptr()->mask, 0, sizeof(ruleptr()->mask));
for (auto s: syscalls) {
int idx = AUDIT_WORD(s);
if (idx < AUDIT_BITMASK_SIZE) {
ruleptr()->mask[idx] |= AUDIT_BIT(s);
}
}
}
std::unordered_set<std::string> AuditRule::GetKeys() const {
std::unordered_set<std::string> keys;
for (int i = 0; i < ruleptr()->field_count; ++i) {
int field = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
auto value = ruleptr()->values[i];
if (field == AUDIT_FILTERKEY) {
std::string key_str(&ruleptr()->buf[_value_offsets[i]], value);
for (auto& key: split(key_str, KEY_SEP)) {
keys.insert(key);
}
}
}
return keys;
}
void AuditRule::AddKey(const std::string& key) {
auto keys = GetKeys();
if (keys.count(key) > 0) {
return;
}
keys.insert(key);
SetKeys(keys);
}
void AuditRule::AddKeys(const std::unordered_set<std::string>& keys) {
auto new_keys = GetKeys();
auto ksize= new_keys.size();
new_keys.insert(keys.begin(), keys.end());
if (new_keys.size() == ksize) {
return;
}
SetKeys(new_keys);
}
void AuditRule::SetKeys(const std::unordered_set<std::string>& keys) {
std::string key_str;
if (!keys.empty()) {
std::vector<std::string> key_list;
key_list.reserve(keys.size());
size_t str_len = 0;
for (auto &key: keys) {
str_len += key.size();
key_list.emplace_back(key);
}
str_len += keys.size() - 1;
std::sort(key_list.begin(), key_list.end());
key_str.reserve(str_len);
for (auto &key: key_list) {
if (!key_str.empty()) {
key_str.push_back(KEY_SEP);
}
key_str.append(key);
}
if (key_str.size() > AUDIT_MAX_KEY_LEN) {
throw std::runtime_error("Max key length exceeded");
}
}
for (int i = 0; i < ruleptr()->field_count; ++i) {
int field = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
if (field == AUDIT_FILTERKEY) {
remove_field(i);
break;
}
}
if (!key_str.empty()) {
add_str_field(AUDIT_FILTERKEY, AUDIT_EQUAL, key_str);
}
}
bool AuditRule::operator==(const AuditRule& rule) const {
/*
* When comparing ruleptr()->mask we ignore the last element in the array.
* The reason for this is because then set to "all" the last element might be 0xFFFFFFFF or 0x0000FFFF.
*/
return ruleptr()->field_count == rule.ruleptr()->field_count &&
ruleptr()->action == rule.ruleptr()->action &&
ruleptr()->flags == rule.ruleptr()->flags &&
ruleptr()->buflen == rule.ruleptr()->buflen &&
::memcmp(ruleptr()->mask, rule.ruleptr()->mask, (AUDIT_BITMASK_SIZE-1)*sizeof(ruleptr()->fields[0])) == 0 &&
::memcmp(ruleptr()->fields, rule.ruleptr()->fields, ruleptr()->field_count*sizeof(ruleptr()->fields[0])) == 0 &&
::memcmp(ruleptr()->fieldflags, rule.ruleptr()->fieldflags, ruleptr()->field_count*sizeof(ruleptr()->fieldflags[0])) == 0 &&
::memcmp(ruleptr()->values, rule.ruleptr()->values, ruleptr()->field_count*sizeof(ruleptr()->values[0])) == 0 &&
::memcmp(ruleptr()->buf, rule.ruleptr()->buf, ruleptr()->buflen) == 0;
}
bool AuditRule::has_field(uint32_t field) const {
for (int i = 0; i < ruleptr()->field_count; ++i) {
int f = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
if (field == f) {
return true;
}
}
return false;
}
uint32_t AuditRule::get_arch() const {
for (int i = 0; i < ruleptr()->field_count; ++i) {
int field = ruleptr()->fields[i] & ~AUDIT_OPERATORS;
int op = ruleptr()->fieldflags[i] & AUDIT_OPERATORS;
if (field == AUDIT_ARCH && op == AUDIT_EQUAL) {
return ruleptr()->values[i];
}
}
return 0;
}
int AuditRule::add_field(uint32_t field, uint32_t op, uint32_t value) {
if (ruleptr()->field_count >= (AUDIT_MAX_FIELDS-1)) {
throw std::runtime_error("Max field count for rule exceeded");
}
int idx = ruleptr()->field_count;
ruleptr()->fields[idx] = field;
ruleptr()->fieldflags[idx] = op;
ruleptr()->values[idx] = value;
ruleptr()->field_count += 1;
return idx;
}
int AuditRule::add_str_field(uint32_t field, uint32_t op, const std::string& value) {
auto idx = add_field(field, op, static_cast<uint32_t>(value.size()));
auto offset = ruleptr()->buflen;
value.copy(&ruleptr()->buf[ruleptr()->buflen], value.size());
ruleptr()->buflen += value.size();
_value_offsets[idx] = offset;
return idx;
}
void AuditRule::remove_field(int idx) {
if (idx >= ruleptr()->field_count) {
return;
}
auto len = ruleptr()->values[idx];
auto offset = _value_offsets[idx];
auto mlen = ruleptr()->buflen - len - offset;
if (mlen > 0) {
::memmove(&ruleptr()->buf[offset], &ruleptr()->buf[offset+len], mlen);
} else {
::memset(&ruleptr()->buf[offset], 0, len);
}
ruleptr()->buflen -= len;
for (int i = idx+1; i < ruleptr()->field_count; ++i) {
ruleptr()->fields[i-1] = ruleptr()->fields[i];
ruleptr()->fieldflags[i-1] = ruleptr()->fieldflags[i];
ruleptr()->values[i-1] = ruleptr()->values[i];
if (_value_offsets[i] != 0) {
_value_offsets[i-1] = _value_offsets[i]-len;
} else {
_value_offsets[i-1] = 0;
}
}
ruleptr()->fields[ruleptr()->field_count-1] = 0;
ruleptr()->fieldflags[ruleptr()->field_count-1] = 0;
ruleptr()->values[ruleptr()->field_count-1] = 0;
_value_offsets[ruleptr()->field_count-1] = 0;
ruleptr()->field_count -= 1;
}
std::string AuditRule::get_str_field(uint32_t field) const {
for (int i = 0; i < ruleptr()->field_count; ++i) {
if (field == ruleptr()->fields[i] & ~AUDIT_OPERATORS) {
auto offset = _value_offsets[i];
auto len = ruleptr()->values[i];
if (len > 0 && offset+len <= ruleptr()->buflen) {
return std::string(&ruleptr()->buf[offset], len);
} else {
return std::string();
}
}
}
return std::string();
}
void AuditRule::append_field_name(std::string& out, int field) const {
out.append(FieldIdToName(field));
}
void AuditRule::append_op(std::string& out, int op) const {
switch (op) {
case AUDIT_EQUAL:
out.append("=");
break;
case AUDIT_NOT_EQUAL:
out.append("!=");
break;
case AUDIT_GREATER_THAN:
out.append(">");
break;
case AUDIT_GREATER_THAN_OR_EQUAL:
out.append(">=");
break;
case AUDIT_LESS_THAN:
out.append("<");
break;
case AUDIT_LESS_THAN_OR_EQUAL:
out.append("<=");
break;
case AUDIT_BIT_MASK:
out.append("&");
break;
case AUDIT_BIT_TEST:
out.append("&=");
break;
default:
out.append("???");
break;
}
}
void AuditRule::fill_value_offsets() {
_value_offsets.fill(0);
uint32_t offset = 0;
for (int i = 0; i < ruleptr()->field_count; ++i) {
switch (ruleptr()->fields[i] & ~AUDIT_OPERATORS) {
case AUDIT_SUBJ_USER:
/* fallthrough */
case AUDIT_SUBJ_ROLE:
/* fallthrough */
case AUDIT_SUBJ_TYPE:
/* fallthrough */
case AUDIT_SUBJ_SEN:
/* fallthrough */
case AUDIT_SUBJ_CLR:
/* fallthrough */
case AUDIT_OBJ_USER:
/* fallthrough */
case AUDIT_OBJ_ROLE:
/* fallthrough */
case AUDIT_OBJ_TYPE:
/* fallthrough */
case AUDIT_OBJ_LEV_LOW:
/* fallthrough */
case AUDIT_OBJ_LEV_HIGH:
/* fallthrough */
case AUDIT_WATCH:
/* fallthrough */
case AUDIT_DIR:
/* fallthrough */
case AUDIT_EXE:
/* fallthrough */
case AUDIT_FILTERKEY:
_value_offsets[i] = offset;
offset += ruleptr()->values[i];
break;
}
}
}
void AuditRule::append_action(std::string& out) const {
switch (ruleptr()->action) {
case AUDIT_NEVER:
out.append("never");
break;
case AUDIT_POSSIBLE:
out.append("possible");
break;
case AUDIT_ALWAYS:
out.append("always");
break;
default:
out.append("unknown-action("+std::to_string(ruleptr()->action)+")");
break;
}
}
void AuditRule::append_flag(std::string& out) const {
switch (ruleptr()->flags & FILTER_MASK) {
case AUDIT_FILTER_TASK:
out.append("task");
break;
case AUDIT_FILTER_EXIT:
out.append("exit");
break;
case AUDIT_FILTER_USER:
out.append("user");
break;
case AUDIT_FILTER_TYPE:
out.append("exclude");
break;
case AUDIT_FILTER_FS:
out.append("path");
break;
default:
out.append("unknown-flag("+std::to_string(ruleptr()->flags)+")");
break;
}
}
/*
*/
void AuditRule::append_field(std::string& out, int idx, bool is_watch) const {
int field = ruleptr()->fields[idx] & ~AUDIT_OPERATORS;
int op = ruleptr()->fieldflags[idx] & AUDIT_OPERATORS;
auto value = ruleptr()->values[idx];
switch(field) {
case AUDIT_ARCH:
out.append(" -F ");
append_field_name(out, field);
append_op(out, op);
if (__AUDIT_ARCH_64BIT & value) {
out.append("b64");
} else {
out.append("b32");
}
break;
case AUDIT_MSGTYPE:
out.append(" -F ");
append_field_name(out, field);
append_op(out, op);
out.append(RecordTypeToName(static_cast<RecordType>(value)));
break;
case AUDIT_SUBJ_USER:
/* fallthrough */
case AUDIT_SUBJ_ROLE:
/* fallthrough */
case AUDIT_SUBJ_TYPE:
/* fallthrough */
case AUDIT_SUBJ_SEN:
/* fallthrough */
case AUDIT_SUBJ_CLR:
/* fallthrough */
case AUDIT_OBJ_USER:
/* fallthrough */
case AUDIT_OBJ_ROLE:
/* fallthrough */
case AUDIT_OBJ_TYPE:
/* fallthrough */
case AUDIT_OBJ_LEV_LOW:
/* fallthrough */
case AUDIT_OBJ_LEV_HIGH:
out.append(" -F ");
append_field_name(out, field);
append_op(out, op);
out.append(std::string(&ruleptr()->buf[_value_offsets[idx]], value));
break;
case AUDIT_INODE:
out.append(" -F ");
append_field_name(out, field);
append_op(out, op);
out.append(std::to_string(value));
break;
case AUDIT_EXIT:
out.append(" -F ");
append_field_name(out, field);
append_op(out, op);
out.append(std::to_string(static_cast<int32_t>(value)));
break;
case AUDIT_WATCH:
if (is_watch) {
if (is_delete_rule) {
out.append("-W ");
} else {
out.append("-w ");
}
} else {
out.append(" -F ");
append_field_name(out, field);
append_op(out, op);
}
out.append(std::string(&ruleptr()->buf[_value_offsets[idx]], value));
break;
case AUDIT_PERM:
if (is_watch) {
out.append(" -p ");
} else {
out.append(" -F ");
append_field_name(out, field);
append_op(out, op);
}
if (value & AUDIT_PERM_READ) {
out.append("r");
}
if (value & AUDIT_PERM_WRITE) {
out.append("w");
}
if (value & AUDIT_PERM_EXEC) {
out.append("x");
}
if (value & AUDIT_PERM_ATTR) {
out.append("a");
}
break;
case AUDIT_DIR:
if (is_watch) {
if (is_delete_rule) {
out.append("-W ");
} else {
out.append("-w ");
}
} else {
out.append(" -F ");
append_field_name(out, field);
append_op(out, op);
}
out.append(std::string(&ruleptr()->buf[_value_offsets[idx]], value));
break;
case AUDIT_FIELD_COMPARE:
out.append(" -C ");
switch (value) {
case AUDIT_COMPARE_UID_TO_OBJ_UID:
out.append("uid");
append_op(out, op);
out.append("obj_uid");
break;
case AUDIT_COMPARE_GID_TO_OBJ_GID:
out.append("gid");
append_op(out, op);
out.append("obj_gid");
break;
case AUDIT_COMPARE_EUID_TO_OBJ_UID:
out.append("euid");
append_op(out, op);
out.append("obj_uid");
break;
case AUDIT_COMPARE_EGID_TO_OBJ_GID:
out.append("egid");
append_op(out, op);
out.append("obj_gid");
break;
case AUDIT_COMPARE_AUID_TO_OBJ_UID:
out.append("auid");
append_op(out, op);
out.append("obj_uid");
break;
case AUDIT_COMPARE_SUID_TO_OBJ_UID:
out.append("suid");
append_op(out, op);
out.append("obj_uid");
break;
case AUDIT_COMPARE_SGID_TO_OBJ_GID:
out.append("sgid");
append_op(out, op);
out.append("obj_gid");
break;
case AUDIT_COMPARE_FSUID_TO_OBJ_UID:
out.append("fsuid");
append_op(out, op);
out.append("obj_uid");
break;
case AUDIT_COMPARE_FSGID_TO_OBJ_GID:
out.append("fsgid");
append_op(out, op);
out.append("obj_gid");
break;
case AUDIT_COMPARE_UID_TO_AUID:
out.append("uid");
append_op(out, op);
out.append("auid");
break;
case AUDIT_COMPARE_UID_TO_EUID:
out.append("uid");
append_op(out, op);
out.append("euid");
break;
case AUDIT_COMPARE_UID_TO_FSUID:
out.append("uid");
append_op(out, op);
out.append("fsuid");
break;
case AUDIT_COMPARE_UID_TO_SUID:
out.append("uid");
append_op(out, op);
out.append("suid");
break;
case AUDIT_COMPARE_AUID_TO_FSUID:
out.append("auid");
append_op(out, op);
out.append("fsuid");
break;
case AUDIT_COMPARE_AUID_TO_SUID:
out.append("auid");
append_op(out, op);
out.append("suid");
break;
case AUDIT_COMPARE_AUID_TO_EUID:
out.append("auid");
append_op(out, op);
out.append("euid");
break;
case AUDIT_COMPARE_EUID_TO_SUID:
out.append("euid");
append_op(out, op);
out.append("suid");
break;
case AUDIT_COMPARE_EUID_TO_FSUID:
out.append("euid");
append_op(out, op);
out.append("fsuid");
break;
case AUDIT_COMPARE_SUID_TO_FSUID:
out.append("suid");
append_op(out, op);
out.append("fsuid");
break;
case AUDIT_COMPARE_GID_TO_EGID:
out.append("gid");
append_op(out, op);
out.append("egid");
break;
case AUDIT_COMPARE_GID_TO_FSGID:
out.append("gid");
append_op(out, op);
out.append("fsgid");
break;
case AUDIT_COMPARE_GID_TO_SGID:
out.append("gid");
append_op(out, op);
out.append("sgid");
break;
case AUDIT_COMPARE_EGID_TO_FSGID:
out.append("egid");
append_op(out, op);
out.append("fsgid");
break;
case AUDIT_COMPARE_EGID_TO_SGID:
out.append("egid");
append_op(out, op);
out.append("sgid");
break;
case AUDIT_COMPARE_SGID_TO_FSGID:
out.append("sgid");
append_op(out, op);
out.append("fsgid");
break;
}
break;
case AUDIT_EXE:
out.append(" -F ");
append_field_name(out, field);
append_op(out, op);
out.append(std::string(&ruleptr()->buf[_value_offsets[idx]], value));
break;
case AUDIT_ARG0:
/* fallthrough */
case AUDIT_ARG1:
/* fallthrough */
case AUDIT_ARG2:
/* fallthrough */
case AUDIT_ARG3: {
char val[32];
snprintf(val, sizeof(val), "%X", value);
out.append(" -F ");
append_field_name(out, field);
append_op(out, op);
out.append("0x");
out.append(val);
break;
}
case AUDIT_FILTERKEY: {
std::string keys_str(&ruleptr()->buf[_value_offsets[idx]], value);
auto keys = split(keys_str, KEY_SEP);
for (auto& key: keys) {
if (is_watch) {
out.append(" -k ");
} else {
out.append(" -F key=");
}
out.append(key);
}
break;
}
default:
out.append(" -F ");
append_field_name(out, field);
append_op(out, op);
out.append(std::to_string(static_cast<int32_t>(value)));
break;
}
}
void AuditRule::append_syscalls(std::string& out) const {
MachineType mach = DetectMachine();
for (int i = 0; i < AUDIT_MAX_FIELDS; ++i) {
if ((ruleptr()->fields[i] & ~AUDIT_OPERATORS) == AUDIT_ARCH && (ruleptr()->fieldflags[i] & AUDIT_OPERATORS) == AUDIT_EQUAL) {
mach = ArchToMachine(ruleptr()->values[i]);
}
}
bool has_syscall = false;
if (IsSyscallAll()) {
out.append(" -S all");
return;
} else {
for (int i = 0; i < AUDIT_BITMASK_SIZE; ++i) {
if (ruleptr()->mask[i] != 0) {
has_syscall = true;
break;
}
}
}
if (has_syscall) {
out.append(" -S ");
bool first = true;
for (int i = 0; i < AUDIT_BITMASK_SIZE; ++i) {
if (ruleptr()->mask[i] != 0) {
for (int x = 0; x < 32; ++x) {
int s = (i*32)+x;
if (ruleptr()->mask[i] & AUDIT_BIT(s)) {
if (!first) {
out.append(",");
}
auto name = SyscallToName(mach, s);
if (starts_with(name, "unknown-syscall")) {
out.append(std::to_string(s));
} else {
out.append(name);
}
first = false;
}
}
}
}
}
}
void ReplaceSection(std::vector<std::string>& lines, const std::vector<std::string>& replacement, const std::string& start_marker, const std::string& end_marker) {
auto start = std::find(lines.begin(), lines.end(), start_marker);
auto end = std::find(lines.begin(), lines.end(), end_marker);
if (start != lines.end() && end == lines.end()) {
throw std::runtime_error("Missing end marker");
}
if (start == lines.end() && end != lines.end()) {
throw std::runtime_error("Missing start marker");
}
if (start != lines.end() && end != lines.end() && start > end) {
throw std::runtime_error("Start and end markers are inverted");
}
if (start == lines.end() && end == lines.end()) {
start = lines.end();
} else {
++end;
start = lines.erase(start, end);
}
if (replacement.empty()) {
return;
}
start = lines.insert(start, start_marker);
++start;
start = lines.insert(start, replacement.begin(), replacement.end());
start += replacement.size();
lines.insert(start, end_marker);
}
void RemoveSection(std::vector<std::string>& lines, const std::string& start_marker, const std::string& end_marker) {
auto start = std::find(lines.begin(), lines.end(), start_marker);
auto end = std::find(lines.begin(), lines.end(), end_marker);
if (start != lines.end() && end == lines.end()) {
throw std::runtime_error("Missing end marker");
}
if (start == lines.end() && end != lines.end()) {
throw std::runtime_error("Missing start marker");
}
if (start == lines.end() && end == lines.end()) {
return;
}
if (start > end) {
throw std::runtime_error("Start and end markers are inverted");
}
++end;
lines.erase(start, end);
}
std::vector<AuditRule> ParseRules(const std::vector<std::string>& lines, std::vector<std::string>* errors) {
std::vector<AuditRule> rules;
for (int i = 0; i < lines.size(); ++i) {
AuditRule rule;
std::string error;
if (rule.Parse(lines[i], error)) {
rules.emplace_back(rule);
} else if (!error.empty()) {
if (errors != nullptr) {
errors->emplace_back("Failed to parse line " + std::to_string(i + 1) + ": " + error);
} else {
throw std::runtime_error("Failed to parse line " + std::to_string(i + 1) + ": " + error);
}
}
}
return rules;
}
template<typename T>
T diff_set(T a, T b) {
T ret;
for (auto& e: b) {
if (a.find(e) == a.end()) {
ret.emplace(e);
}
}
return ret;
}
// Merge perms, syscalls, and keys from srule into drule
void merge_rule(AuditRule& drule, const AuditRule& srule) {
if (drule.IsWatch()) {
auto diff = diff_set(drule.GetPerms(), srule.GetPerms());
if (!diff.empty()) {
drule.AddPerms(diff);
}
} else {
if (srule.IsSyscallAll()) {
drule.SetSyscallAll();
} else {
auto diff = diff_set(drule.GetSyscalls(), srule.GetSyscalls());
if (!diff.empty()) {
drule.AddSyscalls(diff);
}
}
}
auto diff = diff_set(drule.GetKeys(), srule.GetKeys());
if (!diff.empty()) {
drule.AddKeys(diff);
}
}
// Assumes that drule and srule have the same CanonicalMergeKey() value
// Returns true if srule is a strict subset (perms or syscalls) of drule
bool is_subset(const AuditRule& drule, const AuditRule& srule) {
if (drule.IsWatch()) {
auto diff = diff_set(drule.GetPerms(), srule.GetPerms());
return diff.empty();
} else {
if (drule.IsSyscallAll()) {
return true;
} else {
auto diff = diff_set(drule.GetSyscalls(), srule.GetSyscalls());
return diff.empty();
}
}
}
// Assumes that drule and srule have the same CanonicalMergeKey() value
// Returns rule that has perms and syscalls found in srule that where not in drule.
// Returned rule will have same CanonicalMergeKey() and keys as srule.
AuditRule diff_rule(const AuditRule& drule, const AuditRule& srule) {
AuditRule rule = drule;
if (drule.IsWatch()) {
auto diff = diff_set(drule.GetPerms(), srule.GetPerms());
rule.SetPerms(diff);
} else {
auto diff = diff_set(drule.GetSyscalls(), srule.GetSyscalls());
rule.SetSyscalls(diff);
}
rule.SetKeys(srule.GetKeys());
return rule;
}
std::vector<AuditRule> MergeRules(const std::vector<AuditRule>& rules1) {
std::vector<AuditRule> rules;
std::unordered_map<std::string, size_t> indexes;
for (auto& rule: rules1) {
auto key = rule.CanonicalMergeKey();
auto itr = indexes.find(key);
if (itr == indexes.end()) {
rules.emplace_back(rule);
indexes.emplace(key, rules.size()-1);
} else {
auto& r = rules[itr->second];
merge_rule(r, rule);
}
}
return rules;
}
std::vector<AuditRule> MergeRules(const std::vector<AuditRule>& rules1, const std::vector<AuditRule>& rules2) {
std::vector<AuditRule> rules;
std::unordered_map<std::string, size_t> indexes;
for (auto& rule: rules1) {
auto key = rule.CanonicalMergeKey();
auto itr = indexes.find(key);
if (itr == indexes.end()) {
rules.emplace_back(rule);
indexes.emplace(key, rules.size()-1);
} else {
auto& r = rules[itr->second];
merge_rule(r, rule);
}
}
for (auto& rule: rules2) {
auto key = rule.CanonicalMergeKey();
auto itr = indexes.find(key);
if (itr == indexes.end()) {
rules.emplace_back(rule);
indexes.emplace(key, rules.size()-1);
} else {
auto& r = rules[itr->second];
merge_rule(r, rule);
}
}
return rules;
}
// Return set of rules that when added to actual, at least represents the desired
// If rule in actual has a key matching match_key, and that rule matches the canonical(-F) of a desired but not the perm/syscall
// Then the returned rules will include new rule plus delete rule
// Assumes desired rules are already "merged"
std::vector<AuditRule> DiffRules(const std::vector<AuditRule>& actual, const std::vector<AuditRule>& desired, const std::string& match_key) {
std::vector<AuditRule> rules;
std::unordered_map<std::string, size_t> ridxs;
std::unordered_map<std::string, size_t> aidxs;
for (int i = 0; i < actual.size(); ++i) {
aidxs.emplace(actual[i].CanonicalMergeKey(), i);
}
for(auto& drule: desired) {
auto aitr = aidxs.find(drule.CanonicalMergeKey());
if (aitr == aidxs.end()) {
rules.emplace_back(drule);
ridxs.emplace(drule.CanonicalMergeKey(), rules.size()-1);
} else {
auto& arule = actual[aitr->second];
if (!is_subset(arule, drule)) {
auto diff = diff_rule(arule, drule);
rules.emplace_back(diff);
ridxs.emplace(diff.CanonicalMergeKey(), rules.size()-1);
}
}
}
return rules;
}
std::vector<AuditRule> ReadAuditRulesFromDir(const std::string& dir, std::vector<std::string>* errors) {
std::vector<std::string> files;
std::vector<AuditRule> rules;
files = GetDirList(dir);
for(auto& file: files) {
if (ends_with(file, ".rules")) {
auto lines = ReadFile(dir + "/" + file);
rules = MergeRules(rules, ParseRules(lines, errors));
}
}
return rules;
}
// Read rules from auditd rules.d dir
std::vector<AuditRule> ReadAuditdRulesDir(bool exclude_auoms, std::vector<std::string>* errors) {
std::vector<std::string> files;
std::vector<AuditRule> rules;
files = GetDirList(AUDITD_RULES_DIR);
for(auto& file: files) {
if (ends_with(file, ".rules")) {
auto lines = ReadFile(std::string(AUDITD_RULES_DIR) + "/" + file);
std::vector<std::string> file_errors;
if (exclude_auoms) {
auto in_rules = ParseRules(lines, &file_errors);
std::vector<AuditRule> out_rules;
out_rules.reserve(in_rules.size());
// Only include non-auoms rules
for (auto &rule: in_rules) {
auto keys = rule.GetKeys();
if (keys.count(AUOMS_RULE_KEY) == 0) {
out_rules.emplace_back(rule);
}
}
rules = MergeRules(rules, out_rules);
} else {
rules = MergeRules(rules, ParseRules(lines, &file_errors));
}
if (errors != nullptr) {
for (auto &err : file_errors) {
errors->emplace_back("Encountered parse error in '" + std::string(AUDITD_RULES_DIR) + "/" + file + "': " + err);
}
} else {
std::stringstream ss;
ss << "Encountered parse errors in '" << std::string(AUDITD_RULES_DIR) << "/" << file << "': " << std::endl;
for (auto &err : file_errors) {
ss << " " << err << std::endl;
}
throw std::runtime_error(ss.str());
}
}
}
return rules;
}
// Adds auoms's desired rules to auoms rules file in auditd rule.d dir
void WriteAuomsRuleToAuditDir(const std::vector<AuditRule>& rules) {
if (rules.empty()) {
unlink(AUOMS_AUDITD_RULES_FILE);
} else {
std::vector<std::string> rule_lines;
for (auto &rule: rules) {
rule_lines.emplace_back(rule.CanonicalText());
}
WriteFile(AUOMS_AUDITD_RULES_FILE, rule_lines);
}
}
// Adds auoms's desired rules to auditd rules file
void WriteAuomsRulesToAuditFile(const std::vector<AuditRule>& rules) {
auto lines = ReadFile(AUDITD_RULES_FILE);
if (rules.empty()) {
RemoveSection(lines, AUOMS_AUDITD_RULES_FILE_START_MARKER, AUOMS_AUDITD_RULES_FILE_END_MARKER);
} else {
std::vector<std::string> rule_lines;
for (auto &rule: rules) {
rule_lines.emplace_back(rule.CanonicalText());
}
ReplaceSection(lines, rule_lines, AUOMS_AUDITD_RULES_FILE_START_MARKER, AUOMS_AUDITD_RULES_FILE_END_MARKER);
}
WriteFile(AUDITD_RULES_FILE, lines);
}
bool HasAuditdRulesFiles() {
return PathExists(AUDITD_RULES_FILE);
}
std::vector<AuditRule> ReadActualAuditdRules(bool exclude_auoms, std::vector<std::string>* errors) {
if (!PathExists(AUDITD_RULES_FILE)) {
return std::vector<AuditRule>();
}
auto lines = ReadFile(AUDITD_RULES_FILE);
// If the audit.rules file was at any time in the past generated with augenrules
// assume it is still in use
if (PathExists(AUGENRULES_BIN) && lines.size() > 0 && starts_with(lines[0], AUGENRULES_HEADER)) {
return ReadAuditdRulesDir(exclude_auoms, errors);
} else {
std::vector<AuditRule> rules;
std::vector<std::string> file_errors;
if (exclude_auoms) {
// Remove the auoms rules section if present.
RemoveSection(lines, AUOMS_AUDITD_RULES_FILE_START_MARKER, AUOMS_AUDITD_RULES_FILE_END_MARKER);
auto in_rules = ParseRules(lines, &file_errors);
std::vector<AuditRule> out_rules;
out_rules.reserve(in_rules.size());
// Only include non-auoms rules
for (auto &rule: in_rules) {
auto keys = rule.GetKeys();
if (keys.count(AUOMS_RULE_KEY) == 0) {
out_rules.emplace_back(rule);
}
}
rules = MergeRules({}, out_rules);
} else {
rules = MergeRules({}, ParseRules(lines, &file_errors));
}
if (errors != nullptr) {
for (auto &err : file_errors) {
errors->emplace_back("Encountered parse error in '" + std::string(AUDITD_RULES_FILE) + "': " + err);
}
} else {
std::stringstream ss;
ss << "Encountered parse errors in '" << std::string(AUDITD_RULES_FILE) << "': " << std::endl;
for (auto &err : file_errors) {
ss << " " << err << std::endl;
}
throw std::runtime_error(ss.str());
}
return rules;
}
}
bool WriteAuditdRules(const std::vector<AuditRule>& rules) {
auto lines = ReadFile(AUDITD_RULES_FILE);
if (PathExists(AUGENRULES_BIN) && !lines.empty() && starts_with(lines[0], AUGENRULES_HEADER)) {
WriteAuomsRuleToAuditDir(rules);
return true;
} else {
WriteAuomsRulesToAuditFile(rules);
// If rules dir exists, also write rules to it, in case system owner switches to using augenrules
if (PathExists(AUDITD_RULES_DIR)) {
WriteAuomsRuleToAuditDir(rules);
}
}
return false;
}
// Remove auoms's desired rules to auditd config
// Returns true if augenrules needs to be run
bool RemoveAuomsRulesAuditdFiles() {
std::vector<std::string> errors;
bool has_auoms_rules = false;
auto rules = ReadActualAuditdRules(false, &errors);
for (auto &rule: rules) {
auto keys = rule.GetKeys();
if (keys.count(AUOMS_RULE_KEY) > 0) {
has_auoms_rules = true;
break;
}
}
if (has_auoms_rules) {
return WriteAuditdRules({});
}
return false;
}