Skip to content

Commit

Permalink
Detect powershell (#265)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jake-Shadle authored Nov 18, 2024
1 parent 0f8c369 commit c64807d
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 7 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,16 @@ jobs:
- name: cargo test build
run: cargo build --tests --release
- name: cargo test
shell: bash
run: cargo test --release
- name: detects powershell
if: ${{ matrix.os != 'macos-14' }}
shell: pwsh
run: cargo test --release -- --ignored is_powershell_true
- name: doesn't detect powershell
if: ${{ matrix.os != 'macos-14' }}
shell: bash
run: cargo test --release -- --ignored is_powershell_false

msrv-check:
name: Minimum Stable Rust Version Check
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ home = "0.5"
ignore = "0.4"
# Dependency graphing
krates = { version = "0.17.1", features = ["metadata"] }
# Parent process retrieval
libc = "0.2"
# Logging macros
log = "0.4"
# Better heap allocator over system one (usually)
Expand Down
22 changes: 22 additions & 0 deletions src/bindings.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
output = "win_bindings.rs"
binds = [
"MAX_PATH",
"NtClose",
"NtOpenProcess",
"NtQueryInformationProcess",
"ProcessBasicInformation",
"ProcessImageFileName",
"PROCESS_BASIC_INFORMATION",
"PROCESS_QUERY_INFORMATION",
"STATUS_SUCCESS",
"UNICODE_STRING",
]

[bind-mode]
mode = "minwin"

[bind-mode.config]
enum-style = "minwin"
fix-naming = true
use-rust-casing = true
linking-style = "raw-dylib"
21 changes: 14 additions & 7 deletions src/cargo-about/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@ pub fn cmd(args: Args, color: crate::Color) -> anyhow::Result<()> {
"handlebars template(s) must be specified when using handlebars output format"
);

// Check if the parent process is powershell, if it is, assume that it will
// screw up the output https://github.com/EmbarkStudios/cargo-about/issues/198
// and inform the user about the -o, --output-file option
let redirect_stdout =
args.output_file.is_none() || args.output_file.as_deref() == Some(Path::new("-"));
if redirect_stdout {
anyhow::ensure!(!cargo_about::is_powershell_parent(), "cargo-about should not redirect its output in powershell, please use the -o, --output-file option to redirect to a file to avoid powershell encoding issues");
}

rayon::scope(|s| {
s.spawn(|_| {
log::info!("gathering crates for {manifest_path}");
Expand Down Expand Up @@ -289,13 +298,11 @@ pub fn cmd(args: Args, color: crate::Color) -> anyhow::Result<()> {
serde_json::to_string(&input)?
};

match args.output_file.as_ref() {
None => println!("{output}"),
Some(path) if path == Path::new("-") => println!("{output}"),
Some(path) => {
std::fs::write(path, output)
.with_context(|| format!("output file {path} could not be written"))?;
}
if let Some(path) = &args.output_file.filter(|_| !redirect_stdout) {
std::fs::write(path, output)
.with_context(|| format!("output file {path} could not be written"))?;
} else {
println!("{output}");
}

Ok(())
Expand Down
172 changes: 172 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,175 @@ pub fn validate_sha256(buffer: &str, expected: &str) -> anyhow::Result<()> {

Ok(())
}

#[cfg(target_family = "unix")]
#[allow(unsafe_code)]
pub fn is_powershell_parent() -> bool {
if !cfg!(target_os = "linux") {
// Making the assumption that no one on MacOS or any of the *BSDs uses powershell...
return false;
}

// SAFETY: no invariants to uphold
let mut parent_id = Some(unsafe { libc::getppid() });

while let Some(ppid) = parent_id {
let Ok(cmd) = std::fs::read_to_string(format!("/proc/{ppid}/cmdline")) else {
break;
};

let Some(proc) = cmd
.split('\0')
.next()
.and_then(|path| path.split('/').last())
else {
break;
};

if proc == "pwsh" {
return true;
}

let Ok(status) = std::fs::read_to_string(format!("/proc/{ppid}/status")) else {
break;
};

for line in status.lines() {
let Some(ppid) = line.strip_prefix("PPid:\t") else {
continue;
};

parent_id = ppid.parse().ok();
break;
}
}

false
}

#[cfg(target_family = "windows")]
mod win_bindings;

#[cfg(target_family = "windows")]
#[allow(unsafe_code)]
pub fn is_powershell_parent() -> bool {
use std::os::windows::ffi::OsStringExt as _;
use win_bindings::*;

struct NtHandle {
handle: isize,
}

impl Drop for NtHandle {
fn drop(&mut self) {
if self.handle != -1 {
unsafe {
nt_close(self.handle);
}
}
}
}

let mut handle = Some(NtHandle { handle: -1 });

unsafe {
let reset = |fname: &mut [u16]| {
let ustr = &mut *fname.as_mut_ptr().cast::<UnicodeString>();
ustr.length = 0;
ustr.maximum_length = MaxPath as _;
};

// The API for this is extremely irritating, the struct and string buffer
// need to be the same :/
let mut file_name = [0u16; MaxPath as usize + std::mem::size_of::<UnicodeString>() / 2];

while let Some(ph) = handle {
let mut basic_info = std::mem::MaybeUninit::<ProcessBasicInformation>::uninit();
let mut length = 0;
if nt_query_information_process(
ph.handle,
Processinfoclass::ProcessBasicInformation,
basic_info.as_mut_ptr().cast(),
std::mem::size_of::<ProcessBasicInformation>() as _,
&mut length,
) != StatusSuccess
{
break;
}

if length != std::mem::size_of::<ProcessBasicInformation>() as u32 {
break;
}

let basic_info = basic_info.assume_init();
reset(&mut file_name);

let ppid = basic_info.inherited_from_unique_process_id as isize;

if ppid == 0 || ppid == -1 {
break;
}

let mut parent_handle = -1;
let obj_attr = std::mem::zeroed();
let client_id = ClientId {
unique_process: ppid,
unique_thread: 0,
};
if nt_open_process(
&mut parent_handle,
ProcessAccessRights::ProcessQueryInformation,
&obj_attr,
&client_id,
) != StatusSuccess
{
break;
}

handle = Some(NtHandle {
handle: parent_handle,
});

if nt_query_information_process(
parent_handle,
Processinfoclass::ProcessImageFileName,
file_name.as_mut_ptr().cast(),
(file_name.len() * 2) as _,
&mut length,
) != StatusSuccess
{
break;
}

let ustr = &*file_name.as_ptr().cast::<UnicodeString>();
let os = std::ffi::OsString::from_wide(std::slice::from_raw_parts(
ustr.buffer,
(ustr.length >> 1) as usize,
));

let path = std::path::Path::new(&os);
if let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) {
if stem == "pwsh" || stem == "powershell" {
return true;
}
}
}

false
}
}

#[cfg(test)]
mod test {
#[test]
#[ignore = "call when actually run from powershell"]
fn is_powershell_true() {
assert!(super::is_powershell_parent());
}

#[test]
#[ignore = "call when not actually run from powershell"]
fn is_powershell_false() {
assert!(!super::is_powershell_parent());
}
}
111 changes: 111 additions & 0 deletions src/win_bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! Bindings generated by `minwin` 0.1.0
#![allow(
non_snake_case,
non_upper_case_globals,
non_camel_case_types,
clippy::upper_case_acronyms
)]
#[link(name = "ntdll", kind = "raw-dylib")]
extern "system" {
#[link_name = "NtClose"]
pub fn nt_close(handle: Handle) -> Ntstatus;
#[link_name = "NtOpenProcess"]
pub fn nt_open_process(
process_handle: *mut Handle,
desired_access: u32,
object_attributes: *const ObjectAttributes,
client_id: *const ClientId,
) -> Ntstatus;
#[link_name = "NtQueryInformationProcess"]
pub fn nt_query_information_process(
process_handle: Handle,
process_information_class: Processinfoclass::Enum,
process_information: *mut ::core::ffi::c_void,
process_information_length: u32,
return_length: *mut u32,
) -> Ntstatus;
}
pub const MaxPath: u32 = 260;
#[repr(C)]
pub struct ClientId {
pub unique_process: Handle,
pub unique_thread: Handle,
}
pub type Handle = isize;
#[repr(C)]
pub struct ListEntry {
pub flink: *mut ListEntry,
pub blink: *mut ListEntry,
}
pub type Ntstatus = i32;
pub const StatusSuccess: Ntstatus = 0;
#[repr(C)]
pub struct ObjectAttributes {
pub length: u32,
pub root_directory: Handle,
pub object_name: *mut UnicodeString,
pub attributes: u32,
pub security_descriptor: *mut ::core::ffi::c_void,
pub security_quality_of_service: *mut ::core::ffi::c_void,
}
#[repr(C)]
pub struct Peb {
pub reserved1: [u8; 2],
pub being_debugged: u8,
pub reserved2: [u8; 1],
pub reserved3: [*mut ::core::ffi::c_void; 2],
pub ldr: *mut PebLdrData,
pub process_parameters: *mut RtlUserProcessParameters,
pub reserved4: [*mut ::core::ffi::c_void; 3],
pub atl_thunk_s_list_ptr: *mut ::core::ffi::c_void,
pub reserved5: *mut ::core::ffi::c_void,
pub reserved6: u32,
pub reserved7: *mut ::core::ffi::c_void,
pub reserved8: u32,
pub atl_thunk_s_list_ptr32: u32,
pub reserved9: [*mut ::core::ffi::c_void; 45],
pub reserved10: [u8; 96],
pub post_process_init_routine: PpsPostProcessInitRoutine,
pub reserved11: [u8; 128],
pub reserved12: [*mut ::core::ffi::c_void; 1],
pub session_id: u32,
}
#[repr(C)]
pub struct PebLdrData {
pub reserved1: [u8; 8],
pub reserved2: [*mut ::core::ffi::c_void; 3],
pub in_memory_order_module_list: ListEntry,
}
pub type PpsPostProcessInitRoutine = ::core::option::Option<unsafe extern "system" fn()>;
pub mod ProcessAccessRights {
pub type Enum = u32;
pub const ProcessQueryInformation: Enum = 1024;
}
#[repr(C)]
pub struct ProcessBasicInformation {
pub exit_status: Ntstatus,
pub peb_base_address: *mut Peb,
pub affinity_mask: usize,
pub base_priority: i32,
pub unique_process_id: usize,
pub inherited_from_unique_process_id: usize,
}
pub mod Processinfoclass {
pub type Enum = i32;
pub const ProcessBasicInformation: Enum = 0;
pub const ProcessImageFileName: Enum = 27;
}
pub type Pwstr = *mut u16;
#[repr(C)]
pub struct RtlUserProcessParameters {
pub reserved1: [u8; 16],
pub reserved2: [*mut ::core::ffi::c_void; 10],
pub image_path_name: UnicodeString,
pub command_line: UnicodeString,
}
#[repr(C)]
pub struct UnicodeString {
pub length: u16,
pub maximum_length: u16,
pub buffer: Pwstr,
}

0 comments on commit c64807d

Please sign in to comment.