// SPDX-License-Identifier: MIT use std::{fmt::Debug, io}; use bytes::BytesMut; use netlink_packet_core::{ NetlinkBuffer, NetlinkDeserializable, NetlinkMessage, NetlinkSerializable, }; pub(crate) use netlink_proto::{NetlinkCodec, NetlinkMessageCodec}; /// audit specific implementation of [`NetlinkMessageCodec`] due to the /// protocol violations in messages generated by kernal audit. /// /// Among the known bugs in kernel audit messages: /// - `nlmsg_len` sometimes contains the padding too (it shouldn't) /// - `nlmsg_len` sometimes doesn't contain the header (it really should) /// /// See also: /// - https://blog.des.no/2020/08/netlink-auditing-and-counting-bytes/ /// - https://github.com/torvalds/linux/blob/b5013d084e03e82ceeab4db8ae8ceeaebe76b0eb/kernel/audit.c#L2386 /// - https://github.com/mozilla/libaudit-go/issues/24 /// - https://github.com/linux-audit/audit-userspace/issues/78 #[non_exhaustive] pub struct NetlinkAuditCodec { // we don't need an instance of this, just the type _private: (), } impl NetlinkMessageCodec for NetlinkAuditCodec { fn decode(src: &mut BytesMut) -> io::Result>> where T: NetlinkDeserializable + Debug, { debug!("NetlinkAuditCodec: decoding next message"); loop { // If there's nothing to read, return Ok(None) if src.is_empty() { trace!("buffer is empty"); return Ok(None); } // This is a bit hacky because we don't want to keep `src` // borrowed, since we need to mutate it later. let src_len = src.len(); let len = match NetlinkBuffer::new_checked(src.as_mut()) { Ok(mut buf) => { if (src_len as isize - buf.length() as isize) <= 16 { // The audit messages are sometimes truncated, // because the length specified in the header, // does not take the header itself into // account. To workaround this, we tweak the // length. We've noticed two occurences of // truncated packets: // // - the length of the header is not included (see also: // https://github.com/mozilla/libaudit-go/issues/24) // - some rule message have some padding for alignment (see // https://github.com/linux-audit/audit-userspace/issues/78) which is not // taken into account in the buffer length. // // How do we know that's the right length? Due to an // implementation detail and to // the fact that netlink is a datagram protocol. // // - our implementation of Stream always calls the codec // with at most 1 message in the buffer, so we know // the extra bytes do not belong to another message. // - because netlink is a datagram protocol, we receive // entire messages, so we know that if those extra // bytes do not belong to another message, they belong // to this one. warn!("found what looks like a truncated audit packet"); // also write correct length to buffer so parsing does // not fail: warn!( "setting packet length to {} instead of {}", src_len, buf.length() ); buf.set_length(src_len as u32); src_len } else { buf.length() as usize } } Err(e) => { // We either received a truncated packet, or the // packet if malformed (invalid length field). In // both case, we can't decode the datagram, and we // cannot find the start of the next one (if // any). The only solution is to clear the buffer // and potentially lose some datagrams. error!( "failed to decode datagram, clearing buffer: {:?}: {:#x?}.", e, src.as_ref() ); src.clear(); return Ok(None); } }; let bytes = src.split_to(len); let parsed = NetlinkMessage::::deserialize(&bytes); match parsed { Ok(packet) => { trace!("<<< {:?}", packet); return Ok(Some(packet)); } Err(e) => { error!("failed to decode packet {:#x?}: {}", &bytes, e); // continue looping, there may be more datagrams in the // buffer } } } } fn encode(msg: NetlinkMessage, buf: &mut BytesMut) -> io::Result<()> where T: Debug + NetlinkSerializable, { NetlinkCodec::encode(msg, buf) } }