landlock/
errata.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use crate::compat::ABI;
4use crate::{uapi, BitFlags};
5use enumflags2::bitflags;
6
7/// Fixed kernel issues for the running Landlock implementation.
8///
9/// Each variant represents a specific bug fix that may have been
10/// backported to the running kernel.  Use [`Erratum::current()`]
11/// before building a [`Ruleset`](crate::Ruleset) to decide which
12/// features are safe to use.
13///
14/// An [`ABI`] version can be converted into the set of applicable errata
15/// with `BitFlags::<Erratum>::from(abi)`.
16///
17/// # Warning
18///
19/// Most applications should **not** check errata.  Disabling a sandboxing
20/// feature because an erratum is not fixed could leave the system **less**
21/// secure than using Landlock's best-effort protection with the buggy
22/// feature enabled.  Errata should only be used to **add** features
23/// (e.g., enabling a restriction only when its bug is confirmed fixed),
24/// never to remove them.
25#[bitflags]
26#[repr(u32)]
27#[derive(Copy, Clone, Debug, PartialEq, Eq)]
28#[non_exhaustive]
29pub enum Erratum {
30    /// Erratum 1 (ABI 4): non-TCP stream sockets (SMC, MPTCP, SCTP)
31    /// were incorrectly restricted by TCP access rights during
32    /// `bind(2)` and `connect(2)`.
33    ///
34    /// Affects [`crate::AccessNet::BindTcp`] and [`crate::AccessNet::ConnectTcp`].
35    ///
36    /// See [erratum 1](https://docs.kernel.org/userspace-api/landlock.html#erratum-1-tcp-socket-identification).
37    TcpSocketIdentification = 1 << 0,
38    /// Erratum 2 (ABI 6): signal scoping was overly restrictive,
39    /// preventing sandboxed threads from signaling other threads
40    /// within the same process in different domains.
41    ///
42    /// Affects [`crate::Scope::Signal`].
43    ///
44    /// See [erratum 2](https://docs.kernel.org/userspace-api/landlock.html#erratum-2-scoped-signal-handling).
45    ScopedSignalHandling = 1 << 1,
46    /// Erratum 3 (ABI 1): access rights could be widened through
47    /// rename or link actions on disconnected directories under
48    /// bind mounts, potentially bypassing `LANDLOCK_ACCESS_FS_REFER`
49    /// restrictions.
50    ///
51    /// See [erratum 3](https://docs.kernel.org/userspace-api/landlock.html#erratum-3-disconnected-directory-handling).
52    DisconnectedDirectoryHandling = 1 << 2,
53}
54
55impl Erratum {
56    /// Queries the running kernel for fixed errata.
57    ///
58    /// Returns a bitmask of errata that have been fixed in the running
59    /// kernel.  Unknown errata bits from newer kernels are preserved.
60    /// Returns empty if the kernel doesn't support the errata interface.
61    pub fn current() -> BitFlags<Self> {
62        let ret = unsafe {
63            uapi::landlock_create_ruleset(std::ptr::null(), 0, uapi::LANDLOCK_CREATE_RULESET_ERRATA)
64        };
65        if ret >= 0 {
66            // SAFETY: The kernel may return bits unknown to this crate version.
67            // Using from_bits_unchecked to preserve them.
68            unsafe { BitFlags::from_bits_unchecked(ret as u32) }
69        } else {
70            BitFlags::empty()
71        }
72    }
73}
74
75/// Converts an [`ABI`] version into the set of errata applicable to that ABI.
76///
77/// An erratum is applicable if the ABI includes the feature affected by the bug.
78/// For example, [`Erratum::TcpSocketIdentification`] is only applicable to
79/// [`ABI::V4`] and later, since TCP access rights were introduced in that version.
80///
81/// Uses the same incremental accumulation pattern as
82/// [`AccessFs::from_write()`](crate::AccessFs::from_write).
83///
84/// # Stability
85///
86/// The set of errata returned for a given ABI may grow in future versions
87/// of this crate as new kernel bug fixes are identified and backported.
88/// Do not rely on the exact set being stable across crate versions.
89impl From<ABI> for BitFlags<Erratum> {
90    fn from(abi: ABI) -> Self {
91        match abi {
92            ABI::Unsupported => BitFlags::empty(),
93            // Erratum 3: disconnected directory handling (FS, ABI 1+).
94            ABI::V1 | ABI::V2 | ABI::V3 => Erratum::DisconnectedDirectoryHandling.into(),
95            // Erratum 1: TCP socket identification (net, ABI 4+).
96            ABI::V4 | ABI::V5 => Self::from(ABI::V3) | Erratum::TcpSocketIdentification,
97            // Erratum 2: scoped signal handling (scopes, ABI 6+).
98            // When adding a new ABI version without new errata, append it here.
99            ABI::V6 => Self::from(ABI::V5) | Erratum::ScopedSignalHandling,
100        }
101    }
102}
103
104/// Returns the set of errata that have not been backported yet for a given ABI.
105///
106/// This is the single source of truth for known backport gaps.  When an
107/// erratum is backported to a kernel version, remove it from the
108/// corresponding match arm.  The CI will catch mismatches.
109#[cfg(test)]
110fn not_backported_yet(abi: ABI) -> BitFlags<Erratum> {
111    match abi {
112        ABI::Unsupported => BitFlags::empty(),
113        // TODO: erratum 3 (DisconnectedDirectoryHandling) should be backported.
114        ABI::V1 | ABI::V2 => Erratum::DisconnectedDirectoryHandling.into(),
115        // 6.4, 6.7, 6.10: EOL, no errata interface on stable.kernel.
116        ABI::V3 | ABI::V4 | ABI::V5 => BitFlags::empty(),
117        // 6.12: all errata backported.
118        ABI::V6 => BitFlags::empty(),
119    }
120}
121
122#[test]
123fn errata_query() {
124    // Verifies the syscall wrapper works on any kernel.
125    let _errata = Erratum::current();
126}
127
128#[test]
129fn errata_up_to_date() {
130    use crate::compat::{ABI, TEST_ABI, TEST_ABI_ENV_NAME};
131
132    // This test requires LANDLOCK_CRATE_TEST_ABI to be explicitly set because
133    // the errata assertions are tied to specific CI kernel versions.  Without
134    // it, TEST_ABI is auto-detected from the running kernel, but From<i32>
135    // maps unknown ABI versions to the highest known one, making the
136    // ABI-to-kernel mapping ambiguous (e.g., a 6.15 kernel maps to V6 before
137    // ABI::V7 exists).  Since Erratum::current() queries the real kernel, the
138    // expected errata for the declared ABI may not match.
139    if std::env::var(TEST_ABI_ENV_NAME).is_err() {
140        eprintln!("Skipping errata_up_to_date: {} not set", TEST_ABI_ENV_NAME,);
141        return;
142    }
143
144    let current = Erratum::current();
145    let applicable: BitFlags<Erratum> = (*TEST_ABI).into();
146    let expected = applicable & !not_backported_yet(*TEST_ABI);
147
148    // Kernel must never report errata for features absent from this ABI.
149    assert!(
150        current & !applicable == BitFlags::empty(),
151        "kernel reported errata not applicable to ABI {:?}: {:?}",
152        *TEST_ABI,
153        current & !applicable,
154    );
155
156    match *TEST_ABI {
157        ABI::Unsupported => assert!(current.is_empty()),
158        ABI::V1 | ABI::V2 => assert_eq!(current, expected),
159        // 6.4, 6.7, 6.10: EOL, no errata interface on stable.kernel.
160        ABI::V3 | ABI::V4 | ABI::V5 => {}
161        ABI::V6 => assert_eq!(current, expected),
162    }
163}