1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use ctor::ctor;
use std::{
  io::{Error, ErrorKind, Result},
  path::{Path, PathBuf},
};
/// A cached version of the current binary using [`ctor`] to cache it before even `main` runs.
#[ctor]
#[used]
pub(super) static STARTING_BINARY: StartingBinary = StartingBinary::new();
/// Represents a binary path that was cached when the program was loaded.
pub(super) struct StartingBinary(std::io::Result<PathBuf>);
impl StartingBinary {
  /// Find the starting executable as safely as possible.
  fn new() -> Self {
    // see notes on current_exe() for security implications
    let dangerous_path = match std::env::current_exe() {
      Ok(dangerous_path) => dangerous_path,
      error @ Err(_) => return Self(error),
    };
    // note: this only checks symlinks on problematic platforms, see implementation below
    if let Some(symlink) = Self::has_symlink(&dangerous_path) {
      return Self(Err(Error::new(
        ErrorKind::InvalidData,
        format!("StartingBinary found current_exe() that contains a symlink on a non-allowed platform: {}", symlink.display()),
      )));
    }
    // we canonicalize the path to resolve any symlinks to the real exe path
    Self(dangerous_path.canonicalize())
  }
  /// A clone of the [`PathBuf`] found to be the starting path.
  ///
  /// Because [`Error`] is not clone-able, it is recreated instead.
  pub(super) fn cloned(&self) -> Result<PathBuf> {
    self
      .0
      .as_ref()
      .map(Clone::clone)
      .map_err(|e| Error::new(e.kind(), e.to_string()))
  }
  /// We only care about checking this on macOS currently, as it has the least symlink protections.
  #[cfg(any(
    not(target_os = "macos"),
    feature = "process-relaunch-dangerous-allow-symlink-macos"
  ))]
  fn has_symlink(_: &Path) -> Option<&Path> {
    None
  }
  /// We only care about checking this on macOS currently, as it has the least symlink protections.
  #[cfg(all(
    target_os = "macos",
    not(feature = "process-relaunch-dangerous-allow-symlink-macos")
  ))]
  fn has_symlink(path: &Path) -> Option<&Path> {
    path.ancestors().find(|ancestor| {
      matches!(
        ancestor
          .symlink_metadata()
          .as_ref()
          .map(std::fs::Metadata::file_type)
          .as_ref()
          .map(std::fs::FileType::is_symlink),
        Ok(true)
      )
    })
  }
}