Improve rule parsing, fix bugs (#28)

This commit is contained in:
Tad Glines 2019-05-09 13:34:17 -07:00 коммит произвёл GitHub
Родитель 7b8b09d5d4
Коммит dbe2bd2c78
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 626 добавлений и 109 удалений

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

@ -20,6 +20,7 @@
#include "StringUtils.h"
#include "KernelInfo.h"
#include "FileUtils.h"
#include "UserDB.h"
#include <algorithm>
#include <sstream>
@ -28,6 +29,8 @@
#include <climits>
#include <system_error>
#include <pwd.h>
#include <fcntl.h>
// Character that seperates key in AUDIT_FILTERKEY field in rules
#define KEY_SEP 0x01
@ -216,11 +219,13 @@ bool AuditRule::Parse(const std::string& text, std::string& error) {
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) {
@ -236,6 +241,7 @@ bool AuditRule::Parse(const std::string& text, std::string& error) {
if (!parse_add_S_arg(args[idx+1], error)) {
return false;
}
idx += 2;
break;
case 'F':
if (watch) {
@ -244,8 +250,16 @@ bool AuditRule::Parse(const std::string& text, std::string& error) {
error.append("' option");
return false;
}
if (!parse_add_F_arg(args[idx+1], error)) {
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':
@ -258,6 +272,7 @@ bool AuditRule::Parse(const std::string& text, std::string& error) {
if (!parse_add_C_arg(args[idx+1], error)) {
return false;
}
idx += 2;
break;
default:
error.append("Unsupported option '");
@ -265,7 +280,6 @@ bool AuditRule::Parse(const std::string& text, std::string& error) {
error.append("'");
return false;
}
idx += 2;
}
return true;
@ -302,28 +316,32 @@ bool AuditRule::parse_add_a_arg(const std::string& val, std::string& error) {
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 (ruleptr()->action == 0) {
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 (ruleptr()->flags == 0) {
if (!flags_found) {
error.append("Invalid or missing flags value for option '-a': '");
error.append(val);
error.append("'");
@ -422,6 +440,18 @@ std::unordered_map<std::string, uint32_t> s_F_ops(
}
);
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) {
@ -474,6 +504,128 @@ bool AuditRule::parse_add_F_arg(const std::string& val, std::string& error) {
}
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) {
@ -489,12 +641,6 @@ bool AuditRule::parse_add_F_arg(const std::string& val, std::string& error) {
add_field(field, op, arch);
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;
}
break;
case AUDIT_PERM: {
uint32_t perms = 0;
for (auto c: value) {
@ -521,11 +667,29 @@ bool AuditRule::parse_add_F_arg(const std::string& val, std::string& error) {
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: {
/* fallthrough */
auto path = clean_path(val);
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;
@ -533,12 +697,38 @@ bool AuditRule::parse_add_F_arg(const std::string& val, std::string& error) {
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));
@ -1689,7 +1879,7 @@ void RemoveSection(std::vector<std::string>& lines, const std::string& start_mar
lines.erase(start, end);
}
std::vector<AuditRule> ParseRules(const std::vector<std::string>& lines) {
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;
@ -1697,7 +1887,11 @@ std::vector<AuditRule> ParseRules(const std::vector<std::string>& lines) {
if (rule.Parse(lines[i], error)) {
rules.emplace_back(rule);
} else if (!error.empty()) {
throw std::runtime_error("Failed to parse line " + std::to_string(i+1) + ": " + error);
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;
@ -1827,7 +2021,7 @@ std::vector<AuditRule> DiffRules(const std::vector<AuditRule>& actual, const std
return rules;
}
std::vector<AuditRule> ReadAuditRulesFromDir(const std::string& dir) {
std::vector<AuditRule> ReadAuditRulesFromDir(const std::string& dir, std::vector<std::string>* errors) {
std::vector<std::string> files;
std::vector<AuditRule> rules;
@ -1836,7 +2030,7 @@ std::vector<AuditRule> ReadAuditRulesFromDir(const std::string& dir) {
for(auto& file: files) {
if (ends_with(file, ".rules")) {
auto lines = ReadFile(dir + "/" + file);
rules = MergeRules(rules, ParseRules(lines));
rules = MergeRules(rules, ParseRules(lines, errors));
}
}
@ -1844,7 +2038,7 @@ std::vector<AuditRule> ReadAuditRulesFromDir(const std::string& dir) {
}
// Read rules from auditd rules.d dir
std::vector<AuditRule> ReadAuditdRulesDir(bool exclude_auoms) {
std::vector<AuditRule> ReadAuditdRulesDir(bool exclude_auoms, std::vector<std::string>* errors) {
std::vector<std::string> files;
std::vector<AuditRule> rules;
@ -1853,8 +2047,9 @@ std::vector<AuditRule> ReadAuditdRulesDir(bool exclude_auoms) {
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);
auto in_rules = ParseRules(lines, &file_errors);
std::vector<AuditRule> out_rules;
out_rules.reserve(in_rules.size());
// Only include non-auoms rules
@ -1866,7 +2061,19 @@ std::vector<AuditRule> ReadAuditdRulesDir(bool exclude_auoms) {
}
rules = MergeRules(rules, out_rules);
} else {
rules = MergeRules(rules, ParseRules(lines));
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());
}
}
}
@ -1908,7 +2115,7 @@ bool HasAuditdRulesFiles() {
return PathExists(AUDITD_RULES_FILE);
}
std::vector<AuditRule> ReadActualAuditdRules(bool exclude_auoms) {
std::vector<AuditRule> ReadActualAuditdRules(bool exclude_auoms, std::vector<std::string>* errors) {
if (!PathExists(AUDITD_RULES_FILE)) {
return std::vector<AuditRule>();
}
@ -1918,13 +2125,15 @@ std::vector<AuditRule> ReadActualAuditdRules(bool exclude_auoms) {
// 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);
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);
auto in_rules = ParseRules(lines, &file_errors);
std::vector<AuditRule> out_rules;
out_rules.reserve(in_rules.size());
// Only include non-auoms rules
@ -1934,17 +2143,30 @@ std::vector<AuditRule> ReadActualAuditdRules(bool exclude_auoms) {
out_rules.emplace_back(rule);
}
}
return MergeRules({}, out_rules);
rules = MergeRules({}, out_rules);
} else {
return MergeRules({}, ParseRules(lines));
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.size() > 0 && starts_with(lines[0], AUGENRULES_HEADER)) {
if (PathExists(AUGENRULES_BIN) && !lines.empty() && starts_with(lines[0], AUGENRULES_HEADER)) {
WriteAuomsRuleToAuditDir(rules);
return true;
} else {
@ -1960,8 +2182,9 @@ bool WriteAuditdRules(const std::vector<AuditRule>& rules) {
// 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);
auto rules = ReadActualAuditdRules(false, &errors);
for (auto &rule: rules) {
auto keys = rule.GetKeys();
if (keys.count(AUOMS_RULE_KEY) > 0) {

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

@ -141,6 +141,9 @@ public:
AuditRule(const void* data, size_t len): _data(), _value_offsets(), is_delete_rule(false) {
_data.fill(0);
if (len > _data.size()) {
throw std::out_of_range("len too large");
}
::memcpy(_data.data(), data, len);
fill_value_offsets();
}
@ -240,7 +243,10 @@ private:
void ReplaceSection(std::vector<std::string>& lines, const std::vector<std::string>& replacement, const std::string& start_marker, const std::string& end_marker);
void RemoveSection(std::vector<std::string>& lines, const std::string& start_marker, const std::string& end_marker);
std::vector<AuditRule> ParseRules(const std::vector<std::string>& lines);
// If errors is null, then ParseRules will throiw an exception if there is a parse error
// If errors is not null, then ParseRules willa append each parse error to errors and return only the parsed rules.
std::vector<AuditRule> ParseRules(const std::vector<std::string>& lines, std::vector<std::string>* errors);
std::vector<AuditRule> MergeRules(const std::vector<AuditRule>& rules1);
std::vector<AuditRule> MergeRules(const std::vector<AuditRule>& rules1, const std::vector<AuditRule>& rules2);
@ -253,10 +259,10 @@ std::vector<AuditRule> DiffRules(const std::vector<AuditRule>& actual, const std
bool HasAuditdRulesFiles();
// Read all *.rules files from dir, parse, merge then return them.
std::vector<AuditRule> ReadAuditRulesFromDir(const std::string& dir);
std::vector<AuditRule> ReadAuditRulesFromDir(const std::string& dir, std::vector<std::string>* errors);
// Read rules from auditd rules (excluding auoms rules)
std::vector<AuditRule> ReadActualAuditdRules(bool exclude_auoms);
std::vector<AuditRule> ReadActualAuditdRules(bool exclude_auoms, std::vector<std::string>* errors);
// Adds auoms's desired rules to auditd config
// Returns true if augenrules needs to be run

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

@ -2,13 +2,20 @@
// Created by tad on 3/18/19.
//
#include <sstream>
#include "AuditRulesMonitor.h"
#include "Logger.h"
#include "ExecUtil.h"
#define PREFERRED_BACKLOG_LIMIT 8192
void AuditRulesMonitor::run() {
Logger::Info("AuditRulesMonitor: Starting");
check_audit_status();
while(!_sleep(1000)) {
auto now = std::chrono::steady_clock::now();
@ -35,7 +42,8 @@ void AuditRulesMonitor::on_stop() {
void AuditRulesMonitor::get_desired_rules() {
try {
auto rules = ReadAuditRulesFromDir(_audit_rules_dir);
std::vector<std::string> errors;
auto rules = ReadAuditRulesFromDir(_audit_rules_dir, &errors);
_desired_rules.resize(0);
for (auto& rule: rules) {
// Only include the rule in the desired rules if it is supported on the host system
@ -44,7 +52,16 @@ void AuditRulesMonitor::get_desired_rules() {
_desired_rules.emplace_back(rule);
}
}
_op_status->ClearErrorCondition(ErrorCategory::DESIRED_RULES);
if (errors.empty()) {
_op_status->ClearErrorCondition(ErrorCategory::DESIRED_RULES);
} else {
std::stringstream ss;
ss << " Encountered parse errors: " << std::endl;
for (auto& err : errors) {
ss << " " << err << std::endl;
}
_op_status->SetErrorCondition(ErrorCategory::DESIRED_RULES, ss.str());
}
} catch(std::exception& ex) {
Logger::Error("AuditRulesMonitor: Failed to read desired rules from %s: %s", _audit_rules_dir.c_str(), ex.what());
_op_status->SetErrorCondition(ErrorCategory::DESIRED_RULES, "Failed to read desired rules from " + _audit_rules_dir + ": " + ex.what());
@ -57,16 +74,27 @@ void AuditRulesMonitor::check_file_rules() {
return;
}
try {
auto rules = ReadActualAuditdRules(false);
std::vector<std::string> errors;
auto rules = ReadActualAuditdRules(false, &errors);
auto merged_rules = MergeRules(rules);
auto diff = DiffRules(merged_rules, _desired_rules, "");
if (diff.empty()) {
_op_status->ClearErrorCondition(ErrorCategory::AUDIT_RULES_FILE);
if (errors.empty()) {
_op_status->ClearErrorCondition(ErrorCategory::AUDIT_RULES_FILE);
} else {
std::stringstream ss;
ss << " Encountered parse errors: " << std::endl;
for (auto& err : errors) {
ss << " " << err << std::endl;
}
_op_status->SetErrorCondition(ErrorCategory::AUDIT_RULES_FILE, ss.str());
}
return;
}
Logger::Info("AuditRulesMonitor: Found desired audit rules not currently present in auditd rules files(s), adding new rules");
// Re-read rules but exclude auoms rules
rules = ReadActualAuditdRules(true);
errors.clear();
rules = ReadActualAuditdRules(true, &errors);
merged_rules = MergeRules(rules);
// Re-calculate diff
diff = DiffRules(merged_rules, _desired_rules, "");
@ -78,12 +106,32 @@ void AuditRulesMonitor::check_file_rules() {
if (ret != 0) {
Logger::Warn("AuditRulesMonitor: augenrules failed: %s", cmd.FailMsg().c_str());
Logger::Warn("AuditRulesMonitor: augenrules output: %s", output.c_str());
_op_status->SetErrorCondition(ErrorCategory::AUDIT_RULES_FILE, std::string("augenrules failed: ") + cmd.FailMsg());
if (errors.empty()) {
_op_status->SetErrorCondition(ErrorCategory::AUDIT_RULES_FILE, std::string("augenrules failed: ") + cmd.FailMsg());
} else {
std::stringstream ss;
ss << " Encountered parse errors and augenrules failed: " << std::endl;
ss << " augenrules error:" << cmd.FailMsg() << std::endl;
for (auto& err : errors) {
ss << " " << err << std::endl;
}
_op_status->SetErrorCondition(ErrorCategory::AUDIT_RULES_FILE, ss.str());
}
return;
} else {
Logger::Warn("AuditRulesMonitor: augenrules succeeded");
}
}
_op_status->ClearErrorCondition(ErrorCategory::AUDIT_RULES_FILE);
if (errors.empty()) {
_op_status->ClearErrorCondition(ErrorCategory::AUDIT_RULES_FILE);
} else {
std::stringstream ss;
ss << " Encountered parse errors: " << std::endl;
for (auto& err : errors) {
ss << " " << err << std::endl;
}
_op_status->SetErrorCondition(ErrorCategory::AUDIT_RULES_FILE, ss.str());
}
} catch (std::exception& ex) {
Logger::Error("AuditRulesMonitor: Failed to check/update auditd rules: %s", ex.what());
_op_status->SetErrorCondition(ErrorCategory::AUDIT_RULES_FILE, std::string("Failed to check/update auditd rules: ") + ex.what());
@ -226,3 +274,26 @@ bool AuditRulesMonitor::check_kernel_rules() {
return true;
}
void AuditRulesMonitor::check_audit_status() {
audit_status status;
auto ret = NetlinkRetry([this,&status]() { return _netlink.AuditGet(status); } );
if (ret != 0) {
Logger::Error("Failed to get audit status: %s", std::strerror(-ret));
return;
}
if (status.backlog_limit < PREFERRED_BACKLOG_LIMIT) {
Logger::Error("Increasing audit backlog limit from %u to %u", status.backlog_limit, PREFERRED_BACKLOG_LIMIT);
ret = NetlinkRetry([this]() {
audit_status status;
::memset(&status, 0, sizeof(status));
status.mask = AUDIT_STATUS_BACKLOG_LIMIT;
status.backlog_limit = PREFERRED_BACKLOG_LIMIT;
return _netlink.AuditSet(status);
});
if (ret != 0) {
Logger::Error("Failed to set audit backlog limit to %d: %s", PREFERRED_BACKLOG_LIMIT, std::strerror(-ret));
return;
}
}
}

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

@ -36,6 +36,7 @@ private:
void get_desired_rules();
void check_file_rules();
bool check_kernel_rules();
void check_audit_status();
Netlink& _netlink;
std::string _audit_rules_dir;

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

@ -113,6 +113,7 @@ add_executable(auoms
TranslateRecordType.cpp
TranslateFieldType.cpp
TranslateField.cpp
TranslateErrno.cpp
AuditRules.cpp
AuditRulesMonitor.cpp
KernelInfo.cpp
@ -155,6 +156,7 @@ add_executable(auomsctl
TranslateRecordType.cpp
TranslateFieldType.cpp
TranslateField.cpp
TranslateErrno.cpp
AuditRules.cpp
StringUtils.cpp
KernelInfo.cpp
@ -165,6 +167,7 @@ add_executable(auomsctl
FileUtils.cpp
UnixDomainListener.cpp
Event.cpp
UserDB.cpp
)
# See https://gcc.gnu.org/onlinedocs/libstdc++/manual/license.html

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

@ -180,8 +180,6 @@ bool CollectionMonitor::is_collector_alive() {
}
void CollectionMonitor::send_audit_pid_report(int pid) {
Logger::Info("CollectionMonitor: Audit Pid: %d", pid);
auto pinfo = ProcessInfo::Open(pid);
std::string exe;
int ppid = -1;

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

@ -107,7 +107,7 @@ bool CursorWriter::Read() {
Logger::Error("Output(%s): Failed to open cursor file (%s): %s", _name.c_str(), _path.c_str(), std::strerror(errno));
return false;
} else {
_cursor = QueueCursor::TAIL;
_cursor = QueueCursor::HEAD;
return true;
}
}

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

@ -114,7 +114,7 @@ bool ProcessInfo::parse_stat() {
if (*ptr != '(') {
return false;
}
f_end = strchr(ptr, ')');
f_end = strstr(ptr, ") ");
if (f_end != nullptr && f_end+1 < end && f_end[1] == ')') {
f_end += 1;
}

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

@ -294,8 +294,13 @@ void Queue::Close(bool save)
save_locked(lock);
}
// Wait for any active save to complete
_cond.wait(lock, [this]() { return !_save_active; });
close(_fd);
_fd = -1;
_cond.notify_all();
}
void Queue::Save() {
@ -329,8 +334,6 @@ void Queue::save_locked(std::unique_lock<std::mutex>& lock)
return;
}
auto fd = _fd;
_save_active = true;
FileHeader before;
@ -393,20 +396,22 @@ void Queue::save_locked(std::unique_lock<std::mutex>& lock)
int64_t save_size = 0;
if (nregions > 0) {
_pwrite(fd, &before, sizeof(FileHeader), 0);
_pwrite(_fd, &before, sizeof(FileHeader), 0);
for (int i = 0; i < nregions; i++) {
_pwrite(fd, regions[i].data, regions[i].size, regions[i].index);
_pwrite(_fd, regions[i].data, regions[i].size, regions[i].index);
save_size += regions[i].size;
}
}
_pwrite(fd, &after, sizeof(FileHeader), 0);
_pwrite(_fd, &after, sizeof(FileHeader), 0);
lock.lock();
_saved_size += save_size;
_save_active = false;
_cond.notify_all();
}
// Assumes queue is locked

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

@ -21,6 +21,7 @@
#include <signal.h>
std::atomic<bool> Signals::_exit(false);
std::mutex Signals::_mutex;
std::function<void()> Signals::_hup_fn;
std::function<void()> Signals::_exit_fn;
pthread_t Signals::_main_id;
@ -106,13 +107,17 @@ void Signals::run() {
return;
}
if (sig == SIGHUP) {
std::lock_guard<std::mutex> _lock(_mutex);
if (_hup_fn) {
_hup_fn();
}
} else {
_exit.store(true);
if (_exit_fn) {
_exit_fn();
{
std::lock_guard<std::mutex> _lock(_mutex);
if (_exit_fn) {
_exit_fn();
}
}
// Break main thread out of blocking syscall
pthread_kill(_main_id, SIGQUIT);

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

@ -18,6 +18,7 @@
#include <atomic>
#include <functional>
#include <mutex>
#include <pthread.h>
@ -29,13 +30,20 @@ public:
static bool IsExit();
static void Terminate();
static void SetHupHandler(std::function<void()> fn) { _hup_fn = fn; }
static void SetExitHandler(std::function<void()> fn) { _exit_fn = fn; }
static void SetHupHandler(std::function<void()> fn) {
std::lock_guard<std::mutex> _lock(_mutex);
_hup_fn = fn;
}
static void SetExitHandler(std::function<void()> fn) {
std::lock_guard<std::mutex> _lock(_mutex);
_exit_fn = fn;
}
private:
static void run();
static std::atomic<bool> _exit;
static std::mutex _mutex;
static std::function<void()> _hup_fn;
static std::function<void()> _exit_fn;
static pthread_t _main_id;

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

@ -45,4 +45,7 @@ field_type_t FieldNameToType(RecordType rtype, const std::string_view& name, con
std::string FieldIdToName(int field);
int FieldNameToId(const std::string_view& name);
std::string ErrnoToName(int field);
int NameToErrno(const std::string_view& name);
#endif //AUOMS_TRANSLATE_H

182
TranslateErrno.cpp Normal file
Просмотреть файл

@ -0,0 +1,182 @@
/*
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 <linux/audit.h>
#include "Translate.h"
#include "StringTable.h"
#include <errno.h>
static StringTable<int> s_errno_table(0, {
{"EPERM", EPERM},
{"ENOENT", ENOENT},
{"ESRCH", ESRCH},
{"EINTR", EINTR},
{"EIO", EIO},
{"ENXIO", ENXIO},
{"E2BIG", E2BIG},
{"ENOEXEC", ENOEXEC},
{"EBADF", EBADF},
{"ECHILD", ECHILD},
{"EAGAIN", EAGAIN},
{"ENOMEM", ENOMEM},
{"EACCES", EACCES},
{"EFAULT", EFAULT},
{"ENOTBLK", ENOTBLK},
{"EBUSY", EBUSY},
{"EEXIST", EEXIST},
{"EXDEV", EXDEV},
{"ENODEV", ENODEV},
{"ENOTDIR", ENOTDIR},
{"EISDIR", EISDIR},
{"EINVAL", EINVAL},
{"ENFILE", ENFILE},
{"EMFILE", EMFILE},
{"ENOTTY", ENOTTY},
{"ETXTBSY", ETXTBSY},
{"EFBIG", EFBIG},
{"ENOSPC", ENOSPC},
{"ESPIPE", ESPIPE},
{"EROFS", EROFS},
{"EMLINK", EMLINK},
{"EPIPE", EPIPE},
{"EDOM", EDOM},
{"ERANGE", ERANGE},
{"EDEADLK", EDEADLK},
{"ENAMETOOLONG", ENAMETOOLONG},
{"ENOLCK", ENOLCK},
{"ENOSYS", ENOSYS},
{"ENOTEMPTY", ENOTEMPTY},
{"ELOOP", ELOOP},
{"EWOULDBLOCK", EWOULDBLOCK},
{"ENOMSG", ENOMSG},
{"EIDRM", EIDRM},
{"ECHRNG", ECHRNG},
{"EL2NSYNC", EL2NSYNC},
{"EL3HLT", EL3HLT},
{"EL3RST", EL3RST},
{"ELNRNG", ELNRNG},
{"EUNATCH", EUNATCH},
{"ENOCSI", ENOCSI},
{"EL2HLT", EL2HLT},
{"EBADE", EBADE},
{"EBADR", EBADR},
{"EXFULL", EXFULL},
{"ENOANO", ENOANO},
{"EBADRQC", EBADRQC},
{"EBADSLT", EBADSLT},
{"EDEADLOCK", EDEADLOCK},
{"EBFONT", EBFONT},
{"ENOSTR", ENOSTR},
{"ENODATA", ENODATA},
{"ETIME", ETIME},
{"ENOSR", ENOSR},
{"ENONET", ENONET},
{"ENOPKG", ENOPKG},
{"EREMOTE", EREMOTE},
{"ENOLINK", ENOLINK},
{"EADV", EADV},
{"ESRMNT", ESRMNT},
{"ECOMM", ECOMM},
{"EPROTO", EPROTO},
{"EMULTIHOP", EMULTIHOP},
{"EDOTDOT", EDOTDOT},
{"EBADMSG", EBADMSG},
{"EOVERFLOW", EOVERFLOW},
{"ENOTUNIQ", ENOTUNIQ},
{"EBADFD", EBADFD},
{"EREMCHG", EREMCHG},
{"ELIBACC", ELIBACC},
{"ELIBBAD", ELIBBAD},
{"ELIBSCN", ELIBSCN},
{"ELIBMAX", ELIBMAX},
{"ELIBEXEC", ELIBEXEC},
{"EILSEQ", EILSEQ},
{"ERESTART", ERESTART},
{"ESTRPIPE", ESTRPIPE},
{"EUSERS", EUSERS},
{"ENOTSOCK", ENOTSOCK},
{"EDESTADDRREQ", EDESTADDRREQ},
{"EMSGSIZE", EMSGSIZE},
{"EPROTOTYPE", EPROTOTYPE},
{"ENOPROTOOPT", ENOPROTOOPT},
{"EPROTONOSUPPORT", EPROTONOSUPPORT},
{"ESOCKTNOSUPPORT", ESOCKTNOSUPPORT},
{"EOPNOTSUPP", EOPNOTSUPP},
{"EPFNOSUPPORT", EPFNOSUPPORT},
{"EAFNOSUPPORT", EAFNOSUPPORT},
{"EADDRINUSE", EADDRINUSE},
{"EADDRNOTAVAIL", EADDRNOTAVAIL},
{"ENETDOWN", ENETDOWN},
{"ENETUNREACH", ENETUNREACH},
{"ENETRESET", ENETRESET},
{"ECONNABORTED", ECONNABORTED},
{"ECONNRESET", ECONNRESET},
{"ENOBUFS", ENOBUFS},
{"EISCONN", EISCONN},
{"ENOTCONN", ENOTCONN},
{"ESHUTDOWN", ESHUTDOWN},
{"ETOOMANYREFS", ETOOMANYREFS},
{"ETIMEDOUT", ETIMEDOUT},
{"ECONNREFUSED", ECONNREFUSED},
{"EHOSTDOWN", EHOSTDOWN},
{"EHOSTUNREACH", EHOSTUNREACH},
{"EALREADY", EALREADY},
{"EINPROGRESS", EINPROGRESS},
{"ESTALE", ESTALE},
{"EUCLEAN", EUCLEAN},
{"ENOTNAM", ENOTNAM},
{"ENAVAIL", ENAVAIL},
{"EISNAM", EISNAM},
{"EREMOTEIO", EREMOTEIO},
{"EDQUOT", EDQUOT},
{"ENOMEDIUM", ENOMEDIUM},
{"EMEDIUMTYPE", EMEDIUMTYPE},
{"ECANCELED", ECANCELED},
{"ENOKEY", ENOKEY},
{"EKEYEXPIRED", EKEYEXPIRED},
{"EKEYREVOKED", EKEYREVOKED},
{"EKEYREJECTED", EKEYREJECTED},
{"EOWNERDEAD", EOWNERDEAD},
{"ENOTRECOVERABLE", ENOTRECOVERABLE},
{"ERFKILL", ERFKILL},
{"EHWPOISON", EHWPOISON},
});
std::string ErrnoToName(int n) {
int err = n;
if (err < 0) {
err = -err;
}
auto str = std::string(s_errno_table.ToString(n));
if (str.empty()) {
str = std::to_string(n);
}
if (n < 0) {
return "-" + str;
} else {
return str;
}
}
int NameToErrno(const std::string_view& name) {
if (!name.empty() && name[0] == '-') {
return -(s_errno_table.ToInt(name.substr(1)));
} else {
return s_errno_table.ToInt(name);
}
}

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

@ -204,8 +204,9 @@ void UserDB::update_task()
}
}
void parse_file(const std::string& path, std::unordered_map<int, std::string>& db)
std::vector<std::pair<int, std::string>> parse_file(const std::string& path)
{
std::vector<std::pair<int, std::string>> entries;
std::ifstream fs(path);
int line_num = 1;
@ -232,11 +233,9 @@ void parse_file(const std::string& path, std::unordered_map<int, std::string>& d
} catch (...) {
continue;
}
// Just in case there are multiple entries, only the first id->name is used
if (db.count(id) == 0) {
db.emplace(std::make_pair(id, name));
}
entries.emplace_back(id, name);
}
return entries;
}
void UserDB::update()
@ -245,8 +244,18 @@ void UserDB::update()
std::unordered_map<int, std::string> groups;
try {
parse_file(_dir + "/passwd", users);
parse_file(_dir + "/group", groups);
for (auto& e : parse_file(_dir + "/passwd")) {
// Just in case there are multiple entries, only the first id->name is used
if (users.count(e.first) == 0) {
users.emplace(e);
}
}
for (auto& e : parse_file(_dir + "/group")) {
// Just in case there are multiple entries, only the first id->name is used
if (groups.count(e.first) == 0) {
groups.emplace(e);
}
}
} catch (const std::exception& ex) {
Logger::Warn("UserDB: Update failed: %s", ex.what());
return;
@ -256,3 +265,29 @@ void UserDB::update()
_users = users;
_groups = groups;
}
int UserDB::UserNameToUid(const std::string& name) {
try {
for (auto& e : parse_file("/etc/passwd")) {
if (e.second == name) {
return e.first;
}
}
} catch (const std::exception& ex) {
return -1;
}
return -1;
}
int UserDB::GroupNameToGid(const std::string& name) {
try {
for (auto& e : parse_file("/etc/group")) {
if (e.second == name) {
return e.first;
}
}
} catch (const std::exception& ex) {
return -1;
}
return -1;
}

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

@ -37,6 +37,9 @@ public:
void update(); // Exposed only to simplify tests
static int UserNameToUid(const std::string& name);
static int GroupNameToGid(const std::string& name);
private:
void inotify_task();

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

@ -8,6 +8,6 @@
AUOMS_BUILDVERSION_MAJOR=2
AUOMS_BUILDVERSION_MINOR=0
AUOMS_BUILDVERSION_PATCH=0
AUOMS_BUILDVERSION_BUILDNR=10
AUOMS_BUILDVERSION_DATE=20190421
AUOMS_BUILDVERSION_BUILDNR=12
AUOMS_BUILDVERSION_DATE=20190501
AUOMS_BUILDVERSION_STATUS=Developer_Build

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

@ -77,9 +77,6 @@ int show_audit_status() {
return 1;
}
Signals::Init();
Signals::Start();
Netlink netlink;
netlink.SetQuite();
@ -116,9 +113,6 @@ int list_rules(bool raw_fmt, const std::string& key) {
return 1;
}
Signals::Init();
Signals::Start();
Netlink netlink;
netlink.SetQuite();
@ -163,9 +157,6 @@ int delete_rules(const std::string& key) {
return 1;
}
Signals::Init();
Signals::Start();
Netlink netlink;
netlink.SetQuite();
@ -222,10 +213,7 @@ int load_rules(const std::string& path) {
int exit_code = 0;
try {
auto lines = ReadFile(path);
auto rules = ParseRules(lines);
Signals::Init();
Signals::Start();
auto rules = ParseRules(lines, nullptr);
Netlink netlink;
netlink.SetQuite();
@ -268,9 +256,16 @@ int load_rules(const std::string& path) {
int print_rules(const std::string& path) {
try {
auto lines = ReadFile(path);
auto rules = ParseRules(lines);
for (auto& rule: rules) {
std::cout << rule.CanonicalText() << std::endl;
std::vector<AuditRule> rules;
for (int i = 0; i < lines.size(); ++i) {
AuditRule rule;
std::string error;
if (rule.Parse(lines[i], error)) {
std::cout << rule.CanonicalText() << std::endl;
} else if (!error.empty()) {
std::cout << "Failed to parse line " << i+1 << ": " << error << std::endl;
std::cout << " " << lines[i] << std::endl;
}
}
} catch (std::exception& ex) {
std::cerr << ex.what() << std::endl;
@ -282,8 +277,8 @@ int print_rules(const std::string& path) {
int merge_rules(const std::string& file1, const std::string& file2) {
try {
auto rules1 = ParseRules(ReadFile(file1));
auto rules2 = ParseRules(ReadFile(file2));
auto rules1 = ParseRules(ReadFile(file1), nullptr);
auto rules2 = ParseRules(ReadFile(file2), nullptr);
auto merged_rules = MergeRules(rules1, rules2);
for (auto& rule: merged_rules) {
std::cout << rule.CanonicalText() << std::endl;
@ -298,8 +293,8 @@ int merge_rules(const std::string& file1, const std::string& file2) {
int diff_rules(const std::string& file1, const std::string& file2) {
try {
auto rules1 = MergeRules(ParseRules(ReadFile(file1)));
auto rules2 = MergeRules(ParseRules(ReadFile(file2)));
auto rules1 = MergeRules(ParseRules(ReadFile(file1), nullptr));
auto rules2 = MergeRules(ParseRules(ReadFile(file2), nullptr));
auto diffed_rules = DiffRules(rules1, rules2, "");
for (auto& rule: diffed_rules) {
std::cout << rule.CanonicalText() << std::endl;
@ -318,9 +313,6 @@ int show_auoms_status() {
return 1;
}
Signals::Init();
Signals::Start();
UnixDomainWriter io("/var/run/auoms/status.socket");
if (!io.Open()) {
std::cout << "auoms is not running" << std::endl;
@ -676,10 +668,6 @@ int enable_auoms() {
return 1;
}
Signals::Init();
Signals::Start();
Signals::SetExitHandler([](){ exit(1); });
// Return
// 0 on success
// 1 if service could not be enabled
@ -739,10 +727,6 @@ int disable_auoms() {
return 1;
}
Signals::Init();
Signals::Start();
Signals::SetExitHandler([](){ exit(1); });
// Return
// 0 on success
// 1 if service could not be disabled
@ -884,9 +868,6 @@ int tap_audit() {
return false;
};
Signals::Init();
Signals::Start();
Logger::Info("Connecting to AUDIT NETLINK socket");
auto ret = netlink.Open(handler);
if (ret != 0) {
@ -1068,9 +1049,6 @@ int monitor_auoms_events() {
std::string sock_path = "/var/run/auoms/auomsctl.socket";
std::string config_path = "/etc/opt/microsoft/auoms/outconf.d/auomsctl.conf";
Signals::Init();
Signals::Start();
UnixDomainListener listener(sock_path, 0666);
if (!listener.Open()) {
return -1;
@ -1106,7 +1084,7 @@ int monitor_auoms_events() {
}
int set_rules() {
auto rules = ReadAuditRulesFromDir("/etc/opt/microsoft/auoms/rules.d");
auto rules = ReadAuditRulesFromDir("/etc/opt/microsoft/auoms/rules.d", nullptr);
std::vector<AuditRule> desired_rules;
for (auto& rule: rules) {
// Only include the rule in the desired rules if it is supported on the host system
@ -1117,14 +1095,14 @@ int set_rules() {
}
try {
auto rules = ReadActualAuditdRules(false);
auto rules = ReadActualAuditdRules(false, nullptr);
auto diff = DiffRules(rules, desired_rules, "");
if (diff.empty()) {
return 0;
}
Logger::Info("AuditRulesMonitor: Found desired audit rules not currently present in auditd rules files(s), adding new rules");
// Re-read rules but exclude auoms rules
rules = ReadActualAuditdRules(true);
rules = ReadActualAuditdRules(true, nullptr);
// Re-calculate diff
diff = DiffRules(rules, desired_rules, "");
if (WriteAuditdRules(diff)) {
@ -1156,7 +1134,7 @@ bool is_set_intersect(T a, T b) {
}
int load_rules() {
auto rules = ReadAuditRulesFromDir("/etc/opt/microsoft/auoms/rules.d");
auto rules = ReadAuditRulesFromDir("/etc/opt/microsoft/auoms/rules.d", nullptr);
std::vector<AuditRule> desired_rules;
for (auto& rule: rules) {
// Only include the rule in the desired rules if it is supported on the host system
@ -1168,9 +1146,6 @@ int load_rules() {
Netlink netlink;
Signals::Init();
Signals::Start();
Logger::Info("Connecting to AUDIT NETLINK socket");
auto ret = netlink.Open(nullptr);
if (ret != 0) {
@ -1285,10 +1260,6 @@ int upgrade() {
return 1;
}
Signals::Init();
Signals::Start();
Signals::SetExitHandler([](){ exit(1); });
try {
// Use auditd plugin file to determine if auoms should be enabled
if (is_auditd_plugin_enabled()) {
@ -1354,6 +1325,10 @@ int main(int argc, char**argv) {
exit(1);
}
Signals::Init();
Signals::Start();
Signals::SetExitHandler([](){ exit(1); });
if (strcmp(argv[1], "-v") == 0) {
std::cout << std::string(AUOMS_VERSION) << std::endl;
return 0;

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

@ -15,7 +15,7 @@
# Default is ${run_dir}/input.socket
#socket_path = /var/run/auoms/input.socket
# The path to the file where auomscollect will keep trask of which events have been sent to auoms
# The path to the file where auomscollect will keep track of which events have been sent to auoms
#
# Default is ${data_dir}/collect.cursor
#cursor_path = /var/opt/microsoft/auoms/data/collect.cursor

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

@ -28,7 +28,6 @@ if [ "$1" = "upgrade" ]; then
fi
%Postinstall_100
echo "Postinstall_100 $1"
if [ "$1" = "configure" ]; then
if [ -e ${{AUOMS_AUDISP_CONF}}.auomssave ]; then
if [ -e ${{AUOMS_AUDISP_CONF}} ]; then