Repository: pop-os/popsicle (language: rust)

Issue:
Run GUI as normal user
This places popsicle in the right context to allow them to browse for an ISO. Upon flashing, launch a command line executable with pkexec and use a pipe to read the status for each drive back into the GUI.


Discussion / hints:
Noticed since popsicle currently runs with elevated privileges, the "Open" dialog displays mounting options for `/` as well as `/boot/efi`. The name of an external drive may also be the technically defined name (perhaps the UUID) instead of the generic "8.0 GB Volume".  
Would it be possible to do this?

- Launch the application with pkexec
- Create a secondary thread which does all the work as root
- Have the main thread revert back to the original user and initialize GTK there.
Another thing to work on in this regard is a Wayland supported pkexec implementation. Popsicle currently  doesn't run on Wayland.
I think Wayland's issue is with applications that run UIs that interface with Wayland as root, so if we can separate the components that require root into a separate thread, and then revert permissions back to the user that called pkexec in the main thread before initializing GTK, then this _might_ make it work on Wayland.

I'm not certain of that though, so I'd have to try it out.

Fix patch:
diff --git a/gtk/src/app/mod.rs b/gtk/src/app/mod.rs
index 756b6d2..6ff4aa0 100644
--- a/gtk/src/app/mod.rs
+++ b/gtk/src/app/mod.rs
@@ -15,7 +15,12 @@ pub use self::state::{Connect, State};
 use std::path::PathBuf;
 use std::process;
 use std::sync::Arc;
-use std::sync::mpsc::Sender;
+use std::sync::mpsc::{Sender, Receiver};
+use std::fs::File;
+use std::thread::JoinHandle;
+
+use flash::FlashRequest;
+use popsicle::{DiskError, Mount};
 
 use gtk;
 use gtk::*;
