From 16f77d5a594b199e023f633b432006274429991c Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Thu, 24 Nov 2022 20:28:13 -0500
Subject: [PATCH] update: sanity check p2pool/xmrig path from user before
 starting

Define a strict list [&str; 4] of valid path endings for p2pool/xmrig.
This prevents users (for some reason) inputting a path to some
other (maybe very important) file which Gupax would have completely
overridden with the update binary. Windows paths end with [.exe].
---
 src/gupax.rs  |  13 +++---
 src/main.rs   |   8 ++--
 src/update.rs | 108 ++++++++++++++++++++++++++++++++++++++++----------
 3 files changed, 100 insertions(+), 29 deletions(-)

diff --git a/src/gupax.rs b/src/gupax.rs
index 3c99008..ddfd5c7 100644
--- a/src/gupax.rs
+++ b/src/gupax.rs
@@ -23,9 +23,12 @@ use egui::{
 	RichText,
 	Vec2,
 };
-use crate::constants::*;
-use crate::disk::{Gupax,Version};
-use crate::update::*;
+use crate::{
+	constants::*,
+	disk::{Gupax,Version},
+	update::*,
+	ErrorState,ErrorFerris,ErrorButtons,
+};
 use std::{
 	thread,
 	sync::{Arc,Mutex},
@@ -75,7 +78,7 @@ pub enum Ratio {
 
 //---------------------------------------------------------------------------------------------------- Gupax
 impl Gupax {
-	pub fn show(&mut self, og: &Arc<Mutex<State>>, state_ver: &Arc<Mutex<Version>>, update: &Arc<Mutex<Update>>, file_window: &Arc<Mutex<FileWindow>>, state_path: &Path, width: f32, height: f32, frame: &mut eframe::Frame, ctx: &egui::Context, ui: &mut egui::Ui) {
+	pub fn show(&mut self, og: &Arc<Mutex<State>>, state_path: &Path, update: &Arc<Mutex<Update>>, file_window: &Arc<Mutex<FileWindow>>, error_state: &mut ErrorState, width: f32, height: f32, frame: &mut eframe::Frame, ctx: &egui::Context, ui: &mut egui::Ui) {
 		// Update button + Progress bar
 		ui.group(|ui| {
 				// These are in unnecessary [ui.vertical()]'s
@@ -88,7 +91,7 @@ impl Gupax {
 				ui.vertical(|ui| {
 					ui.set_enabled(!updating);
 					if ui.add_sized([width, height], Button::new("Check for updates")).on_hover_text(GUPAX_UPDATE).clicked() {
-						Update::spawn_thread(og, update, state_ver, state_path);
+						Update::spawn_thread(og, &self, state_path, update, error_state);
 					}
 				});
 				ui.vertical(|ui| {
diff --git a/src/main.rs b/src/main.rs
index aba5b10..3f1a844 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -479,7 +479,7 @@ fn init_options(initial_window_size: Option<Vec2>) -> NativeOptions {
 	options
 }
 
-fn init_auto(app: &App) {
+fn init_auto(app: &mut App) {
 	// Return early if [--no-startup] was not passed
 	if app.no_startup {
 		info!("[--no-startup] flag passed, skipping init_auto()...");
@@ -493,7 +493,7 @@ fn init_auto(app: &App) {
 
 	// [Auto-Update]
 	if app.state.gupax.auto_update {
-		Update::spawn_thread(&app.og, &app.update, &app.state.version, &app.state_path);
+		Update::spawn_thread(&app.og, &app.state.gupax, &app.state_path, &app.update, &mut app.error_state);
 	} else {
 		info!("Skipping auto-update...");
 	}
@@ -636,7 +636,7 @@ fn main() {
 	init_logger(now);
 	let mut app = App::new();
 	app.now = now;
-	init_auto(&app);
+	init_auto(&mut app);
 	let initial_window_size = match app.state.gupax.simple {
 		true  => Some(Vec2::new(app.state.gupax.selected_width as f32, app.state.gupax.selected_height as f32)),
 		false => Some(Vec2::new(APP_DEFAULT_WIDTH, APP_DEFAULT_HEIGHT)),
@@ -1001,7 +1001,7 @@ impl eframe::App for App {
 					Status::show(self, self.width, self.height, ctx, ui);
 				}
 				Tab::Gupax => {
-					Gupax::show(&mut self.state.gupax, &self.og, &self.state.version, &self.update, &self.file_window, &self.state_path, self.width, self.height, frame, ctx, ui);
+					Gupax::show(&mut self.state.gupax, &self.og, &self.state_path, &self.update, &self.file_window, &mut self.error_state, self.width, self.height, frame, ctx, ui);
 				}
 				Tab::P2pool => {
 					P2pool::show(&mut self.state.p2pool, &mut self.node_vec, &self.og, self.p2pool, &self.ping, &self.regex, self.width, self.height, ctx, ui);
diff --git a/src/update.rs b/src/update.rs
index bcac491..033923b 100644
--- a/src/update.rs
+++ b/src/update.rs
@@ -27,10 +27,12 @@
 use anyhow::{anyhow,Error};
 use arti_client::{TorClient};
 use arti_hyper::*;
-use crate::constants::GUPAX_VERSION;
-//use crate::{Name::*,State};
-use crate::disk::*;
-use crate::update::Name::*;
+use crate::{
+	constants::GUPAX_VERSION,
+	disk::*,
+	update::Name::*,
+	ErrorState,ErrorFerris,ErrorButtons,
+};
 use hyper::{
 	Client,Body,Request,
 	header::{HeaderValue,LOCATION},
@@ -81,18 +83,18 @@ const P2POOL_HASH: &str = "sha256sums.txt.asc";
 const XMRIG_HASH: &str = "SHA256SUMS";
 
 #[cfg(target_os = "windows")]
-const GUPAX_EXTENSION: &'static str = "-windows-x64-standalone.zip";
+const GUPAX_EXTENSION: &str = "-windows-x64-standalone.zip";
 #[cfg(target_os = "windows")]
-const P2POOL_EXTENSION: &'static str = "-windows-x64.zip";
+const P2POOL_EXTENSION: &str = "-windows-x64.zip";
 #[cfg(target_os = "windows")]
-const XMRIG_EXTENSION: &'static str = "-msvc-win64.zip";
+const XMRIG_EXTENSION: &str = "-msvc-win64.zip";
 
 #[cfg(target_os = "macos")]
-const GUPAX_EXTENSION: &'static str = "-macos-x64-standalone.tar.gz";
+const GUPAX_EXTENSION: &str = "-macos-x64-standalone.tar.gz";
 #[cfg(target_os = "macos")]
-const P2POOL_EXTENSION: &'static str = "-macos-x64.tar.gz";
+const P2POOL_EXTENSION: &str = "-macos-x64.tar.gz";
 #[cfg(target_os = "macos")]
-const XMRIG_EXTENSION: &'static str = "-macos-x64.tar.gz";
+const XMRIG_EXTENSION: &str = "-macos-x64.tar.gz";
 
 #[cfg(target_os = "linux")]
 const GUPAX_EXTENSION: &str = "-linux-x64-standalone.tar.gz";
@@ -102,22 +104,32 @@ const P2POOL_EXTENSION: &str = "-linux-x64.tar.gz";
 const XMRIG_EXTENSION: &str = "-linux-static-x64.tar.gz";
 
 #[cfg(target_os = "windows")]
-const GUPAX_BINARY: &'static str = "Gupax.exe";
+const GUPAX_BINARY: &str = "Gupax.exe";
 #[cfg(target_os = "macos")]
-const GUPAX_BINARY: &'static str = "Gupax";
+const GUPAX_BINARY: &str = "Gupax";
 #[cfg(target_os = "linux")]
 const GUPAX_BINARY: &str = "gupax";
 
 #[cfg(target_os = "windows")]
-const P2POOL_BINARY: &'static str = "p2pool.exe";
+const P2POOL_BINARY: &str = "p2pool.exe";
 #[cfg(target_family = "unix")]
 const P2POOL_BINARY: &str = "p2pool";
 
 #[cfg(target_os = "windows")]
-const XMRIG_BINARY: &'static str = "xmrig.exe";
+const XMRIG_BINARY: &str = "xmrig.exe";
 #[cfg(target_family = "unix")]
 const XMRIG_BINARY: &str = "xmrig";
 
+#[cfg(target_os = "windows")]
+const ACCEPTABLE_XMRIG: [&str; 4] = ["XMRIG.exe", "XMRig.exe", "Xmrig.exe", "xmrig.exe"];
+#[cfg(target_family = "unix")]
+const ACCEPTABLE_XMRIG: [&str; 4] = ["XMRIG", "XMRig", "Xmrig", "xmrig"];
+
+#[cfg(target_os = "windows")]
+const ACCEPTABLE_P2POOL: [&str; 4] = ["P2POOL.exe", "P2Pool.exe", "P2pool.exe", "p2pool.exe"];
+#[cfg(target_family = "unix")]
+const ACCEPTABLE_P2POOL: [&str; 4] = ["P2POOL", "P2Pool", "P2pool", "p2pool"];
+
 // Some fake Curl/Wget user-agents because GitHub API requires one and a Tor browser
 // user-agent might be fingerprintable without all the associated headers.
 const FAKE_USER_AGENT: [&str; 50] = [
@@ -253,14 +265,70 @@ impl Update {
 	// actually contains the code. This is so that everytime
 	// an update needs to happen (Gupax tab, auto-update), the
 	// code only needs to be edited once, here.
-	pub fn spawn_thread(og: &Arc<Mutex<State>>, update: &Arc<Mutex<Update>>, state_ver: &Arc<Mutex<Version>>, state_path: &Path) {
-		update.lock().unwrap().path_p2pool = og.lock().unwrap().gupax.absolute_p2pool_path.display().to_string();
-		update.lock().unwrap().path_xmrig = og.lock().unwrap().gupax.absolute_xmrig_path.display().to_string();
-		update.lock().unwrap().tor = og.lock().unwrap().gupax.update_via_tor;
+	pub fn spawn_thread(og: &Arc<Mutex<State>>, gupax: &crate::disk::Gupax, state_path: &Path, update: &Arc<Mutex<Update>>, error_state: &mut ErrorState) {
+		// Check P2Pool path for safety
+		// Attempt relative to absolute path
+		let p2pool_path = match into_absolute_path(gupax.p2pool_path.clone()) {
+			Ok(p) => p,
+			Err(e) => { error_state.set("Provided P2Pool path could not be turned into an absolute path", ErrorFerris::Error, ErrorButtons::Okay); return; },
+		};
+		// Attempt to get basename
+		let file = match p2pool_path.file_name() {
+			Some(p) => {
+				// Attempt to turn into str
+				match p.to_str() {
+					Some(p) => p,
+					None => { error_state.set("Provided P2Pool path could not be turned into a UTF-8 string (are you using non-English characters?)", ErrorFerris::Error, ErrorButtons::Okay); return; },
+				}
+			},
+			None => { error_state.set("Provided P2Pool path could not be found", ErrorFerris::Error, ErrorButtons::Okay); return; },
+		};
+		// If it doesn't look like [P2Pool], its probably a bad move
+		// to overwrite it with an update, so set an error.
+		// Doesnt seem like you can [match] on array indexes
+		// so that explains the ridiculous if/else.
+		if file == ACCEPTABLE_P2POOL[0] || file == ACCEPTABLE_P2POOL[1] || file == ACCEPTABLE_P2POOL[2] || file == ACCEPTABLE_P2POOL[3] {
+			info!("Update | Using P2Pool path: [{}]", p2pool_path.display());
+		} else {
+			warn!("Update | Aborting update, incorrect P2Pool path: [{}]", file);
+			let text = format!("Provided P2Pool path seems incorrect. Not starting update for safety.\nTry one of these: {:?}", ACCEPTABLE_P2POOL);
+			error_state.set(text, ErrorFerris::Error, ErrorButtons::Okay);
+			return;
+		}
+
+		// Check XMRig path for safety
+		let xmrig_path = match into_absolute_path(gupax.xmrig_path.clone()) {
+			Ok(p) => p,
+			Err(e) => { error_state.set("Provided XMRig path could not be turned into an absolute path", ErrorFerris::Error, ErrorButtons::Okay); return; },
+		};
+		let file = match xmrig_path.file_name() {
+			Some(p) => {
+				// Attempt to turn into str
+				match p.to_str() {
+					Some(p) => p,
+					None => { error_state.set("Provided XMRig path could not be turned into a UTF-8 string (are you using non-English characters?)", ErrorFerris::Error, ErrorButtons::Okay); return; },
+				}
+			},
+			None => { error_state.set("Provided XMRig path could not be found", ErrorFerris::Error, ErrorButtons::Okay); return; },
+		};
+		if file == ACCEPTABLE_XMRIG[0] || file == ACCEPTABLE_XMRIG[1] || file == ACCEPTABLE_XMRIG[2] || file == ACCEPTABLE_XMRIG[3] {
+			info!("Update | Using XMRig path: [{}]", xmrig_path.display());
+		} else {
+			warn!("Update | Aborting update, incorrect XMRig path: [{}]", file);
+			let text = format!("Provided XMRig path seems incorrect. Not starting update for safety.\nTry one of these: {:?}", ACCEPTABLE_XMRIG);
+			error_state.set(text, ErrorFerris::Error, ErrorButtons::Okay);
+			return;
+		}
+
+		update.lock().unwrap().path_p2pool = p2pool_path.display().to_string();
+		update.lock().unwrap().path_xmrig = xmrig_path.display().to_string();
+		update.lock().unwrap().tor = gupax.update_via_tor;
+
+		// Clone before thread spawn
 		let og = Arc::clone(og);
-		let state_ver = Arc::clone(state_ver);
-		let update = Arc::clone(update);
+		let state_ver = Arc::clone(&og.lock().unwrap().version);
 		let state_path = state_path.to_path_buf();
+		let update = Arc::clone(update);
 		std::thread::spawn(move|| {
 			info!("Spawning update thread...");
 			match Update::start(update.clone(), og.clone(), state_ver.clone()) {