netlink_packet_audit/codec.rs
1// SPDX-License-Identifier: MIT
2
3use std::{fmt::Debug, io};
4
5use bytes::BytesMut;
6use netlink_packet_core::{
7 NetlinkBuffer, NetlinkDeserializable, NetlinkMessage, NetlinkSerializable,
8};
9pub(crate) use netlink_proto::{NetlinkCodec, NetlinkMessageCodec};
10
11/// audit specific implementation of [`NetlinkMessageCodec`] due to the
12/// protocol violations in messages generated by kernal audit.
13///
14/// Among the known bugs in kernel audit messages:
15/// - `nlmsg_len` sometimes contains the padding too (it shouldn't)
16/// - `nlmsg_len` sometimes doesn't contain the header (it really should)
17///
18/// See also:
19/// - https://blog.des.no/2020/08/netlink-auditing-and-counting-bytes/
20/// - https://github.com/torvalds/linux/blob/b5013d084e03e82ceeab4db8ae8ceeaebe76b0eb/kernel/audit.c#L2386
21/// - https://github.com/mozilla/libaudit-go/issues/24
22/// - https://github.com/linux-audit/audit-userspace/issues/78
23#[non_exhaustive]
24pub struct NetlinkAuditCodec {
25 // we don't need an instance of this, just the type
26 _private: (),
27}
28
29impl NetlinkMessageCodec for NetlinkAuditCodec {
30 fn decode<T>(src: &mut BytesMut) -> io::Result<Option<NetlinkMessage<T>>>
31 where
32 T: NetlinkDeserializable + Debug,
33 {
34 debug!("NetlinkAuditCodec: decoding next message");
35
36 loop {
37 // If there's nothing to read, return Ok(None)
38 if src.is_empty() {
39 trace!("buffer is empty");
40 return Ok(None);
41 }
42
43 // This is a bit hacky because we don't want to keep `src`
44 // borrowed, since we need to mutate it later.
45 let src_len = src.len();
46 let len = match NetlinkBuffer::new_checked(src.as_mut()) {
47 Ok(mut buf) => {
48 if (src_len as isize - buf.length() as isize) <= 16 {
49 // The audit messages are sometimes truncated,
50 // because the length specified in the header,
51 // does not take the header itself into
52 // account. To workaround this, we tweak the
53 // length. We've noticed two occurences of
54 // truncated packets:
55 //
56 // - the length of the header is not included (see also:
57 // https://github.com/mozilla/libaudit-go/issues/24)
58 // - some rule message have some padding for alignment (see
59 // https://github.com/linux-audit/audit-userspace/issues/78) which is not
60 // taken into account in the buffer length.
61 //
62 // How do we know that's the right length? Due to an
63 // implementation detail and to
64 // the fact that netlink is a datagram protocol.
65 //
66 // - our implementation of Stream always calls the codec
67 // with at most 1 message in the buffer, so we know
68 // the extra bytes do not belong to another message.
69 // - because netlink is a datagram protocol, we receive
70 // entire messages, so we know that if those extra
71 // bytes do not belong to another message, they belong
72 // to this one.
73 warn!("found what looks like a truncated audit packet");
74 // also write correct length to buffer so parsing does
75 // not fail:
76 warn!(
77 "setting packet length to {} instead of {}",
78 src_len,
79 buf.length()
80 );
81 buf.set_length(src_len as u32);
82 src_len
83 } else {
84 buf.length() as usize
85 }
86 }
87 Err(e) => {
88 // We either received a truncated packet, or the
89 // packet if malformed (invalid length field). In
90 // both case, we can't decode the datagram, and we
91 // cannot find the start of the next one (if
92 // any). The only solution is to clear the buffer
93 // and potentially lose some datagrams.
94 error!(
95 "failed to decode datagram, clearing buffer: {:?}: {:#x?}.",
96 e,
97 src.as_ref()
98 );
99 src.clear();
100 return Ok(None);
101 }
102 };
103
104 let bytes = src.split_to(len);
105
106 let parsed = NetlinkMessage::<T>::deserialize(&bytes);
107 match parsed {
108 Ok(packet) => {
109 trace!("<<< {:?}", packet);
110 return Ok(Some(packet));
111 }
112 Err(e) => {
113 error!("failed to decode packet {:#x?}: {}", &bytes, e);
114 // continue looping, there may be more datagrams in the
115 // buffer
116 }
117 }
118 }
119 }
120
121 fn encode<T>(msg: NetlinkMessage<T>, buf: &mut BytesMut) -> io::Result<()>
122 where
123 T: Debug + NetlinkSerializable,
124 {
125 NetlinkCodec::encode(msg, buf)
126 }
127}