| 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 | } |