From 2a2aa74c09538b1b09d9273aa0c9eaea288ad2dd Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Fri, 24 Oct 2025 18:44:28 -0700 Subject: [PATCH] fix(context): add context finalization iteration limit This prevents any possibility of an infinite loop during finalization. --- src/actions.rs | 7 +++++-- src/context.rs | 20 ++++++++++++++++---- src/main.rs | 5 ++++- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/actions.rs b/src/actions.rs index 3e0c039..2893d9f 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -1,5 +1,5 @@ use crate::context::SproutContext; -use anyhow::{Result, bail}; +use anyhow::{Context, Result, bail}; use serde::{Deserialize, Serialize}; use std::rc::Rc; @@ -50,7 +50,10 @@ pub fn execute(context: Rc, name: impl AsRef) -> Result<()> bail!("unknown action '{}'", name.as_ref()); }; // Finalize the context and freeze it. - let context = context.finalize().freeze(); + let context = context + .finalize() + .context("unable to finalize context")? + .freeze(); // Execute the action. if let Some(chainload) = &action.chainload { diff --git a/src/context.rs b/src/context.rs index 8755e83..fbcbc80 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,11 +1,14 @@ use crate::actions::ActionDeclaration; use crate::options::SproutOptions; -use anyhow::Result; use anyhow::anyhow; +use anyhow::{Result, bail}; use std::collections::{BTreeMap, BTreeSet}; use std::rc::Rc; use uefi::proto::device_path::DevicePath; +/// The maximum number of iterations that can be performed in [SproutContext::finalize]. +const CONTEXT_FINALIZE_ITERATION_LIMIT: usize = 100; + /// Declares a root context for Sprout. /// This contains data that needs to be shared across Sprout. #[derive(Default)] @@ -151,11 +154,20 @@ impl SproutContext { /// Finalizes a context by producing a context with no parent that contains all the values /// of all parent contexts merged. This makes it possible to ensure [SproutContext] has no /// inheritance with other [SproutContext]s. It will still contain a [RootContext] however. - pub fn finalize(&self) -> SproutContext { + pub fn finalize(&self) -> Result { // Collect all the values from the context and its parents. let mut current_values = self.all_values(); + // To ensure that there is no possible infinite loop, we need to check + // the number of iterations. If it exceeds 100, we bail. + let mut iterations: usize = 0; loop { + iterations += 1; + + if iterations > CONTEXT_FINALIZE_ITERATION_LIMIT { + bail!("infinite loop detected in context finalization"); + } + let mut did_change = false; let mut values = BTreeMap::new(); for (key, value) in ¤t_values { @@ -176,11 +188,11 @@ impl SproutContext { } // Produce the final context. - Self { + Ok(Self { root: self.root.clone(), parent: None, values: current_values, - } + }) } /// Stamps the `text` value with the specified `values` map. The returned value indicates diff --git a/src/main.rs b/src/main.rs index f3b6474..f857ac5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -143,7 +143,10 @@ fn main() -> Result<()> { // Insert the values from the entry configuration into the // sprout context to use with the entry itself. context.insert(&entry.declaration().values); - let context = context.finalize().freeze(); + let context = context + .finalize() + .context("unable to finalize context")? + .freeze(); // Provide the new context to the bootable entry. entry.swap_context(context); // Restamp the title with any values.