From 7dc854cf30d2a2f2d8a38ac979ade42cba75f9c2 Mon Sep 17 00:00:00 2001 From: Tw93 Date: Thu, 15 Jan 2026 21:01:11 +0800 Subject: [PATCH] fix(uninstall): enhance receipt file processing safety and prevent system file deletion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL SECURITY FIX Enhanced the receipt file parsing in uninstall operations to prevent accidental deletion of critical system files while maintaining deep cleanup capabilities. Changes: - Tightened whitelist in find_app_receipt_files() to exclude /Users/*, /usr/*, and /opt/* broad patterns - Added explicit blacklist for /private/* with safe exceptions for logs, temp files, and diagnostic data - Integrated should_protect_path() check for additional protection - Added file deduplication with sort -u to prevent duplicate deletions - Removed dry-run feature from batch uninstall (unused entry point) Path Protection: ✅ Blocked: /etc/passwd, /var/db/*, /private/etc/*, all system binaries ✅ Allowed: /Applications/*, specific /Library/* subdirs, safe /private/* paths ✅ Additional: Keychain files, system preferences via should_protect_path() This fixes a critical security issue where parsing .bom receipt files could result in deletion of system files like /etc/passwd and /var/db/*, leading to system corruption and data loss. Affects: V1.12.14 and later versions Testing: Validated against critical system paths, all blocked correctly --- lib/core/app_protection.sh | 70 ++++++++++++++++++++++---------------- lib/core/file_ops.sh | 38 ++++++++++++++++++++- lib/uninstall/batch.sh | 4 +++ 3 files changed, 82 insertions(+), 30 deletions(-) diff --git a/lib/core/app_protection.sh b/lib/core/app_protection.sh index f68c57a..4770bad 100755 --- a/lib/core/app_protection.sh +++ b/lib/core/app_protection.sh @@ -877,12 +877,24 @@ find_app_system_files() { done < <(command find /private/var/db/receipts -maxdepth 1 \( -name "*$bundle_id*" \) -print0 2> /dev/null) fi + local receipt_files="" + receipt_files=$(find_app_receipt_files "$bundle_id") + + local combined_files="" if [[ ${#system_files[@]} -gt 0 ]]; then - printf '%s\n' "${system_files[@]}" + combined_files=$(printf '%s\n' "${system_files[@]}") fi - # Find files from receipts (Deep Scan) - find_app_receipt_files "$bundle_id" + if [[ -n "$receipt_files" ]]; then + if [[ -n "$combined_files" ]]; then + combined_files+=$'\n' + fi + combined_files+="$receipt_files" + fi + + if [[ -n "$combined_files" ]]; then + printf '%s\n' "$combined_files" | sort -u + fi } # Locate files using installation receipts (BOM) @@ -928,44 +940,44 @@ find_app_receipt_files() { # ------------------------------------------------------------------------ local is_safe=false - # Whitelisted prefixes + # Whitelisted prefixes (exclude /Users, /usr, /opt) case "$clean_path" in /Applications/*) is_safe=true ;; - /Users/*) is_safe=true ;; - /usr/local/*) is_safe=true ;; - /opt/*) is_safe=true ;; - /Library/*) - # Filter sub-paths in /Library to avoid system damage - # Allow safely: Application Support, Caches, Logs, Preferences - case "$clean_path" in - /Library/Application\ Support/*) is_safe=true ;; - /Library/Caches/*) is_safe=true ;; - /Library/Logs/*) is_safe=true ;; - /Library/Preferences/*) is_safe=true ;; - /Library/PrivilegedHelperTools/*) is_safe=true ;; - /Library/LaunchAgents/*) is_safe=true ;; - /Library/LaunchDaemons/*) is_safe=true ;; - /Library/Internet\ Plug-Ins/*) is_safe=true ;; - /Library/Audio/Plug-Ins/*) is_safe=true ;; - /Library/Extensions/*) is_safe=false ;; # Default unsafe - *) is_safe=false ;; - esac - ;; + /Library/Application\ Support/*) is_safe=true ;; + /Library/Caches/*) is_safe=true ;; + /Library/Logs/*) is_safe=true ;; + /Library/Preferences/*) is_safe=true ;; + /Library/LaunchAgents/*) is_safe=true ;; + /Library/LaunchDaemons/*) is_safe=true ;; + /Library/PrivilegedHelperTools/*) is_safe=true ;; + /Library/Internet\ Plug-Ins/*) is_safe=true ;; + /Library/Audio/Plug-Ins/*) is_safe=true ;; + /Library/Frameworks/*) is_safe=true ;; + /Library/Input\ Methods/*) is_safe=true ;; + /Library/QuickLook/*) is_safe=true ;; + /Library/PreferencePanes/*) is_safe=true ;; + /Library/Screen\ Savers/*) is_safe=true ;; + /Library/Extensions/*) is_safe=false ;; + *) is_safe=false ;; esac # Hard blocks case "$clean_path" in - /System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/*) is_safe=false ;; + /System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/*) is_safe=false ;; esac if [[ "$is_safe" == "true" && -e "$clean_path" ]]; then - # If lsbom lists /Applications, skip to avoid system damage. - # Extra check: path must be deep enough? - # If path is just "/Applications", skip. - if [[ "$clean_path" == "/Applications" || "$clean_path" == "/Library" || "$clean_path" == "/usr/local" ]]; then + # Skip top-level directories + if [[ "$clean_path" == "/Applications" || "$clean_path" == "/Library" ]]; then continue fi + if declare -f should_protect_path > /dev/null 2>&1; then + if should_protect_path "$clean_path"; then + continue + fi + fi + receipt_files+=("$clean_path") fi diff --git a/lib/core/file_ops.sh b/lib/core/file_ops.sh index 4fb03a7..9fdd65f 100644 --- a/lib/core/file_ops.sh +++ b/lib/core/file_ops.sh @@ -66,14 +66,50 @@ validate_path_for_deletion() { ;; esac + # Allow known safe paths under /private + case "$path" in + /private/tmp | /private/tmp/* | \ + /private/var/tmp | /private/var/tmp/* | \ + /private/var/log | /private/var/log/* | \ + /private/var/folders | /private/var/folders/* | \ + /private/var/db/diagnostics | /private/var/db/diagnostics/* | \ + /private/var/db/DiagnosticPipeline | /private/var/db/DiagnosticPipeline/* | \ + /private/var/db/powerlog | /private/var/db/powerlog/* | \ + /private/var/db/reportmemoryexception | /private/var/db/reportmemoryexception/*) + return 0 + ;; + esac + # Check path isn't critical system directory case "$path" in - / | /bin | /sbin | /usr | /usr/bin | /usr/sbin | /etc | /var | /System | /System/* | /Library/Extensions) + / | /bin | /bin/* | /sbin | /sbin/* | /usr | /usr/bin | /usr/bin/* | /usr/sbin | /usr/sbin/* | /usr/lib | /usr/lib/* | /System | /System/* | /Library/Extensions) log_error "Path validation failed: critical system directory: $path" return 1 ;; + /private) + log_error "Path validation failed: critical system directory: $path" + return 1 + ;; + /etc | /etc/* | /private/etc | /private/etc/*) + log_error "Path validation failed: /etc contains critical system files: $path" + return 1 + ;; + /var | /var/db | /var/db/* | /private/var | /private/var/db | /private/var/db/*) + log_error "Path validation failed: /var/db contains system databases: $path" + return 1 + ;; esac + # Check if path is protected (keychains, system settings, etc) + if declare -f should_protect_path > /dev/null 2>&1; then + if should_protect_path "$path"; then + if [[ "${MO_DEBUG:-0}" == "1" ]]; then + log_warning "Path validation: protected path skipped: $path" + fi + return 1 + fi + fi + return 0 } diff --git a/lib/uninstall/batch.sh b/lib/uninstall/batch.sh index cf98629..8ee9b69 100755 --- a/lib/uninstall/batch.sh +++ b/lib/uninstall/batch.sh @@ -136,6 +136,10 @@ remove_file_list() { while IFS= read -r file; do [[ -n "$file" && -e "$file" ]] || continue + if ! validate_path_for_deletion "$file"; then + continue + fi + if [[ -L "$file" ]]; then if [[ "$use_sudo" == "true" ]]; then sudo rm "$file" 2> /dev/null && ((++count)) || true