说明
上一篇通过分析osquery中的socket_events
的实现对linux中的auditd的一些简单使用以及auditd日志文件的结构进行了讲解,本篇文章聚焦于osquery中的audit的实现。
前言
在之前的文章osquery源码解读之分析socket_events中已经说到,在osquery接管了auditd服务之后,就会增加三条规则:1
2
3-a always,exit -S connect
-a always,exit -S bind
-a always,exit -S execve
在正常情况下如果我们要设置audit的规则,要么在audit.rules
中添加规则要么就通过auditctl
增加。但是如果我们使用osquery,osquery也能够增加规则。通过分析osquery这部分的源代码来了解osquery是如何通过代码来给auditd
增减规则的。在osquery源码解读之分析socket_events中,在osquery
采用的是event publisher/subscriber
的模式,也说到了socket_events
数据解析是通过接受audit过滤之后的数据。而socket_event.cpp
(osquery/tables/events/linux/socket_events.cpp
)其实就是一个订阅者的模式。
通过以上的分析,我们知道在osquery中存在对audit
规则进行设置以及event
的发布者的功能。接下来我们将一一进行分析。
auditdnetlink
在osquery源码解读之分析socket_events中也简单地提到了,系统中的内核kaudit和用户态的消息传递采用的就是netlink。auditdnetlink.cpp
(osquery/events/linux/auditdnetlink.cpp
)就是用于获取netlink
之后设置规则。
在auditdnetlink.cpp
存在如下的函数:
- AuditdNetlink::getEvents()
- AuditdNetlinkReader::AuditdNetlinkReader
- AuditdNetlinkReader::start()
- AuditdNetlinkReader::stop()
- AuditdNetlinkReader::acquireMessages()
- AuditdNetlinkReader::configureAuditService()
- AuditdNetlinkReader::clearAuditConfiguration()
- AuditdNetlinkReader::deleteAuditRule
- AuditdNetlinkReader::restoreAuditServiceConfiguration()
- AuditdNetlinkReader::acquireHandle()
- AuditdNetlinkParser::AuditdNetlinkParser(AuditdContextRef context)
- AuditdNetlinkParser::start()
- AuditdNetlinkParser::ParseAuditReply
- AuditdNetlinkParser::AdjustAuditReply(audit_reply& reply)
函数的功能很多都表明了这个函数的作用。我们先从start()
开始分析。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47void AuditdNetlinkReader::start() {
int counter_to_next_status_request = 0;
const int status_request_countdown = 1000;
while (!interrupted()) {
if (auditd_context_->acquire_handle) {
if (FLAGS_audit_debug) {
std::cout << "(re)acquiring the audit handle.." << std::endl;
}
NetlinkStatus netlink_status = acquireHandle();
if (netlink_status == NetlinkStatus::Disabled ||
netlink_status == NetlinkStatus::Error) {
std::this_thread::sleep_for(std::chrono::seconds(5));
continue;
}
auditd_context_->acquire_handle = false;
counter_to_next_status_request = status_request_countdown;
}
if (counter_to_next_status_request == 0) {
errno = 0;
if (audit_request_status(audit_netlink_handle_) <= 0) {
if (errno == ENOBUFS) {
VLOG(1) << "Warning: Failed to request audit status (ENOBUFS). "
"Retrying again later";
} else {
VLOG(1) << "Error: Failed to request audit status. Requesting a "
"handle reset";
auditd_context_->acquire_handle = true;
}
}
counter_to_next_status_request = status_request_countdown;
} else {
--counter_to_next_status_request;
}
if (!acquireMessages()) {
auditd_context_->acquire_handle = true;
}
}
}
其中关键的代码是NetlinkStatus netlink_status = acquireHandle();
用于获取netlinkhander
,跟进代码分析;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79NetlinkStatus AuditdNetlinkReader::acquireHandle() noexcept {
auto L_GetNetlinkStatus = [](int netlink_handle) -> NetlinkStatus {
if (netlink_handle <= 0) {
return NetlinkStatus::Error;
}
errno = 0;
if (audit_request_status(netlink_handle) < 0 && errno != ENOBUFS) {
VLOG(1) << "Failed to query the audit netlink status";
return NetlinkStatus::Error;
}
auto enabled = audit_is_enabled(netlink_handle);
if (enabled == AUDIT_IMMUTABLE || getuid() != 0 ||
!FLAGS_audit_allow_config) {
return NetlinkStatus::ActiveImmutable;
} else if (enabled == AUDIT_ENABLED) {
return NetlinkStatus::ActiveMutable;
} else if (enabled == AUDIT_DISABLED) {
return NetlinkStatus::Disabled;
} else {
return NetlinkStatus::Error;
}
};
if (audit_netlink_handle_ != -1) {
audit_close(audit_netlink_handle_);
audit_netlink_handle_ = -1;
}
audit_netlink_handle_ = audit_open();
if (audit_netlink_handle_ <= 0) {
VLOG(1) << "Failed to acquire the netlink handle";
audit_netlink_handle_ = -1;
return NetlinkStatus::Error;
}
.......
NetlinkStatus netlink_status = L_GetNetlinkStatus(audit_netlink_handle_);
if (FLAGS_audit_allow_config &&
(netlink_status != NetlinkStatus::ActiveMutable &&
netlink_status != NetlinkStatus::ActiveImmutable)) {
if (audit_set_enabled(audit_netlink_handle_, AUDIT_ENABLED) < 0) {
VLOG(1) << "Failed to enable the audit service";
audit_close(audit_netlink_handle_);
audit_netlink_handle_ = -1;
return NetlinkStatus::Error;
}
if (FLAGS_audit_debug) {
std::cout << "Audit service enabled" << std::endl;
}
}
if (FLAGS_audit_allow_config) {
if (FLAGS_audit_force_reconfigure) {
if (!clearAuditConfiguration()) {
audit_netlink_handle_ = -1;
return NetlinkStatus::Error;
}
}
if (!configureAuditService()) {
return NetlinkStatus::ActiveImmutable;
}
if (FLAGS_audit_debug) {
std::cout << "Audit service configured" << std::endl;
}
}
return NetlinkStatus::ActiveMutable;
}
代码较长,我们分步进行分析:
- 首先定义了
L_GetNetlinkStatus(int netlink_handle)
函数,作用是根据netlink的fd来判断netlink的状态,状态分别有AUDIT_IMMUTABLE
(根据Disable auditd immutable mode without rebooting的说法,当处于netlink
处于AUDIT_IMMUTABLE
模式时需要重启规则才能够生效)、AUDIT_ENABLED
、AUDIT_DISABLED
。其中最为关键的函数是auto enabled = audit_is_enabled(netlink_handle)
(audit_is_enabled
是位于libaudit
中,作用就是根据netlink的句柄知道netlink的状态),根据audit_is_enabled的实现知道This function will return 0 if auditing is NOT enabled and 1 if enabled, and -1 on error.
。那么AUDIT_IMMUTABLE
,AUDIT_ENABLED
,AUDIT_DISABLED
和Error
分别对应此函数值的返回值的2,1,0,-1。这就是L_GetNetlinkStatus()
获得netlink状态的流程。 - 在第一步讲了通过
L_GetNetlinkStatus()
根据netlink_handle
获取netlink
的状态,那么第二部就是如何获取netlink_handle
了。audit_netlink_handle_ = audit_open();
通过audit_open()
获取状态。根据audit_open的说明,audit_open creates a NETLINK_AUDIT socket for communication with the kernel part of the Linux Audit Subsystem
,含义大致就是创建了一个NETLINK_AUDIT
用于和内核的audit服务进行通信,返回值就是audit_netlink_handle_
- 利用
NetlinkStatus netlink_status = L_GetNetlinkStatus(audit_netlink_handle_);
综合前面2步,获取当前netlink的状态,判断是否满足当前要求。 接下来就是函数的核心部分了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16if (FLAGS_audit_allow_config) {
if (FLAGS_audit_force_reconfigure) {
if (!clearAuditConfiguration()) {
audit_netlink_handle_ = -1;
return NetlinkStatus::Error;
}
}
if (!configureAuditService()) {
return NetlinkStatus::ActiveImmutable;
}
if (FLAGS_audit_debug) {
std::cout << "Audit service configured" << std::endl;
}
}FLAGS_audit_allow_config
和FLAGS_audit_force_reconfigure
,两个参数的作用表示是否需要清空之前系统中存在的audit的规则。clearAuditConfiguration()
在1的基础上,调用clearAuditConfiguration()
清空规则。删除方法大致如下:audit_get_reply(audit_netlink_handle_, &reply, GET_REPLY_NONBLOCKING, 0)
得到当前auditd中所有设置的规则;- 调用
deleteAuditRule(const AuditRuleDataObject &rule_object)
函数完成删除工作。
deleteAuditRule()
的主要代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16bool AuditdNetlinkReader::deleteAuditRule(const AuditRuleDataObject &rule_object) {
....
auto rule_data =reinterpret_cast<const struct audit_rule_data *>(rule_object.data());
struct audit_message request = {};
request.nlh.nlmsg_len = static_cast<__u32>(NLMSG_SPACE(rule_object.size()));
request.nlh.nlmsg_type = AUDIT_DEL_RULE;
request.nlh.nlmsg_flags = NLM_F_REQUEST;
std::memcpy(NLMSG_DATA(&request.nlh), rule_data, rule_object.size());
.....
bytes_sent = sendto(audit_netlink_handle_,
&request,
request.nlh.nlmsg_len,
0,
reinterpret_cast<struct sockaddr *>(&address),
.....由于代码较长,仅仅显示部分代码,可以看到其实最终是利用
sendto()
方法来删除规则;configureAuditService()
,在清空了系统中已经存在的规则之后,利用configureAuditService()
设置osquery需要的规则。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57bool AuditdNetlinkReader::configureAuditService() noexcept {
// Want to set a min sane buffer and maximum number of events/second min.
// This is normally controlled through the audit config, but we must
// enforce sane minimums: -b 8192 -e 100
audit_set_backlog_wait_time(audit_netlink_handle_, 1);
audit_set_backlog_limit(audit_netlink_handle_, 4096);
audit_set_failure(audit_netlink_handle_, AUDIT_FAIL_SILENT);
// Request only the highest priority of audit status messages.
set_aumessage_mode(MSG_QUIET, DBG_NO);
//
// Audit rules
//
// Rules required by the socket_events table
if (FLAGS_audit_allow_sockets) {
VLOG(1) << "Enabling audit rules for the socket_events table";
for (int syscall : SocketEventSubscriber::GetSyscallSet()) {
monitored_syscall_list_.insert(syscall);
}
}
// Rules required by the process_events table
if (FLAGS_audit_allow_process_events) {
VLOG(1) << "Enabling audit rules for the process_events table";
for (int syscall : AuditProcessEventSubscriber::GetSyscallSet()) {
monitored_syscall_list_.insert(syscall);
}
}
// Rules required by the process_file_events table
if (FLAGS_audit_allow_fim_events) {
VLOG(1) << "Enabling audit rules for the process_file_events table";
for (int syscall : ProcessFileEventSubscriber::GetSyscallSet()) {
monitored_syscall_list_.insert(syscall);
}
}
// Attempt to add each one of the rules we collected
for (int syscall_number : monitored_syscall_list_) {
audit_rule_data rule = {};
audit_rule_syscall_data(&rule, syscall_number);
// clang-format off
int rule_add_error = audit_add_rule_data(audit_netlink_handle_, &rule,
// We want to be notified when we exit from the syscall
AUDIT_FILTER_EXIT,
// Always audit this syscall event
AUDIT_ALWAYS
);
......首先会设置audit的基本规则
1
2
3audit_set_backlog_wait_time(audit_netlink_handle_, 1);
audit_set_backlog_limit(audit_netlink_handle_, 4096);
audit_set_failure(audit_netlink_handle_, AUDIT_FAIL_SILENT);audit_set_backlog_wait_time,表示当当event日志数量达到
backlog_limit
限制时,内核的等待时间;audit_set_backlog_limit,表示在audit events的缓冲区中能够存储的events数量上限;audit_set_failure中表示当events达到backlog limit
上限时的操作(0,忽略;1,使用printk记录事件;2,调用panic function)。根据不同的设定
FLAGS_audit_allow_sockets
、FLAGS_audit_allow_process_events
、FLAGS_audit_allow_fim_events
增加响应的规则。由于这三者的处理方法都一样,以FLAGS_audit_allow_sockets
为例进行说明;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//osquery/events/linux/auditeventpublisher.h
std::set<int> monitored_syscall_list_;
....
//osquery/events/linux/auditdnetlink.cpp
if (FLAGS_audit_allow_sockets) {
for (int syscall : SocketEventSubscriber::GetSyscallSet()) {
monitored_syscall_list_.insert(syscall);
}
}
......
//osquery/tables/events/linux/socket_events.cpp
const std::set<int> &SocketEventSubscriber::GetSyscallSet() noexcept {
//__NR_bind, __NR_connect 都是Linux中系统自行定义的函数
static const std::set<int> syscall_set = {__NR_bind, __NR_connect};
return syscall_set;
}在
asm/unistd_64.h
对所有的系统调用函数都有定义。在/arch/sh/include/uapi/asm/unistd_64.h中同样有说明,实例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/* Non-multiplexed socket family */
所以在
GetSyscallSet()
中返回的__NR_bind
和__NR_connect
分别是221
和222
,之后保存到monitored_syscall_list_
变量中提取
monitored_syscall_list_
中的规则:1
2
3
4for (int syscall_number : monitored_syscall_list_) {
audit_rule_data rule = {};
audit_rule_syscall_data(&rule, syscall_number);
....audit_rule_syscall_data()
将每一个syscall_number
变为audit_rule_data
规则,实现参考audit_rule_syscall_data设置规则,
int rule_add_error = audit_add_rule_data(audit_netlink_handle_, &rule,AUDIT_FILTER_EXIT,AUDIT_ALWAYS);
,audit_add_rule_data位于libaudit.h
中,就是用来添加过滤规则的。如此,最终就得到了如下的规则。1
2
3-a always,exit -S connect
-a always,exit -S bind
-a always,exit -S execve
以上就是整个规则的设定,在设置完规则之后,接下来就是接受audit审计之后的数据了。调用
acquireMessages()
,关键代码是:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27bool AuditdNetlinkReader::acquireMessages() noexcept {
...
audit_reply reply = {};
ssize_t len = recvfrom(audit_netlink_handle_,
&reply.msg,
sizeof(reply.msg),
0,
reinterpret_cast<struct sockaddr *>(&nladdr),
&nladdrlen);
....
read_buffer_[events_received] = reply;
if (events_received != 0) {
std::unique_lock <std::mutex> lock(
auditd_context_->unprocessed_records_mutex);
auditd_context_->unprocessed_records.reserve(
auditd_context_->unprocessed_records.size() + events_received);
auditd_context_->unprocessed_records.insert(
auditd_context_->unprocessed_records.end(),
read_buffer_.begin(),
std::next(read_buffer_.begin(), events_received));
auditd_context_->unprocessed_records_cv.notify_all();
}
...
}首先调用
recvfrom()
函数从audit_netlink_handle_
接受数据,将数据全部填充至reply
中;之后将reply
中的数据全部放入auditd_context_
中。AuditdNetlinkParser::ParseAuditReply()
在第8步中说道利用acquireMessages()
将数据全部保存至auditd_context_
,那么ParseAuditReply()
就是来解析auditd_context_
中的数据的。部分代码如下所示:1
2
3
4
5
6
7
8
9
10
11
12bool AuditdNetlinkParser::ParseAuditReply(const audit_reply &reply, AuditEventRecord &event_record) noexcept {
....
// Parse the record header
event_record.type = reply.type;
boost::string_ref message_view(reply.message,static_cast<unsigned int>(reply.len));
auto preamble_end = message_view.find("): ");
if (preamble_end == std::string::npos) {
return false;
}
event_record.time =tryTo<unsigned long int>(message_view.substr(6, 10).to_string(), 10).takeOr(event_record.time);
event_record.audit_id = message_view.substr(6, preamble_end - 6).to_string();- 通过
boost::string_ref message_view(reply.message,static_cast<unsigned int>(reply.len));
将type中的数据全部保存至message_view
中。 之后通过
message_view
解析得到event_record.time
和event_record.audit_id
。我们以一个实际的例子来进行说明。1
type=SYSCALL msg=audit(1544195763.393:260010): arch=c000003e syscall=42 success=no exit=-115 a0=3 a1=7ffccb794910 a2=10 a3=7ffccb7941e0 items=0 ppid=53240 pid=17096 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts5 ses=1 comm="curl" exe="/usr/bin/curl" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=(null)
其中
event_record.time =tryTo<unsigned long int>(message_view.substr(6, 10).to_string(), 10).takeOr(event_record.time);
,中得到的就是1544195763
而event_record.audit_id = message_view.substr(6, preamble_end - 6).to_string();
得到的就是1544195763.393:260010
。所以其实ParseAuditReply()
中的message_view
数据也像audit.log
中的数据一样需要自己来对数据进行解析分割然后填充至对应的表中;
- 通过
以上就是整个的osquery设定audit的规则以及获取audit的数据并解析的过程。
AuditEventPublisher
osquery/events/linux/auditeventpublisher.cpp
此文件就是audit event的消息发布者。通过分析源代码,我们来看看他是如何发布event消息的。
GetEventRecord
1 | const AuditEventRecord* GetEventRecord(const AuditEvent& event, |
通过GetEventRecord()
函数所有的AuditEventRecord
消息,而这些消息都是由之前AuditdNetlinkParser::ParseAuditReply()
解析得到的。
ProcessEvents
由于AuditEventPublisher::ProcessEvents(AuditEventContextRef event_context,const std::vector<AuditEventRecord>& record_list,AuditTraceContext& trace_context)
的函数实现较长,我们仅以其中的关键部分为例来进行说明。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17for (const auto& audit_event_record : record_list) {
auto audit_event_it = trace_context.find(audit_event_record.audit_id); //将audit_id转换为audit_event_it
if (audit_event_record.type >= AUDIT_FIRST_USER_MSG &&audit_event_record.type <= AUDIT_LAST_USER_MSG) {
....
} else if (selinux_event_set.find(audit_event_record.type) !=selinux_event_set.end()) {
...
} else if (audit_event_record.type == AUDIT_SYSCALL) {
if (audit_event_it != trace_context.end()) {
VLOG(1) << "Received a duplicated event.";
trace_context.erase(audit_event_it);
}
AuditEvent audit_event;
audit_event.type = AuditEvent::Type::Syscall;
// Estimate based on the process_file_events_tests.cpp records
audit_event.record_list.reserve(4U);
通过遍历record_list
得到audit_event_record
并根据其type
分别进行不同处理,我们这里仅仅只关注AUDIT_SYSCALL
类型。通过检测audit_event_it
是否在trace_context
来避免重复处理消息。接下来就是对原始的audit的日志解码了。
data.executable_path = DecodeAuditPathValues(raw_executable_path);
获取executable_path
获取系统调用结果
syscall_status
1
2
3
4
5
6
7
8
9
10std::string syscall_status;
GetStringFieldFromMap(
syscall_status, audit_event_record.fields, "success", "yes");
// By discarding this event, we will also automatically discard any other
// attached record
// 如果 syscall_status 不是success,则自动丢弃,但是这样会导致很多的服务都会存在问题,很多的日志都会被丢掉
if (syscall_status != "yes") {
continue;
}获取pid
1
2
3
4
5std::uint64_t process_id;
if (!GetIntegerFieldFromMap(process_id, audit_event_record.fields, "pid")) {
VLOG(1) << "Malformed AUDIT_SYSCALL record received. The process id field is either missing or not valid.";
continue;
}通过获取
audit_event_record
中的pid字段的内容。同理获取ppid
1
2
3
4std::uint64_t parent_process_id;
if (!GetIntegerFieldFromMap(parent_process_id, audit_event_record.fields, "ppid")) {
continue;
}其他的参数以此类推,包括
uid
、euid
、gid
和egid
都是相同的方法获取。这里就不一一展示了。存储数据
1
2audit_event.record_list.push_back(audit_event_record);
trace_context[audit_event_record.audit_id] = std::move(audit_event);完成了基本的数据解析之后,利用
audit_event
保留所有解析的数据。trace_context将audit_id
和audit_event
以键值对的方式保存;
SocketEventSubscriber&AuditProcessEventSubscriber
在AuditEventPublisher
已经完成了消息的发布,那么SocketEventSubscriber
和AuditProcessEventSubscriber
通过订阅的方式就能够获取自己需要的消息了;1
2REGISTER(SocketEventSubscriber,"event_subscriber", "socket_events");
REGISTER(AuditProcessEventSubscriber, "event_subscriber", "process_events");
他们的消息处理方法是:socket_events.cpp
1
2
3
4
5
6
7
8
9Status SocketEventSubscriber::ProcessEvents(std::vector <Row> &emitted_row_list,const std::vector <AuditEvent> &event_list) noexcept {
emitted_row_list.clear();
emitted_row_list.reserve(event_list.size());
// 判断是否是Syscall
for (const auto &event : event_list) {
if (event.type != AuditEvent::Type::Syscall) {
continue;
}
....
process_events.cpp
1
2
3
4
5
6
7
8Status AuditProcessEventSubscriber::ProcessEvents(std::vector<Row>& emitted_row_list,const std::vector<AuditEvent>& event_list) noexcept {
emitted_row_list.clear();
emitted_row_list.reserve(event_list.size());
for (const auto& event : event_list) {
if (event.type != AuditEvent::Type::Syscall) {
continue;
}
.....
可以发现socket_events.cpp
和process_events.cpp
处理方法基本相同,都是对event_list
进行遍历处理,而event_list
就是来自于前面AuditEventPublisher
所产生的日志;整个流程就完全是连通的啦,如果想了解socket_events.cpp
是如何处理数据的,可以参考前一篇文章osquery源码解读之分析socket_events
总结
一个auditd
就读了这么长时间,怎么时候才能够如同庖丁解牛一般对Linux了如指掌啊。
拥有快速学习能⼒的⽩帽子,是不能有短板的。有的只是⼤量的标准板和⼏块长板。
以上