Skip to main content

luks/
error.rs

1//! Error type for LUKS parsing and unlocking.
2
3use std::io;
4
5/// Result alias for `luks-core`.
6pub type Result<T> = std::result::Result<T, LuksError>;
7
8/// A LUKS parse or unlock failure. Every variant names the offending value so an
9/// investigator can act on it (never a bare "invalid").
10#[derive(Debug, thiserror::Error)]
11#[non_exhaustive]
12pub enum LuksError {
13    /// The `LUKS\xba\xbe` magic is absent — not a LUKS container.
14    #[error("not a LUKS container: magic is {found:02x?}, expected 4c554b53babe")]
15    NotLuks {
16        /// The first six bytes actually found.
17        found: [u8; 6],
18    },
19
20    /// The header version is neither 1 nor 2.
21    #[error("unsupported LUKS version {version} (only 1 and 2 are supported)")]
22    UnsupportedVersion {
23        /// The version field value.
24        version: u16,
25    },
26
27    /// The cipher/mode/hash combination has no validated decrypt path.
28    #[error("unsupported {what}: {value:?}")]
29    Unsupported {
30        /// Which axis is unsupported (cipher, mode, hash).
31        what: &'static str,
32        /// The offending value verbatim.
33        value: String,
34    },
35
36    /// The keyslot demands more key-derivation work than the wall-clock budget
37    /// allows. The iteration count comes from the header, so a crafted container
38    /// can ask for billions of rounds; refusing is the only way the tool stays
39    /// responsive on hostile input.
40    ///
41    /// A *refusal*, never a silent reduction: deriving with fewer rounds than the
42    /// header specifies produces a different key, which would report a wrong
43    /// passphrase for a container that would in fact have opened.
44    #[error(
45        "key derivation would take about {projected_secs}s for {iterations} iterations, \
46         over the {budget_secs}s budget — refusing rather than clamping, because a \
47         reduced iteration count derives a different key"
48    )]
49    DerivationBudgetExceeded {
50        /// The iteration count the header asked for, verbatim.
51        iterations: u32,
52        /// Projected cost on this machine, measured from a calibration run.
53        projected_secs: u64,
54        /// The budget that was exceeded.
55        budget_secs: u64,
56    },
57
58    /// The keyslot's anti-forensic stripe count is not plausible.
59    ///
60    /// `stripes` is a header field, so the container chooses it. It multiplies
61    /// the key size to give the key-material length the caller then allocates,
62    /// and it is the trip count of the AF merge loop — so an unbounded value is
63    /// both an allocation and a spin, on the container's say-so.
64    ///
65    /// LUKS1 fixes the count at 4000 and cryptsetup writes the same for LUKS2,
66    /// so the ceiling sits far above anything genuine.
67    #[error(
68        "keyslot declares {stripes} anti-forensic stripes, over the {max} ceiling \
69         — refusing before sizing a {} byte buffer",
70        .stripes.saturating_mul(*.block_size)
71    )]
72    ImplausibleStripes {
73        /// The stripe count the header asked for, verbatim.
74        stripes: usize,
75        /// The ceiling that was exceeded.
76        max: usize,
77        /// The key size the count would have been multiplied by.
78        block_size: usize,
79    },
80
81    /// The Argon2 keyslot demands more memory than the ceiling allows.
82    ///
83    /// Separate from [`Self::DerivationBudgetExceeded`] because memory cannot be
84    /// bounded the same way: a time cost can be measured and projected, but
85    /// *attempting* an oversized allocation is itself the harm — the process is
86    /// killed by the OS before any deadline could fire. `argon2::Params` caps
87    /// `m_cost` at `u32::MAX` 1 KiB blocks (4 TiB) and the header chooses the
88    /// value, so the ceiling has to be ours.
89    #[error(
90        "argon2 keyslot asks for {requested_kib} KiB of memory, over the \
91         {max_kib} KiB ceiling — refusing before allocating"
92    )]
93    DerivationMemoryExceeded {
94        /// The memory cost the header asked for, verbatim, in KiB blocks.
95        requested_kib: u32,
96        /// The ceiling that was exceeded, in KiB blocks.
97        max_kib: u32,
98    },
99
100    /// The header is structurally malformed (a field runs past the buffer).
101    #[error("malformed LUKS header: {what} (need {need} bytes, have {got})")]
102    MalformedHeader {
103        /// What was being read.
104        what: &'static str,
105        /// Bytes needed.
106        need: usize,
107        /// Bytes available.
108        got: usize,
109    },
110
111    /// No keyslot could be unlocked with the supplied passphrase, or the derived
112    /// master key failed the mk-digest check (wrong passphrase).
113    #[error("authentication failed: no keyslot matched the passphrase")]
114    AuthenticationFailed,
115
116    /// The container carries no active (enabled) keyslot.
117    #[error("no active keyslot in the LUKS header")]
118    NoActiveKeyslot,
119
120    /// An I/O error reading the container.
121    #[error("I/O error: {0}")]
122    Io(#[from] io::Error),
123}