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    /// An unlock exhausted its TOTAL time budget across all keyslots.
44    ///
45    /// Distinct from [`Self::DerivationBudgetExceeded`], which bounds a single
46    /// derivation. LUKS permits 8 keyslots and the header decides how many are
47    /// active and what each costs, so a crafted header can stay under the
48    /// per-derivation budget on every slot while the sum runs for hours. Each
49    /// individual check passes; nothing owns the total. This variant is that
50    /// total.
51    #[error(
52        "unlock exceeded its total budget of {budget_secs}s after {slots_tried} keyslot(s); \
53         the header's cumulative key-derivation cost is not satisfiable"
54    )]
55    UnlockBudgetExceeded {
56        /// How many keyslots had been attempted when the budget ran out.
57        slots_tried: usize,
58        /// The total budget for the whole unlock.
59        budget_secs: u64,
60    },
61
62    /// passphrase for a container that would in fact have opened.
63    #[error(
64        "key derivation would take about {projected_secs}s for {iterations} iterations, \
65         over the {budget_secs}s budget — refusing rather than clamping, because a \
66         reduced iteration count derives a different key"
67    )]
68    DerivationBudgetExceeded {
69        /// The iteration count the header asked for, verbatim.
70        iterations: u32,
71        /// Projected cost on this machine, measured from a calibration run.
72        projected_secs: u64,
73        /// The budget that was exceeded.
74        budget_secs: u64,
75    },
76
77    /// The keyslot's anti-forensic stripe count is not plausible.
78    ///
79    /// `stripes` is a header field, so the container chooses it. It multiplies
80    /// the key size to give the key-material length the caller then allocates,
81    /// and it is the trip count of the AF merge loop — so an unbounded value is
82    /// both an allocation and a spin, on the container's say-so.
83    ///
84    /// LUKS1 fixes the count at 4000 and cryptsetup writes the same for LUKS2,
85    /// so the ceiling sits far above anything genuine.
86    #[error(
87        "keyslot declares {stripes} anti-forensic stripes, over the {max} ceiling \
88         — refusing before sizing a {} byte buffer",
89        .stripes.saturating_mul(*.block_size)
90    )]
91    ImplausibleStripes {
92        /// The stripe count the header asked for, verbatim.
93        stripes: usize,
94        /// The ceiling that was exceeded.
95        max: usize,
96        /// The key size the count would have been multiplied by.
97        block_size: usize,
98    },
99
100    /// The Argon2 keyslot demands more memory than the ceiling allows.
101    ///
102    /// Separate from [`Self::DerivationBudgetExceeded`] because memory cannot be
103    /// bounded the same way: a time cost can be measured and projected, but
104    /// *attempting* an oversized allocation is itself the harm — the process is
105    /// killed by the OS before any deadline could fire. `argon2::Params` caps
106    /// `m_cost` at `u32::MAX` 1 KiB blocks (4 TiB) and the header chooses the
107    /// value, so the ceiling has to be ours.
108    #[error(
109        "argon2 keyslot asks for {requested_kib} KiB of memory, over the \
110         {max_kib} KiB ceiling — refusing before allocating"
111    )]
112    DerivationMemoryExceeded {
113        /// The memory cost the header asked for, verbatim, in KiB blocks.
114        requested_kib: u32,
115        /// The ceiling that was exceeded, in KiB blocks.
116        max_kib: u32,
117    },
118
119    /// The header asks to derive a master key longer than the ceiling allows.
120    ///
121    /// A third cost axis, independent of iterations and of Argon2 memory, and
122    /// bounded for the same reason as the memory one: the derived-key length
123    /// multiplies the work of every PBKDF2 iteration (`ceil(dkLen / hLen)` HMAC
124    /// blocks each), and it sizes an allocation. Crucially it also sizes the
125    /// CALIBRATION probe used to project the iteration cost, so leaving it
126    /// unbounded makes the measurement itself the denial of service — the guard
127    /// has to run before anything is measured or allocated.
128    #[error(
129        "header asks to derive a {requested} byte master key, over the {max} \
130         byte ceiling — refusing before allocating or calibrating"
131    )]
132    KeyLengthExceeded {
133        /// The master-key length the header asked for, verbatim, in bytes.
134        requested: usize,
135        /// The ceiling that was exceeded, in bytes.
136        max: usize,
137    },
138
139    /// The header is structurally malformed (a field runs past the buffer).
140    #[error("malformed LUKS header: {what} (need {need} bytes, have {got})")]
141    MalformedHeader {
142        /// What was being read.
143        what: &'static str,
144        /// Bytes needed.
145        need: usize,
146        /// Bytes available.
147        got: usize,
148    },
149
150    /// No keyslot could be unlocked with the supplied passphrase, or the derived
151    /// master key failed the mk-digest check (wrong passphrase).
152    #[error("authentication failed: no keyslot matched the passphrase")]
153    AuthenticationFailed,
154
155    /// The container carries no active (enabled) keyslot.
156    #[error("no active keyslot in the LUKS header")]
157    NoActiveKeyslot,
158
159    /// An I/O error reading the container.
160    #[error("I/O error: {0}")]
161    Io(#[from] io::Error),
162}