master
c 277 lines 10.4 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 // Linux audit subsystem status collector.
4 // Queries the kernel audit status via NETLINK_AUDIT socket (AUDIT_GET)
5 // and exposes backlog depth, lost events, configuration, and failure mode.
6
7 #include "debugfs_plugin.h"
8
9 #include <linux/audit.h>
10 #include <linux/netlink.h>
11 #include <sys/socket.h>
12 #include <sys/time.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <errno.h>
16
17 #define AUDIT_STATUS_MIN_PAYLOAD 32 // 8 fields (mask through backlog) = 32 bytes
18 #define AUDIT_RECV_TIMEOUT_MS 500 // netlink receive timeout in milliseconds
19 #define AUDIT_RECV_MAX_ATTEMPTS 5 // max recvfrom attempts per query
20 #define AUDIT_STARTUP_RETRIES 3 // startup failures before permanent disable
21
22 // -----------------------------------------------------------------------
23 // netlink audit query
24
25 struct audit_reply {
26 int valid; // whether the query succeeded
27 uint32_t enabled; // 0=disabled, 1=enabled, 2=immutable
28 uint32_t failure; // 0=silent, 1=printk, 2=panic
29 uint32_t pid; // audit daemon pid (0=no daemon)
30 uint32_t rate_limit; // max events/s (0=unlimited)
31 uint32_t backlog_limit;
32 uint32_t lost; // cumulative lost events
33 uint32_t backlog; // current queue depth
34 };
35
36 // query the kernel audit status via netlink
37 // returns 0 on success, -1 on failure
38 static int audit_netlink_query(struct audit_reply *reply) {
39 memset(reply, 0, sizeof(*reply));
40
41 int fd = socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_AUDIT);
42 if (fd < 0)
43 return -1;
44
45 // bind to the netlink socket (nl_pid=0 lets kernel auto-assign a unique port ID)
46 struct sockaddr_nl addr = {
47 .nl_family = AF_NETLINK,
48 .nl_pid = 0,
49 .nl_groups = 0,
50 };
51 if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
52 close(fd);
53 return -1;
54 }
55
56 // send AUDIT_GET request
57 struct {
58 struct nlmsghdr nlh;
59 struct audit_status s;
60 } req = {
61 .nlh = {
62 .nlmsg_len = NLMSG_LENGTH(sizeof(struct audit_status)),
63 .nlmsg_type = AUDIT_GET,
64 .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
65 .nlmsg_seq = 1,
66 .nlmsg_pid = 0,
67 },
68 .s = { 0 },
69 };
70
71 // send to kernel (nl_pid=0)
72 struct sockaddr_nl kernel_addr = {
73 .nl_family = AF_NETLINK,
74 .nl_pid = 0,
75 .nl_groups = 0,
76 };
77 if (sendto(fd, &req, req.nlh.nlmsg_len, 0,
78 (struct sockaddr *)&kernel_addr, sizeof(kernel_addr)) < 0) {
79 close(fd);
80 return -1;
81 }
82
83 // receive response
84 char buf[8192];
85
86 // set a timeout to avoid blocking the plugin's collection loop
87 struct timeval tv = { .tv_sec = 0, .tv_usec = AUDIT_RECV_TIMEOUT_MS * 1000 };
88 if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
89 close(fd);
90 return -1;
91 }
92
93 for (int attempts = 0; attempts < AUDIT_RECV_MAX_ATTEMPTS; attempts++) {
94 struct sockaddr_nl from;
95 socklen_t fromlen = sizeof(from);
96
97 ssize_t len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fromlen);
98 if (len < 0) {
99 if (errno == EINTR)
100 continue;
101 close(fd);
102 return -1;
103 }
104
105 // only accept messages from the kernel
106 if (from.nl_pid != 0)
107 continue;
108
109 // iterate all messages in the received buffer
110 int msg_len = (int)len;
111 for (struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
112 NLMSG_OK(nlh, msg_len);
113 nlh = NLMSG_NEXT(nlh, msg_len)) {
114
115 if (nlh->nlmsg_type == AUDIT_GET) {
116 if (nlh->nlmsg_len < NLMSG_LENGTH(AUDIT_STATUS_MIN_PAYLOAD))
117 continue;
118
119 struct audit_status *s = NLMSG_DATA(nlh);
120 reply->valid = 1;
121 reply->enabled = s->enabled;
122 reply->failure = s->failure;
123 reply->pid = s->pid;
124 reply->rate_limit = s->rate_limit;
125 reply->backlog_limit = s->backlog_limit;
126 reply->lost = s->lost;
127 reply->backlog = s->backlog;
128 close(fd);
129 return 0;
130 }
131
132 if (nlh->nlmsg_type == NLMSG_ERROR) {
133 if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr)))
134 continue;
135 struct nlmsgerr *err = NLMSG_DATA(nlh);
136 if (err->error == 0)
137 continue; // ACK, keep looking for AUDIT_GET
138 close(fd);
139 return -1;
140 }
141 }
142 }
143
144 close(fd);
145 return -1;
146 }
147
148 // -----------------------------------------------------------------------
149 // charts
150
151 static int charts_created = 0;
152
153 static void audit_send_charts(int update_every, const char *name) {
154 if (charts_created)
155 return;
156
157 charts_created = 1;
158
159 netdata_mutex_lock(&stdout_mutex);
160
161 // chart: audit backlog (stacked: used + free = backlog_limit)
162 printf(PLUGINSD_KEYWORD_CHART
163 " audit.backlog '' 'Audit Backlog' 'events' 'audit' 'audit.backlog' %s %d %d '' 'debugfs.plugin' '%s'\n",
164 debugfs_rrdset_type_name(RRDSET_TYPE_STACKED), NETDATA_CHART_PRIO_AUDIT_BACKLOG, update_every, name);
165 printf(PLUGINSD_KEYWORD_DIMENSION " 'used' 'used' %s 1 1 ''\n",
166 RRD_ALGORITHM_ABSOLUTE_NAME);
167 printf(PLUGINSD_KEYWORD_DIMENSION " 'free' 'free' %s 1 1 'hidden'\n",
168 RRD_ALGORITHM_ABSOLUTE_NAME);
169
170 // chart: audit backlog utilization (percentage)
171 printf(PLUGINSD_KEYWORD_CHART
172 " audit.backlog_utilization '' 'Audit Backlog Utilization' '%%' 'audit' 'audit.backlog_utilization' %s %d %d '' 'debugfs.plugin' '%s'\n",
173 debugfs_rrdset_type_name(RRDSET_TYPE_AREA), NETDATA_CHART_PRIO_AUDIT_BACKLOG_UTIL, update_every, name);
174 printf(PLUGINSD_KEYWORD_DIMENSION " 'utilization' 'utilization' %s 1 100 ''\n",
175 RRD_ALGORITHM_ABSOLUTE_NAME);
176
177 // chart: audit lost events
178 printf(PLUGINSD_KEYWORD_CHART
179 " audit.lost '' 'Audit Lost Events' 'events/s' 'audit' 'audit.lost' %s %d %d '' 'debugfs.plugin' '%s'\n",
180 debugfs_rrdset_type_name(RRDSET_TYPE_AREA), NETDATA_CHART_PRIO_AUDIT_LOST, update_every, name);
181 printf(PLUGINSD_KEYWORD_DIMENSION " 'lost' 'lost' %s 1 1 ''\n",
182 RRD_ALGORITHM_INCREMENTAL_NAME);
183
184 // chart: audit enabled state (exactly one dimension is 1 at any time)
185 printf(PLUGINSD_KEYWORD_CHART
186 " audit.enabled '' 'Audit Enabled State' 'state' 'audit' 'audit.enabled' %s %d %d '' 'debugfs.plugin' '%s'\n",
187 debugfs_rrdset_type_name(RRDSET_TYPE_LINE), NETDATA_CHART_PRIO_AUDIT_ENABLED, update_every, name);
188 printf(PLUGINSD_KEYWORD_DIMENSION " 'disabled' 'disabled' %s 1 1 ''\n",
189 RRD_ALGORITHM_ABSOLUTE_NAME);
190 printf(PLUGINSD_KEYWORD_DIMENSION " 'enabled' 'enabled' %s 1 1 ''\n",
191 RRD_ALGORITHM_ABSOLUTE_NAME);
192 printf(PLUGINSD_KEYWORD_DIMENSION " 'immutable' 'immutable' %s 1 1 ''\n",
193 RRD_ALGORITHM_ABSOLUTE_NAME);
194
195 // chart: audit failure mode (exactly one dimension is 1 at any time)
196 printf(PLUGINSD_KEYWORD_CHART
197 " audit.failure '' 'Audit Failure Mode' 'state' 'audit' 'audit.failure' %s %d %d '' 'debugfs.plugin' '%s'\n",
198 debugfs_rrdset_type_name(RRDSET_TYPE_LINE), NETDATA_CHART_PRIO_AUDIT_FAILURE, update_every, name);
199 printf(PLUGINSD_KEYWORD_DIMENSION " 'silent' 'silent' %s 1 1 ''\n",
200 RRD_ALGORITHM_ABSOLUTE_NAME);
201 printf(PLUGINSD_KEYWORD_DIMENSION " 'printk' 'printk' %s 1 1 ''\n",
202 RRD_ALGORITHM_ABSOLUTE_NAME);
203 printf(PLUGINSD_KEYWORD_DIMENSION " 'panic' 'panic' %s 1 1 ''\n",
204 RRD_ALGORITHM_ABSOLUTE_NAME);
205
206 fflush(stdout);
207 netdata_mutex_unlock(&stdout_mutex);
208 }
209
210 static void audit_send_data(struct audit_reply *r) {
211 netdata_mutex_lock(&stdout_mutex);
212
213 // backlog (stacked: used + free = backlog_limit)
214 uint32_t free_backlog = (r->backlog_limit > r->backlog) ? r->backlog_limit - r->backlog : 0;
215 printf(PLUGINSD_KEYWORD_BEGIN " audit.backlog\n");
216 printf(PLUGINSD_KEYWORD_SET " used = %u\n", r->backlog);
217 printf(PLUGINSD_KEYWORD_SET " free = %u\n", free_backlog);
218 printf(PLUGINSD_KEYWORD_END "\n");
219
220 // backlog utilization (percentage)
221 collected_number utilization = 0;
222 if (r->backlog_limit > 0)
223 utilization = (collected_number)r->backlog * 10000 / (collected_number)r->backlog_limit;
224 printf(PLUGINSD_KEYWORD_BEGIN " audit.backlog_utilization\n");
225 printf(PLUGINSD_KEYWORD_SET " utilization = %lld\n", utilization);
226 printf(PLUGINSD_KEYWORD_END "\n");
227
228 // lost events (incremental)
229 printf(PLUGINSD_KEYWORD_BEGIN " audit.lost\n");
230 printf(PLUGINSD_KEYWORD_SET " lost = %u\n", r->lost);
231 printf(PLUGINSD_KEYWORD_END "\n");
232
233 // enabled state
234 printf(PLUGINSD_KEYWORD_BEGIN " audit.enabled\n");
235 printf(PLUGINSD_KEYWORD_SET " disabled = %d\n", r->enabled == 0 ? 1 : 0);
236 printf(PLUGINSD_KEYWORD_SET " enabled = %d\n", r->enabled == 1 ? 1 : 0);
237 printf(PLUGINSD_KEYWORD_SET " immutable = %d\n", r->enabled == 2 ? 1 : 0);
238 printf(PLUGINSD_KEYWORD_END "\n");
239
240 // failure mode
241 printf(PLUGINSD_KEYWORD_BEGIN " audit.failure\n");
242 printf(PLUGINSD_KEYWORD_SET " silent = %d\n", r->failure == 0 ? 1 : 0);
243 printf(PLUGINSD_KEYWORD_SET " printk = %d\n", r->failure == 1 ? 1 : 0);
244 printf(PLUGINSD_KEYWORD_SET " panic = %d\n", r->failure == 2 ? 1 : 0);
245 printf(PLUGINSD_KEYWORD_END "\n");
246
247 fflush(stdout);
248 netdata_mutex_unlock(&stdout_mutex);
249 }
250
251 // -----------------------------------------------------------------------
252 // module entry point
253
254 int do_module_audit(int update_every, const char *name) {
255 static int startup_retries = AUDIT_STARTUP_RETRIES;
256
257 struct audit_reply reply;
258 if (audit_netlink_query(&reply) < 0 || !reply.valid) {
259 if (startup_retries > 0) {
260 startup_retries--;
261 if (startup_retries == 0) {
262 netdata_log_info("audit: netlink AUDIT_GET query failed, audit module disabled");
263 return 1; // permanently disable after exhausting retries
264 }
265 return 0; // retry next cycle
266 }
267 return 0; // transient failure after startup, keep module enabled
268 }
269
270 // mark startup as successful
271 startup_retries = 0;
272
273 audit_send_charts(update_every, name);
274 audit_send_data(&reply);
275
276 return 0;
277 }