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