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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
// 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
pub struct NetlinkAuditCodec {
// we don't need an instance of this, just the type
_private: (),
}
impl NetlinkMessageCodec for NetlinkAuditCodec {
fn decode<T>(src: &mut BytesMut) -> io::Result<Option<NetlinkMessage<T>>>
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::<T>::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<T>(msg: NetlinkMessage<T>, buf: &mut BytesMut) -> io::Result<()>
where
T: Debug + NetlinkSerializable,
{
NetlinkCodec::encode(msg, buf)
}
}