Skip to content

Commit fc40d45

Browse files
authored
Merge pull request fedora-iot#181 from say-paul/add-fallback
add fallback to greenboot
2 parents 6e0d615 + cbefa74 commit fc40d45

6 files changed

Lines changed: 444 additions & 114 deletions

File tree

src/lib/grub.rs

Lines changed: 206 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -82,54 +82,91 @@ fn unset_boot_counter_at(grub_path: &str) -> Result<()> {
8282
unset_grub_var("boot_counter", grub_path)
8383
}
8484

85-
/// sets greenboot_rollback_trigger=1
86-
pub fn set_rollback_trigger() -> Result<()> {
87-
set_rollback_trigger_at(GRUB_PATH)
85+
/// sets fallback=1 for GRUB-level kernel fallback protection
86+
pub fn set_fallback() -> Result<()> {
87+
set_fallback_at(GRUB_PATH)
8888
}
8989

90-
fn set_rollback_trigger_at(grub_path: &str) -> Result<()> {
91-
set_grub_var("greenboot_rollback_trigger", 1, grub_path)
90+
fn set_fallback_at(grub_path: &str) -> Result<()> {
91+
set_grub_var("fallback", 1, grub_path)
9292
}
9393

94-
/// unsets greenboot_rollback_trigger
95-
pub fn unset_rollback_trigger() -> Result<()> {
96-
unset_rollback_trigger_at(GRUB_PATH)
94+
/// unsets the fallback grub variable
95+
pub fn unset_fallback() -> Result<()> {
96+
unset_fallback_at(GRUB_PATH)
9797
}
9898

99-
fn unset_rollback_trigger_at(grub_path: &str) -> Result<()> {
100-
unset_grub_var("greenboot_rollback_trigger", grub_path)
99+
fn unset_fallback_at(grub_path: &str) -> Result<()> {
100+
unset_grub_var("fallback", grub_path)
101101
}
102102

103-
/// gets greenboot_rollback_trigger value, returns true if set to 1
104-
pub fn get_rollback_trigger() -> Result<bool> {
105-
get_rollback_trigger_at(GRUB_PATH)
103+
/// gets fallback value, returns true if set to 1
104+
pub fn get_fallback() -> Result<bool> {
105+
get_fallback_at(GRUB_PATH)
106106
}
107107

108-
fn get_rollback_trigger_at(grub_path: &str) -> Result<bool> {
108+
fn get_fallback_at(grub_path: &str) -> Result<bool> {
109+
get_grub_bool_var("fallback", grub_path)
110+
}
111+
112+
/// sets greenboot_next_deployment_id to the given deployment ID string
113+
pub fn set_next_deployment_id(id: &str) -> Result<()> {
114+
set_next_deployment_id_at(id, GRUB_PATH)
115+
}
116+
117+
fn set_next_deployment_id_at(id: &str, grub_path: &str) -> Result<()> {
118+
set_grub_str_var("greenboot_next_deployment_id", id, grub_path)
119+
}
120+
121+
/// unsets greenboot_next_deployment_id
122+
pub fn unset_next_deployment_id() -> Result<()> {
123+
unset_next_deployment_id_at(GRUB_PATH)
124+
}
125+
126+
fn unset_next_deployment_id_at(grub_path: &str) -> Result<()> {
127+
unset_grub_var("greenboot_next_deployment_id", grub_path)
128+
}
129+
130+
/// returns the stored deployment ID, or None if not set
131+
pub fn get_next_deployment_id() -> Result<Option<String>> {
132+
get_next_deployment_id_at(GRUB_PATH)
133+
}
134+
135+
fn get_next_deployment_id_at(grub_path: &str) -> Result<Option<String>> {
136+
get_grub_str_var("greenboot_next_deployment_id", grub_path)
137+
}
138+
139+
fn get_grub_bool_var(key: &str, grub_path: &str) -> Result<bool> {
109140
let grub_vars = Command::new("grub2-editenv")
110141
.arg(grub_path)
111142
.arg("list")
112143
.output()
113-
.context("Unable to list grubenv variables")?;
144+
.context(format!("Unable to list grubenv variables for key: {key}"))?;
145+
146+
if !grub_vars.status.success() {
147+
bail!(
148+
"grub2-editenv failed to list variables: {}",
149+
String::from_utf8_lossy(&grub_vars.stderr)
150+
);
151+
}
114152

153+
let prefix = format!("{key}=");
115154
let output = String::from_utf8_lossy(&grub_vars.stdout);
116155
for line in output.lines() {
117-
if line.starts_with("greenboot_rollback_trigger=") {
118-
let value = line.split('=').nth(1).unwrap_or("0");
156+
if let Some(value) = line.strip_prefix(&prefix) {
119157
return Ok(value == "1");
120158
}
121159
}
122-
Ok(false) // Not set means false
160+
Ok(false)
123161
}
124162

125163
fn unset_grub_var(key: &str, grub_path: &str) -> Result<()> {
126-
// Execute GRUB command and capture result
127164
let grub_result = Command::new("grub2-editenv")
128165
.arg(grub_path)
129166
.arg("unset")
130167
.arg(key)
131168
.status()
132-
.context("Unable to clear boot_counter")?;
169+
.context(format!("Unable to unset grubenv key: {key}"))?;
133170

134171
if !grub_result.success() {
135172
bail!("Failed to unset grubenv key: {key}");
@@ -156,11 +193,56 @@ fn set_grub_var(key: &str, val: u16, grub_path: &str) -> Result<()> {
156193
Ok(())
157194
}
158195

196+
fn set_grub_str_var(key: &str, val: &str, grub_path: &str) -> Result<()> {
197+
let grub_result = Command::new("grub2-editenv")
198+
.arg(grub_path)
199+
.arg("set")
200+
.arg(format!("{key}={val}"))
201+
.status()
202+
.context("Unable to set grubenv")?;
203+
204+
if !grub_result.success() {
205+
bail!("Failed to set grubenv key: {key}");
206+
}
207+
208+
log::info!("Set grubenv: {key}={val}");
209+
Ok(())
210+
}
211+
212+
fn get_grub_str_var(key: &str, grub_path: &str) -> Result<Option<String>> {
213+
let grub_vars = Command::new("grub2-editenv")
214+
.arg(grub_path)
215+
.arg("list")
216+
.output()
217+
.context(format!("Unable to list grubenv variables for key: {key}"))?;
218+
219+
if !grub_vars.status.success() {
220+
bail!(
221+
"grub2-editenv failed to list variables: {}",
222+
String::from_utf8_lossy(&grub_vars.stderr)
223+
);
224+
}
225+
226+
let prefix = format!("{key}=");
227+
let output = String::from_utf8_lossy(&grub_vars.stdout);
228+
for line in output.lines() {
229+
if let Some(value) = line.strip_prefix(&prefix) {
230+
let trimmed = value.trim();
231+
if trimmed.is_empty() {
232+
return Ok(None);
233+
}
234+
return Ok(Some(trimmed.to_string()));
235+
}
236+
}
237+
Ok(None)
238+
}
239+
159240
#[cfg(test)]
160241
mod tests {
161242
use super::{
162-
get_boot_counter_at, get_rollback_trigger_at, set_boot_counter_at, set_rollback_trigger_at,
163-
unset_boot_counter_at, unset_rollback_trigger_at,
243+
get_boot_counter_at, get_fallback_at, get_next_deployment_id_at, set_boot_counter_at,
244+
set_fallback_at, set_next_deployment_id_at, unset_boot_counter_at, unset_fallback_at,
245+
unset_next_deployment_id_at,
164246
};
165247
use anyhow::Context;
166248
use std::fs;
@@ -234,38 +316,122 @@ mod tests {
234316
}
235317

236318
#[test]
237-
fn test_rollback_trigger_functions() {
319+
fn test_next_deployment_id_set_and_get() {
238320
let (_temp_dir, grubenv) = setup_test_paths();
239321

240-
// Test when rollback trigger is not set
241-
assert!(!get_rollback_trigger_at(&grubenv).unwrap());
322+
assert_eq!(get_next_deployment_id_at(&grubenv).unwrap(), None);
323+
324+
let id = "sha256:abc123def456";
325+
set_next_deployment_id_at(id, &grubenv).unwrap();
326+
assert_eq!(
327+
get_next_deployment_id_at(&grubenv).unwrap(),
328+
Some(id.to_string())
329+
);
330+
}
331+
332+
#[test]
333+
fn test_next_deployment_id_unset() {
334+
let (_temp_dir, grubenv) = setup_test_paths();
242335

243-
// Test setting rollback trigger
244-
set_rollback_trigger_at(&grubenv).unwrap();
245-
assert!(get_rollback_trigger_at(&grubenv).unwrap());
336+
let id = "sha256:abc123def456";
337+
set_next_deployment_id_at(id, &grubenv).unwrap();
338+
assert!(get_next_deployment_id_at(&grubenv).unwrap().is_some());
246339

247-
// Test unsetting rollback trigger
248-
unset_rollback_trigger_at(&grubenv).unwrap();
249-
assert!(!get_rollback_trigger_at(&grubenv).unwrap());
340+
unset_next_deployment_id_at(&grubenv).unwrap();
341+
assert_eq!(get_next_deployment_id_at(&grubenv).unwrap(), None);
250342
}
251343

252344
#[test]
253-
fn test_rollback_trigger_with_other_vars() {
345+
fn test_next_deployment_id_coexists_with_boot_counter() {
254346
let (_temp_dir, grubenv) = setup_test_paths();
255347

256-
// Set boot counter
348+
let id = "sha256:abc123def456";
257349
set_boot_counter_at(3, &grubenv).unwrap();
350+
set_next_deployment_id_at(id, &grubenv).unwrap();
258351

259-
// Set rollback trigger
260-
set_rollback_trigger_at(&grubenv).unwrap();
352+
assert_eq!(get_boot_counter_at(&grubenv).unwrap(), Some(3));
353+
assert_eq!(
354+
get_next_deployment_id_at(&grubenv).unwrap(),
355+
Some(id.to_string())
356+
);
261357

262-
// Both should coexist
358+
unset_next_deployment_id_at(&grubenv).unwrap();
263359
assert_eq!(get_boot_counter_at(&grubenv).unwrap(), Some(3));
264-
assert!(get_rollback_trigger_at(&grubenv).unwrap());
360+
assert_eq!(get_next_deployment_id_at(&grubenv).unwrap(), None);
361+
}
362+
363+
#[test]
364+
fn test_next_deployment_id_coexists_with_fallback() {
365+
let (_temp_dir, grubenv) = setup_test_paths();
366+
367+
let id = "sha256:abc123def456";
368+
set_next_deployment_id_at(id, &grubenv).unwrap();
369+
set_fallback_at(&grubenv).unwrap();
370+
371+
assert_eq!(
372+
get_next_deployment_id_at(&grubenv).unwrap(),
373+
Some(id.to_string())
374+
);
375+
assert!(get_fallback_at(&grubenv).unwrap());
376+
377+
unset_next_deployment_id_at(&grubenv).unwrap();
378+
assert_eq!(get_next_deployment_id_at(&grubenv).unwrap(), None);
379+
assert!(get_fallback_at(&grubenv).unwrap());
380+
}
381+
382+
#[test]
383+
fn test_fallback_set_and_get() {
384+
let (_temp_dir, grubenv) = setup_test_paths();
385+
386+
assert!(!get_fallback_at(&grubenv).unwrap());
387+
388+
set_fallback_at(&grubenv).unwrap();
389+
assert!(get_fallback_at(&grubenv).unwrap());
390+
}
391+
392+
#[test]
393+
fn test_fallback_unset() {
394+
let (_temp_dir, grubenv) = setup_test_paths();
395+
396+
set_fallback_at(&grubenv).unwrap();
397+
assert!(get_fallback_at(&grubenv).unwrap());
398+
399+
unset_fallback_at(&grubenv).unwrap();
400+
assert!(!get_fallback_at(&grubenv).unwrap());
401+
}
402+
403+
#[test]
404+
fn test_fallback_unset_when_not_set() {
405+
let (_temp_dir, grubenv) = setup_test_paths();
406+
407+
unset_fallback_at(&grubenv).unwrap();
408+
assert!(!get_fallback_at(&grubenv).unwrap());
409+
}
410+
411+
#[test]
412+
fn test_fallback_coexists_with_boot_counter() {
413+
let (_temp_dir, grubenv) = setup_test_paths();
414+
415+
set_boot_counter_at(3, &grubenv).unwrap();
416+
set_fallback_at(&grubenv).unwrap();
265417

266-
// Unset rollback trigger, boot_counter should remain
267-
unset_rollback_trigger_at(&grubenv).unwrap();
268418
assert_eq!(get_boot_counter_at(&grubenv).unwrap(), Some(3));
269-
assert!(!get_rollback_trigger_at(&grubenv).unwrap());
419+
assert!(get_fallback_at(&grubenv).unwrap());
420+
}
421+
422+
#[test]
423+
fn test_fallback_independent_of_deployment_id() {
424+
let (_temp_dir, grubenv) = setup_test_paths();
425+
426+
let id = "sha256:abc123def456";
427+
set_next_deployment_id_at(id, &grubenv).unwrap();
428+
set_fallback_at(&grubenv).unwrap();
429+
430+
unset_next_deployment_id_at(&grubenv).unwrap();
431+
assert_eq!(get_next_deployment_id_at(&grubenv).unwrap(), None);
432+
assert!(get_fallback_at(&grubenv).unwrap());
433+
434+
unset_fallback_at(&grubenv).unwrap();
435+
assert!(!get_fallback_at(&grubenv).unwrap());
270436
}
271437
}

0 commit comments

Comments
 (0)