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}