Protect local/ overlay source trees from repo unfetch and fetch wipe

Add is_local_overlay() path guard in repo.rs that detects recipes symlinked into local/recipes/ and refuses to delete their source/ during unfetch unless REDBEAR_ALLOW_LOCAL_UNFETCH=1 is set. Add the same guard in fetch.rs to block source-dir wipe and git reset --hard for local overlays unless REDBEAR_ALLOW_PROTECTED_FETCH=1 is set. Expand redbear_protected_recipe() from 8 core recipes to all 95+ local overlay recipe names.
This commit is contained in:
2026-04-22 21:59:59 +01:00
parent 78e5d99fb8
commit 07c97ebaf8
2 changed files with 168 additions and 13 deletions
+29 -3
View File
@@ -22,7 +22,7 @@ use redox_installer::PackageConfig;
use std::borrow::Cow;
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::io::{Read, Write, stderr, stdin, stdout};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
@@ -38,6 +38,24 @@ use termion::{color, style};
// A repo manager, to replace repo.sh
/// Check if a recipe directory is a local Red Bear overlay (symlink into local/).
/// Local overlay recipes must never have their source/ deleted by unfetch/clean.
fn is_local_overlay(recipe_dir: &Path) -> bool {
if let Ok(resolved) = recipe_dir.canonicalize() {
let resolved_str = resolved.to_string_lossy();
return resolved_str.contains("/local/recipes/");
}
false
}
/// Check if the operator has explicitly allowed destructive operations on local overlays.
fn redbear_allow_local_unfetch() -> bool {
matches!(
std::env::var("REDBEAR_ALLOW_LOCAL_UNFETCH").ok().as_deref(),
Some("1" | "true" | "TRUE" | "yes" | "YES")
)
}
const REPO_HELP_STR: &str = r#"
Usage: repo <command> [flags] <recipe1> <recipe2> ...
@@ -772,8 +790,16 @@ fn handle_clean(
}
let dir = recipe.dir.join("source");
if dir.exists() && matches!(*command, CliCommand::Unfetch) {
fs::remove_dir_all(&dir).context(format!("failed to delete {}", dir.display()))?;
cached = false;
if is_local_overlay(&recipe.dir) && !redbear_allow_local_unfetch() {
eprintln!(
"[WARN] skipping unfetch for local overlay recipe {} \
(source lives in local/; set REDBEAR_ALLOW_LOCAL_UNFETCH=1 to override)",
recipe.name.name()
);
} else {
fs::remove_dir_all(&dir).context(format!("failed to delete {}", dir.display()))?;
cached = false;
}
}
Ok(cached)
}
+139 -10
View File
@@ -38,6 +38,7 @@ pub struct FetchResult {
fn redbear_protected_recipe(name: &str) -> bool {
matches!(
name,
// Core patched recipes (upstream + Red Bear patches)
"relibc"
| "bootloader"
| "kernel"
@@ -46,6 +47,107 @@ fn redbear_protected_recipe(name: &str) -> bool {
| "installer"
| "redoxfs"
| "grub"
// Red Bear custom core recipes
| "ext4d"
| "fatd"
// Red Bear driver infrastructure
| "redox-driver-sys"
| "linux-kpi"
| "firmware-loader"
| "redbear-btusb"
| "redbear-iwlwifi"
// Red Bear GPU stack
| "redox-drm"
| "amdgpu"
// Red Bear system tools
| "cub"
| "evdevd"
| "udev-shim"
| "iommu"
| "redbear-firmware"
| "redbear-hwutils"
| "redbear-info"
| "rbos-info"
| "redbear-meta"
| "redbear-netctl"
| "redbear-netctl-console"
| "redbear-netstat"
| "redbear-btctl"
| "redbear-wifictl"
| "redbear-traceroute"
| "redbear-mtr"
| "redbear-nmap"
| "redbear-sessiond"
| "redbear-authd"
| "redbear-session-launch"
| "redbear-greeter"
| "redbear-dbus-services"
| "redbear-notifications"
| "redbear-upower"
| "redbear-udisks"
| "redbear-polkit"
| "redbear-quirks"
// Red Bear branding
| "redbear-release"
// Red Bear library stubs and custom libs
| "libepoxy-stub"
| "libdisplay-info-stub"
| "lcms2-stub"
| "libxcvt-stub"
| "libudev-stub"
| "zbus"
| "libqrencode"
// Red Bear Wayland
| "qt6-wayland-smoke"
| "smallvil"
| "seatd-redox"
// Red Bear KDE (47 recipes)
| "kf6-extra-cmake-modules"
| "kf6-kcoreaddons"
| "kf6-kwidgetsaddons"
| "kf6-kconfig"
| "kf6-ki18n"
| "kf6-kcodecs"
| "kf6-kguiaddons"
| "kf6-kcolorscheme"
| "kf6-kauth"
| "kf6-kitemmodels"
| "kf6-kitemviews"
| "kf6-karchive"
| "kf6-kwindowsystem"
| "kf6-knotifications"
| "kf6-kjobwidgets"
| "kf6-kconfigwidgets"
| "kf6-kcrash"
| "kf6-kdbusaddons"
| "kf6-kglobalaccel"
| "kf6-kservice"
| "kf6-kpackage"
| "kf6-kiconthemes"
| "kf6-kxmlgui"
| "kf6-ktextwidgets"
| "kf6-solid"
| "kf6-sonnet"
| "kf6-kio"
| "kf6-kbookmarks"
| "kf6-kcompletion"
| "kf6-kdeclarative"
| "kf6-kcmutils"
| "kf6-kidletime"
| "kf6-kwayland"
| "kf6-knewstuff"
| "kf6-kwallet"
| "kf6-prison"
| "kf6-kirigami"
| "kdecoration"
| "kwin"
| "plasma-desktop"
| "plasma-workspace"
| "plasma-framework"
| "plasma-wayland-protocols"
| "kirigami"
// Orbutils (has local patch)
| "orbutils"
)
}
@@ -56,6 +158,15 @@ fn redbear_allow_protected_fetch() -> bool {
)
}
/// Check if a recipe directory is a local Red Bear overlay (symlink into local/).
fn is_local_overlay(recipe_dir: &Path) -> bool {
if let Ok(resolved) = recipe_dir.canonicalize() {
let resolved_str = resolved.to_string_lossy();
return resolved_str.contains("/local/recipes/");
}
false
}
impl FetchResult {
pub fn new(source_dir: PathBuf, ident: String, cached: bool) -> Self {
Self {
@@ -331,11 +442,20 @@ pub fn fetch(recipe: &CookRecipe, check_source: bool, logger: &PtyOut) -> Result
}
if !patches.is_empty() || script.is_some() {
// Hard reset
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("reset").arg("--hard");
run_command(command, logger)?;
if is_local_overlay(recipe_dir) && !redbear_allow_protected_fetch() {
log_to_pty!(
logger,
"[WARN] skipping git reset --hard for local overlay recipe at {} \
(set REDBEAR_ALLOW_PROTECTED_FETCH=1 to override)",
recipe_dir.display()
);
} else {
// Hard reset
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("reset").arg("--hard");
run_command(command, logger)?;
}
}
if let Some(rev) = rev {
@@ -443,11 +563,20 @@ pub fn fetch(recipe: &CookRecipe, check_source: bool, logger: &PtyOut) -> Result
let mut cached = true;
if source_dir.is_dir() {
if tar_updated || fetch_is_patches_newer(recipe_dir, patches, &source_dir)? {
log_to_pty!(
logger,
"DEBUG: source tar or patches is newer than the source directory"
);
remove_all(&source_dir)?
if is_local_overlay(recipe_dir) && !redbear_allow_protected_fetch() {
log_to_pty!(
logger,
"[WARN] refusing to wipe source for local overlay recipe at {} \
(set REDBEAR_ALLOW_PROTECTED_FETCH=1 to override)",
recipe_dir.display()
);
} else {
log_to_pty!(
logger,
"DEBUG: source tar or patches is newer than the source directory"
);
remove_all(&source_dir)?
}
}
}
if !source_dir.is_dir() {