@@ -30,7 +35,13 @@ pub struct App {
 }
 
 impl App {
-    pub fn new(sender: Sender<PathBuf>) -> App {
+    pub fn new(
+        sender: Sender<PathBuf>,
+        devices_request: Sender<(Vec<String>, Vec<Mount>)>,
+        devices_response: Receiver<Result<Vec<(String, File)>, DiskError>>,
+        flash_request: Sender<FlashRequest>,
+        flash_response: Receiver<JoinHandle<Result<(), DiskError>>>,
+    ) -> App {
         // Initialize GTK before proceeding.
         if gtk::init().is_err() {
             eprintln!("failed to initialize GTK Application");
@@ -74,7 +85,7 @@ impl App {
             window,
             header,
             content,
-            state: Arc::new(State::new(sender)),
+            state: Arc::new(State::new(sender, devices_request, devices_response, flash_request, flash_response)),
         }
     }
 }
diff --git a/gtk/src/app/state/flash_devices.rs b/gtk/src/app/state/flash_devices.rs
index 610084b..02f0756 100644
--- a/gtk/src/app/state/flash_devices.rs
+++ b/gtk/src/app/state/flash_devices.rs
@@ -2,9 +2,7 @@ use super::*;
 use gtk;
 use gtk::prelude::*;
 use std::sync::{Arc, Mutex};
-use popsicle::{self, DiskError};
-use std::sync::atomic::Ordering;
-use std::thread;
+use popsicle;
 use std::mem;
 use std::time::Instant;
 use std::path::Path;
@@ -77,7 +75,8 @@ pub fn flash_devices(
     let devs = device_list
         .iter()
         .filter(|x| x.1.get_active())
-        .map(|x| x.0.clone());
+        .map(|x| x.0.clone())
+        .collect::<Vec<_>>();
 
     let mounts = try_or_error!(
         popsicle::Mount::all(),
@@ -90,14 +89,36 @@ pub fn flash_devices(
         ()
     );
 
+    try_or_error!(
+        state.devices_request.send((devs, mounts.clone())),
+        state.view,
+        back,
+        next,
+        stack,
+        error,
+        "unable to send device request",
+        ()
+    );
+
+    let disks_result = try_or_error!(
+        state.devices_response.recv(),
+        state.view,
+        back,
+        next,
+        stack,
+        error,
+        "unable to get device request response",
+        ()
+    );
+
     let disks = try_or_error!(
-        popsicle::disks_from_args(devs, &mounts, true),
+        disks_result,
         state.view,
         back,
         next,
         stack,
         error,
-        "unable to get collect devices",
+        "unable to get devices",
         ()
     );
 
@@ -194,23 +215,16 @@ pub fn flash_devices(
         // The value will be stored within an intermediary atomic integer,
         // because it is unsafe to send GTK widgets across threads.
         task_handles.push({
-            let progress = progress.clone();
-            let finished = finished.clone();
-            thread::spawn(move || -> Result<(), DiskError> {
-                let result = popsicle::write_to_disk(
-                    |_msg| (),
-                    || (),
-                    |value| progress.store(value as usize, Ordering::SeqCst),
-                    disk,
-                    disk_path,
-                    image_data.len() as u64,
-                    &image_data,
-                    false,
-                );
-
-                finished.store(1, Ordering::SeqCst);
-                result
-            })
+            let _ = state.flash_request.send(FlashRequest::new(
+                disk,
+                disk_path,
+                image_data.len() as u64,
+                image_data,
+                progress.clone(),
+                finished.clone()
+            ));
+
+            state.flash_response.recv().expect("expected join handle to be returned")
         });
 
         tasks.push(FlashTask {
diff --git a/gtk/src/app/state/mod.rs b/gtk/src/app/state/mod.rs
index 7278e95..6dc6b79 100644
--- a/gtk/src/app/state/mod.rs
+++ b/gtk/src/app/state/mod.rs
@@ -4,18 +4,20 @@ mod flash_devices;
 mod try;
 
 use self::flash_devices::flash_devices;
+use flash::FlashRequest;
 
 use super::super::BlockDevice;
 use super::{App, OpenDialog};
 
 use hash::HashState;
 use image::{self, BufferingData};
+use std::fs::File;
 use std::cell::{Cell, RefCell};
 use std::mem;
 use std::ops::{Deref, DerefMut};
 use std::path::{Path, PathBuf};
 use std::sync::atomic::{AtomicUsize, Ordering};
-use std::sync::mpsc::Sender;
+use std::sync::mpsc::{Sender, Receiver};
 use std::sync::{Arc, Mutex};
 use std::thread;
 use std::thread::JoinHandle;
@@ -23,7 +25,7 @@ use std::time::{Duration, Instant};
 
 use gtk;
 use gtk::*;
-use popsicle::DiskError;
+use popsicle::{DiskError, Mount};
 
 /// Contains all of the state that needs to be shared across the program's lifetime.
 pub struct State {
@@ -48,11 +50,25 @@ pub struct State {
     pub tasks: Mutex<Vec<FlashTask>>,
     /// Stores an integer which defines the currently-active view.
     pub view: Cell<u8>,
+    /// Requests for a list of devices to be returned by an authenticated user (ie: root).
+    pub devices_request: Sender<(Vec<String>, Vec<Mount>)>,
+    /// The accompanying response that follows a device request.
+    pub devices_response: Receiver<Result<Vec<(String, File)>, DiskError>>,
+    /// Requests for a device to be flashed by an authenticated user (ie: root).
+    pub flash_request: Sender<FlashRequest>,
+    /// Contains the join handle to the thread where the task is being flashed.
+    pub flash_response: Receiver<JoinHandle<Result<(), DiskError>>>,
 }
 
 impl State {
     /// Initailizes a new structure for managing the state of the application.
-    pub fn new(image_sender: Sender<PathBuf>) -> State {
+    pub fn new(
+        image_sender: Sender<PathBuf>,
+        devices_request: Sender<(Vec<String>, Vec<Mount>)>,
+        devices_response: Receiver<Result<Vec<(String, File)>, DiskError>>,
+        flash_request: Sender<FlashRequest>,
+        flash_response: Receiver<JoinHandle<Result<(), DiskError>>>,
+    ) -> State {
         State {
             bars: RefCell::new(Vec::new()),
             devices: Mutex::new(Vec::new()),
@@ -64,6 +80,10 @@ impl State {
             buffer: Arc::new(BufferingData::new()),
             image_sender,
             image_length: Cell::new(0),
+            devices_request,
+            devices_response,
+            flash_request,
+            flash_response,
         }
     }
 }
@@ -74,9 +94,6 @@ pub struct FlashTask {
     finished: Arc<AtomicUsize>,
 }
 
-
-
-
 pub struct Connected(App);
 
 impl Connected {
diff --git a/gtk/src/flash.rs b/gtk/src/flash.rs
new file mode 100644
index 0000000..60ca6f3
--- /dev/null
+++ b/gtk/src/flash.rs
@@ -0,0 +1,57 @@
+use std::fs::File;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Arc;
+
+use popsicle::{self, DiskError};
+
+pub struct FlashRequest {
+    disk: File,
+    disk_path: String,
+    image_len: u64,
+    image_data: Arc<Vec<u8>>,
+    progress: Arc<AtomicUsize>,
+    finished: Arc<AtomicUsize>,
+}
+
+impl FlashRequest {
+    pub fn new(
+        disk: File,
+        disk_path: String,
+        image_len: u64,
+        image_data: Arc<Vec<u8>>,
+        progress: Arc<AtomicUsize>,
+        finished: Arc<AtomicUsize>,
+    ) -> FlashRequest {
+        FlashRequest {
+            disk,
+            disk_path,
+            image_len,
+            image_data,
+            progress,
+            finished,
+        }
+    }
+
+    pub fn write(self) -> Result<(), DiskError> {
+        let disk = self.disk;
+        let disk_path = self.disk_path;
+        let progress = self.progress;
+        let image_len = self.image_len;
+        let image_data = self.image_data;
+
+        let result = popsicle::write_to_disk(
+            |_msg| (),
+            || (),
+            |value| progress.store(value as usize, Ordering::SeqCst),
+            disk,
+            disk_path,
+            image_len,
+            &image_data,
+            false,
+        );
+
+        self.finished.store(1, Ordering::SeqCst);
+
+        result
+    }
+}
diff --git a/gtk/src/main.rs b/gtk/src/main.rs
index 809321d..738ffb9 100644
--- a/gtk/src/main.rs
+++ b/gtk/src/main.rs
@@ -1,6 +1,7 @@
 extern crate digest;
 extern crate gdk;
 extern crate gtk;
+extern crate libc;
 extern crate md5;
 extern crate pango;
 extern crate popsicle;
@@ -9,6 +10,7 @@ extern crate sha2;
 
 mod app;
 mod block;
+mod flash;
 mod hash;
 mod image;
 
@@ -17,21 +19,43 @@ use std::env;
 use std::path::PathBuf;
 use std::sync::mpsc::channel;
 use std::thread;
+use std::fs::File;
+use std::sync::mpsc::{Sender, Receiver};
+use std::thread::JoinHandle;
 
 pub use block::BlockDevice;
+pub use flash::FlashRequest;
+
+use popsicle::{DiskError, Mount};
 
 fn main() {
-    // If running in pkexec, restore home directory for open dialog
-    if let Ok(pkexec_uid) = env::var("PKEXEC_UID") {
+    let (devices_request, devices_request_receiver) =
+        channel::<(Vec<String>, Vec<Mount>)>();
+    let (devices_response_sender, devices_response) =
+        channel::<Result<Vec<(String, File)>, DiskError>>();
+    let (flash_request, flash_request_receiver) = channel::<FlashRequest>();
+    let (flash_response_sender, flash_response) = channel();
+
+    authenticated_threads(
+        devices_request_receiver,
+        devices_response_sender,
+        flash_request_receiver,
+        flash_response_sender
+    );
+
+    // If running in pkexec or sudo, restore home directory for open dialog,
+    // and then downgrade permissions back to a regular user.
+    if let Ok(pkexec_uid) = env::var("PKEXEC_UID").or(env::var("SUDO_UID")) {
         if let Ok(uid) = pkexec_uid.parse::<u32>() {
             if let Some(passwd) = pwd::Passwd::from_uid(uid) {
                 env::set_var("HOME", passwd.dir);
+                downgrade_permissions(passwd.uid, passwd.gid);
             }
         }
     }
 
     let (sender, receiver) = channel::<PathBuf>();
-    let app = App::new(sender);
+    let app = App::new(sender, devices_request, devices_response, flash_request, flash_response);
 
     {
         let buffer = app.state.buffer.clone();
@@ -48,3 +72,37 @@ fn main() {
 
     app.connect_events().then_execute()
 }
+
+/// Actions which require root authentication will be spawned as background threads from here.
+///
+/// This function should be called before `downgrade_permissions()`.
+fn authenticated_threads(
+    devices_request: Receiver<(Vec<String>, Vec<Mount>)>,
+    devices_response: Sender<Result<Vec<(String, File)>, DiskError>>,
+    flash_request: Receiver<FlashRequest>,
+    flash_response: Sender<JoinHandle<Result<(), DiskError>>>
+) {
+    thread::spawn(move || {
+        while let Ok((devs, mounts)) = devices_request.recv() {
+            let resp = popsicle::disks_from_args(devs.into_iter(), &mounts, true);
+            let _ = devices_response.send(resp);
+        }
+    });
+
+    thread::spawn(move || {
+        while let Ok(flash_request) = flash_request.recv() {
+            let handle = thread::spawn(move || flash_request.write());
+            let _ = flash_response.send(handle);
+        }
+    });
+}
+
+/// Downgrades the permissions of the current thread to the specified user and group ID.
+fn downgrade_permissions(uid: u32, gid: u32) {
+    unsafe {
+        // By using system calls directly, we apply this on a per-thread basis.
+        // The setresuid() and setresguid() functions apply to all threads.
+        libc::syscall(libc::SYS_setresgid, gid, gid, gid);
+        libc::syscall(libc::SYS_setresuid, uid, uid, uid);
+    }
+}
diff --git a/src/mount.rs b/src/mount.rs
index ae7c30e..e5e447c 100644
--- a/src/mount.rs
+++ b/src/mount.rs
@@ -4,6 +4,7 @@ use std::fs::File;
 use std::io::{BufRead, BufReader, Error, ErrorKind, Result};
 use std::os::unix::ffi::OsStringExt;
 
+#[derive(Clone)]
 pub struct Mount {
     pub source:  OsString,
     pub dest:    OsString,


Test patch: