diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs index 7b3ca372db..785a964cc2 100644 --- a/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs @@ -224,6 +224,9 @@ pub mod unix { pub fn freeifaddrs(ifa: *mut ifaddrs); pub fn setpriority(which: c_int, who: c_int, prio: c_int) -> c_int; + + #[cfg(test)] + pub fn pipe(pipefd: *mut i32) -> i32; } #[cfg(any(target_os = "linux", target_os = "solaris", target_os = "illumos"))] @@ -606,6 +609,14 @@ pub mod windows { pub fn SetThreadPriority(pthread: HANDLE, npriority: i32) -> i32; pub fn GetCurrentThread() -> HANDLE; + + #[cfg(test)] + pub fn CreatePipe( + hreadpipe: *mut HANDLE, + hwritepipe: *mut HANDLE, + lppipeattributes: *mut c_void, + nsize: u32, + ) -> i32; } pub const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002; diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/io.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/io.rs index 7b0e064d51..d46572437d 100644 --- a/subprojects/gstreamer/libs/gst/helpers/ptp/io.rs +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/io.rs @@ -8,8 +8,23 @@ // // SPDX-License-Identifier: MPL-2.0 +/// Result of polling the inputs of the `Poll`. +/// +/// Any input that has data available for reading will be set to `true`, potentially multiple +/// at once. +/// +/// Note that reading from the sockets is non-blocking but reading from stdin is blocking so +/// special care has to be taken to only read as much as is available. +pub struct PollResult { + pub event_socket: bool, + pub general_socket: bool, + pub stdin: bool, +} + #[cfg(unix)] mod imp { + use super::PollResult; + use std::{ io::{self, Read, Write}, net::UdpSocket, @@ -28,17 +43,81 @@ mod imp { stdout: Stdout, } - /// Result of polling the inputs of the `Poll`. - /// - /// Any input that has data available for reading will be set to `true`, potentially multiple - /// at once. - /// - /// Note that reading from the sockets is non-blocking but reading from stdin is blocking so - /// special care has to be taken to only read as much as is available. - pub struct PollResult { - pub event_socket: bool, - pub general_socket: bool, - pub stdin: bool, + #[cfg(test)] + /// A file descriptor pair representing a pipe for testing purposes. + pub struct Pipe { + pub read: i32, + pub write: i32, + } + + #[cfg(test)] + impl Pipe { + fn new() -> io::Result { + // SAFETY: Requires two integers to be passed in and creates the read + // and write end of a pipe. + unsafe { + let mut fds = std::mem::MaybeUninit::<[i32; 2]>::uninit(); + let res = pipe(fds.as_mut_ptr() as *mut i32); + if res == 0 { + let fds = fds.assume_init(); + Ok(Pipe { + read: fds[0], + write: fds[1], + }) + } else { + Err(io::Error::last_os_error()) + } + } + } + } + + #[cfg(test)] + impl Drop for Pipe { + fn drop(&mut self) { + // SAFETY: Only ever created with valid fds + unsafe { + close(self.read); + close(self.write); + } + } + } + + #[cfg(test)] + impl Read for Pipe { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + // SAFETY: read() requires a valid fd and a mutable buffer with the given size. + // The fd is valid by construction as is the buffer. + // + // read() will return the number of bytes read or a negative value on errors. + let res = unsafe { read(self.read, buf.as_mut_ptr(), buf.len()) }; + + if res < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(res as usize) + } + } + } + + #[cfg(test)] + impl Write for Pipe { + fn write(&mut self, buf: &[u8]) -> io::Result { + // SAFETY: write() requires a valid fd and a mutable buffer with the given size. + // The fd is valid by construction as is the buffer. + // + // write() will return the number of bytes written or a negative value on errors. + let res = unsafe { write(self.write, buf.as_ptr(), buf.len()) }; + + if res == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(res as usize) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } } impl Poll { @@ -65,6 +144,29 @@ mod imp { }) } + #[cfg(test)] + /// Create a new `Poll` instance for testing purposes. + /// + /// The returned `Pipe`s are for stdin and stdout. + pub fn new_test( + event_socket: UdpSocket, + general_socket: UdpSocket, + ) -> Result<(Self, Pipe, Pipe), Error> { + let stdin = Pipe::new().unwrap(); + let stdout = Pipe::new().unwrap(); + + Ok(( + Self { + event_socket, + general_socket, + stdin: Stdin(stdin.read), + stdout: Stdout(stdout.write), + }, + stdin, + stdout, + )) + } + /// Mutable reference to the event socket. pub fn event_socket(&mut self) -> &mut UdpSocket { &mut self.event_socket @@ -210,6 +312,8 @@ mod imp { #[cfg(windows)] mod imp { + use super::PollResult; + use std::{ cmp, io::{self, Read, Write}, @@ -270,25 +374,111 @@ mod imp { } } - /// Result of polling the inputs of the `Poll`. - /// - /// Any input that has data available for reading will be set to `true`, potentially multiple - /// at once. - /// - /// Note that reading from the sockets is non-blocking but reading from stdin is blocking so - /// special care has to be taken to only read as much as is available. - pub struct PollResult { - pub event_socket: bool, - pub general_socket: bool, - pub stdin: bool, + #[cfg(test)] + pub struct Pipe { + read: HANDLE, + write: HANDLE, + } + + #[cfg(test)] + impl Drop for Pipe { + fn drop(&mut self) { + // SAFETY: Both handles are by construction valid up there. + unsafe { + CloseHandle(self.read); + CloseHandle(self.write); + } + } + } + + #[cfg(test)] + impl Pipe { + fn new() -> io::Result { + // SAFETY: On success returns a non-zero integer and stores read/write handles in the + // two out pointers, which will have to be closed again later. + unsafe { + let mut readpipe = mem::MaybeUninit::uninit(); + let mut writepipe = mem::MaybeUninit::uninit(); + + let res = CreatePipe( + readpipe.as_mut_ptr(), + writepipe.as_mut_ptr(), + ptr::null_mut(), + 0, + ); + + if res != 0 { + Ok(Self { + read: readpipe.assume_init(), + write: writepipe.assume_init(), + }) + } else { + Err(io::Error::last_os_error()) + } + } + } + } + + #[cfg(test)] + impl Read for Pipe { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + // SAFETY: Reads the given number of bytes into the buffer from the stdin handle. + unsafe { + let mut lpnumberofbytesread = mem::MaybeUninit::uninit(); + let res = ReadFile( + self.read, + buf.as_mut_ptr(), + cmp::min(buf.len() as u32, u32::MAX) as u32, + lpnumberofbytesread.as_mut_ptr(), + ptr::null_mut(), + ); + + if res == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(lpnumberofbytesread.assume_init() as usize) + } + } + } + } + + #[cfg(test)] + impl Write for Pipe { + fn write(&mut self, buf: &[u8]) -> io::Result { + // SAFETY: Writes the given number of bytes to stdout or at most u32::MAX. On error + // zero is returned, otherwise the number of bytes written is set accordingly and + // returned. + unsafe { + let mut lpnumberofbyteswritten = mem::MaybeUninit::uninit(); + let res = WriteFile( + self.write, + buf.as_ptr(), + cmp::min(buf.len() as u32, u32::MAX) as u32, + lpnumberofbyteswritten.as_mut_ptr(), + ptr::null_mut(), + ); + + if res == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(lpnumberofbyteswritten.assume_init() as usize) + } + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } } impl Poll { - /// Create a new `Poll` instance from the two sockets. - pub fn new(event_socket: UdpSocket, general_socket: UdpSocket) -> Result { - let stdin = Stdin::acquire().context("Failure acquiring stdin handle")?; - let stdout = Stdout::acquire().context("Failed acquiring stdout handle")?; - + /// Internal constructor. + pub fn new_internal( + event_socket: UdpSocket, + general_socket: UdpSocket, + stdin: Stdin, + stdout: Stdout, + ) -> Result { // Create event objects for the readability of the sockets. let event_socket_event = EventHandle::new().context("Failed creating WSA event")?; let general_socket_event = EventHandle::new().context("Failed creating WSA event")?; @@ -329,6 +519,35 @@ mod imp { }) } + /// Create a new `Poll` instance from the two sockets. + pub fn new(event_socket: UdpSocket, general_socket: UdpSocket) -> Result { + let stdin = Stdin::acquire().context("Failure acquiring stdin handle")?; + let stdout = Stdout::acquire().context("Failed acquiring stdout handle")?; + + Self::new_internal(event_socket, general_socket, stdin, stdout) + } + + #[cfg(test)] + /// Create a new `Poll` instance for testing purposes. + /// + /// The returned `Pipe`s are for stdin and stdout. + pub fn new_test( + event_socket: UdpSocket, + general_socket: UdpSocket, + ) -> Result<(Self, Pipe, Pipe), Error> { + let stdin_pipe = Pipe::new().unwrap(); + let stdout_pipe = Pipe::new().unwrap(); + + let stdin = + Stdin::from_handle(stdin_pipe.read).context("Failure acquiring stdin handle")?; + let stdout = + Stdout::from_handle(stdout_pipe.write).context("Failed acquiring stdout handle")?; + + let poll = Self::new_internal(event_socket, general_socket, stdin, stdout)?; + + Ok((poll, stdin_pipe, stdout_pipe)) + } + /// Mutable reference to the event socket. pub fn event_socket(&mut self) -> &mut UdpSocket { &mut self.event_socket @@ -516,6 +735,11 @@ mod imp { handle }; + + Self::from_handle(handle) + } + + fn from_handle(handle: HANDLE) -> Result { // SAFETY: GetFileType() is safe to call on any valid handle. let type_ = unsafe { GetFileType(handle) }; @@ -717,6 +941,11 @@ mod imp { handle }; + + Self::from_handle(handle) + } + + fn from_handle(handle: HANDLE) -> Result { // SAFETY: GetFileType() is safe to call on any valid handle. let type_ = unsafe { GetFileType(handle) }; @@ -766,4 +995,69 @@ mod imp { } } -pub use self::imp::{Poll, PollResult, Stdin, Stdout}; +pub use self::imp::{Poll, Stdin, Stdout}; + +#[cfg(test)] +mod test { + #[test] + fn test_poll() { + use std::io::prelude::*; + + let event_socket = std::net::UdpSocket::bind(std::net::SocketAddr::from(( + std::net::Ipv4Addr::LOCALHOST, + 0, + ))) + .unwrap(); + let event_port = event_socket.local_addr().unwrap().port(); + + let general_socket = std::net::UdpSocket::bind(std::net::SocketAddr::from(( + std::net::Ipv4Addr::LOCALHOST, + 0, + ))) + .unwrap(); + let general_port = general_socket.local_addr().unwrap().port(); + + let send_socket = std::net::UdpSocket::bind(std::net::SocketAddr::from(( + std::net::Ipv4Addr::LOCALHOST, + 0, + ))) + .unwrap(); + + let (mut poll, mut stdin, _stdout) = + super::Poll::new_test(event_socket, general_socket).unwrap(); + + let mut buf = [0u8; 4]; + + for _ in 0..10 { + send_socket + .send_to(&[1, 2, 3, 4], (std::net::Ipv4Addr::LOCALHOST, event_port)) + .unwrap(); + let res = poll.poll().unwrap(); + assert!(res.event_socket); + assert!(!res.general_socket); + assert!(!res.stdin); + assert_eq!(poll.event_socket().recv(&mut buf).unwrap(), 4); + assert_eq!(buf, [1, 2, 3, 4]); + + send_socket + .send_to(&[1, 2, 3, 4], (std::net::Ipv4Addr::LOCALHOST, general_port)) + .unwrap(); + let res = poll.poll().unwrap(); + assert!(!res.event_socket); + assert!(res.general_socket); + assert!(!res.stdin); + assert_eq!(poll.general_socket().recv(&mut buf).unwrap(), 4); + assert_eq!(buf, [1, 2, 3, 4]); + + stdin.write_all(&[1, 2, 3, 4]).unwrap(); + let res = poll.poll().unwrap(); + assert!(!res.event_socket); + assert!(!res.general_socket); + assert!(res.stdin); + poll.stdin().read_exact(&mut buf).unwrap(); + assert_eq!(buf, [1, 2, 3, 4]); + } + + drop(poll); + } +} diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/meson.build b/subprojects/gstreamer/libs/gst/helpers/ptp/meson.build index 7ebc2c8964..55f1c6a106 100644 --- a/subprojects/gstreamer/libs/gst/helpers/ptp/meson.build +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/meson.build @@ -98,7 +98,6 @@ conf_lib_rs = configure_file(input : 'conf_lib.rs.in', conf = static_library('gst_ptp_helper_conf', conf_lib_rs, override_options : ['rust_std=2018'], - rust_args : ['-Cpanic=abort'], rust_crate_type : 'rlib') exe = executable('gst-ptp-helper', 'main.rs', @@ -109,6 +108,16 @@ exe = executable('gst-ptp-helper', 'main.rs', install_dir : helpers_install_dir, install : true) +exe_test = executable('gst-ptp-helper-test', 'main.rs', + override_options : ['rust_std=2018'], + rust_args : ['--test', rust_args], + dependencies : [cap_dep], + link_with : conf, + install_dir : helpers_install_dir, + install : true) + +test('gst-ptp-helper-test', exe_test, protocol : 'rust') + if host_system != 'windows' meson.add_install_script('ptp_helper_post_install.sh', helpers_install_dir, with_ptp_helper_permissions, diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs index e3b0bbddeb..fc9b263072 100644 --- a/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs @@ -665,3 +665,34 @@ mod imp { } pub use imp::*; + +#[cfg(test)] +mod test { + #[test] + fn test_query_interfaces() { + let ifaces = super::query_interfaces().unwrap(); + for iface in ifaces { + assert!(!iface.name.is_empty()); + assert_ne!(iface.index, 0); + assert!(!iface.ip_addr.is_unspecified()); + } + } + + #[test] + fn test_join_multicast() { + let ifaces = super::query_interfaces().unwrap(); + let iface = if ifaces.is_empty() { + return; + } else { + &ifaces[0] + }; + + let socket = std::net::UdpSocket::bind(std::net::SocketAddr::from(( + std::net::Ipv4Addr::UNSPECIFIED, + 0, + ))) + .unwrap(); + super::set_reuse(&socket); + super::join_multicast_v4(&socket, &std::net::Ipv4Addr::new(224, 0, 0, 1), iface).unwrap(); + } +} diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/privileges.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/privileges.rs index 66e5c954e1..7ab567714c 100644 --- a/subprojects/gstreamer/libs/gst/helpers/ptp/privileges.rs +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/privileges.rs @@ -10,125 +10,184 @@ use crate::error::Error; -/// Drop all additional permissions / capabilities the current process might have as they're not -/// needed anymore. -/// -/// This does nothing if no such mechanism is implemented / selected for the target platform. -pub fn drop() -> Result<(), Error> { - #[cfg(ptp_helper_permissions = "setcap")] - { - // Drop all current capabilities of the process. +#[cfg(ptp_helper_permissions = "setcap")] +mod setcap { + use super::*; - use std::io; + use crate::{error::Context, ffi::unix::setcaps::*}; + use std::io; - use crate::{bail, ffi::unix::setcaps::*}; + struct Cap(cap_t); - struct Cap(cap_t); - impl Drop for Cap { - fn drop(&mut self) { - // SAFETY: The capabilities are valid by construction and are only dropped - // once here. - unsafe { - let _ = cap_free(self.0); - } - } - } - - // SAFETY: There are 3 steps here - // 1. Get the current capabilities of the process. This - // returns NULL on error or otherwise newly allocated capabilities that have to be - // freed again in the end. For that purpose we wrap them in the Cap struct. - // - // 2. Clearing all current capabilities. This requires a valid capabilities pointer, - // which we have at this point by construction. - // - // 3. Setting the current process's capabilities, which is only affecting the current - // thread unfortunately. At this point, no other threads were started yet so this is - // not a problem. Also the capabilities pointer is still valid by construction. - // - // On every return path, the capabilities are going to be freed. - unsafe { - let c = cap_get_proc(); - if c.is_null() { - bail!( - source: io::Error::last_os_error(), - "Failed to get current process capabilities" - ); - } - - let c = Cap(c); - if cap_clear(c.0) != 0 { - bail!( - source: io::Error::last_os_error(), - "Failed to clear capabilities" - ); - } - if cap_set_proc(c.0) != 0 { - bail!( - source: io::Error::last_os_error(), - "Failed to set current process capabilities" - ); + impl Drop for Cap { + fn drop(&mut self) { + // SAFETY: The capabilities are valid by construction and are only dropped + // once here. + unsafe { + let _ = cap_free(self.0); } } } - #[cfg(ptp_helper_permissions = "setuid-root")] - { - // Drop the process's UID/GID from root to the configured user/group or the user "nobody". - use std::{ffi::CString, io}; - - use crate::{bail, error::Context, ffi::unix::setuid_root::*}; - - fn get_uid_gid_for_user(name: &str) -> io::Result<(uid_t, gid_t)> { - let name_cstr = CString::new(name).unwrap(); - - loop { - // SAFETY: getpwnam() requires a NUL-terminated user name string and - // returns either the user information in static storage, or NULL on error. - // In case of EINTR, getting the user information can be retried. - // - // The user information is stored in static storage so might change if something - // else calls related functions. As this is the only thread up to this point and we - // just extract two integers from it there is no such possibility. - unsafe { - let pw = getpwnam(name_cstr.as_ptr()); - if pw.is_null() { - let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::Interrupted { - continue; - } - return Err(err); - } - return Ok(((*pw).pw_uid, (*pw).pw_gid)); + impl Cap { + /// Get the current process' capabilities. + fn get_proc() -> io::Result { + // SAFETY: Get the current capabilities of the process. This returns NULL on error or + // otherwise newly allocated capabilities that have to be freed again in the end. For + // that purpose we wrap them in the Cap struct. + unsafe { + let c = cap_get_proc(); + if c.is_null() { + return Err(io::Error::last_os_error()); } + + Ok(Cap(c)) } } - fn get_gid_for_group(name: &str) -> io::Result { - let name_cstr = CString::new(name).unwrap(); - loop { - // SAFETY: getgrnam() requires a NUL-terminated group name string and - // returns either the group information in static storage, or NULL on error. - // In case of EINTR, getting the group information can be retried. - // - // The user information is stored in static storage so might change if something - // else calls related functions. As this is the only thread up to this point and we - // just extract two integers from it there is no such possibility. - unsafe { - let grp = getgrnam(name_cstr.as_ptr()); - if grp.is_null() { - let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::Interrupted { - continue; - } - return Err(err); + /// Clear all capabilities. + fn clear(&mut self) -> io::Result<()> { + // SAFETY: Clearing all current capabilities. This requires a valid capabilities + // pointer, which we have at this point by construction. + unsafe { + if cap_clear(self.0) != 0 { + return Err(io::Error::last_os_error()); + } + } + + Ok(()) + } + + /// Set current process' capabilities. + fn set_proc(&self) -> io::Result<()> { + // SAFETY: Setting the current process's capabilities, which is only affecting the + // current thread unfortunately. At this point, no other threads were started yet so + // this is not a problem. Also the capabilities pointer is still valid by construction. + unsafe { + if cap_set_proc(self.0) != 0 { + return Err(io::Error::last_os_error()); + } + } + Ok(()) + } + } + + /// Drop all current capabilities of the process. + pub fn drop() -> Result<(), Error> { + let mut c = Cap::get_proc().context("Failed to get current process capabilities")?; + c.clear().context("Failed to clear capabilities")?; + c.set_proc() + .context("Failed to set current process capabilities")?; + + Ok(()) + } + + #[cfg(test)] + mod test { + #[test] + fn test_get_set_same_and_clear_cap() { + let mut c = super::Cap::get_proc().unwrap(); + // Setting the same capabilities should always succeed + c.set_proc().unwrap(); + c.clear().unwrap(); + } + } +} + +#[cfg(ptp_helper_permissions = "setuid-root")] +mod setuid_root { + use super::*; + + use crate::{bail, error::Context, ffi::unix::setuid_root::*}; + use std::{ffi::CString, io}; + + /// Retrieve UID and GID for the given username. + fn get_uid_gid_for_user(name: &str) -> io::Result<(uid_t, gid_t)> { + let name_cstr = CString::new(name).unwrap(); + + loop { + // SAFETY: getpwnam() requires a NUL-terminated user name string and + // returns either the user information in static storage, or NULL on error. + // In case of EINTR, getting the user information can be retried. + // + // The user information is stored in static storage so might change if something + // else calls related functions. As this is the only thread up to this point and we + // just extract two integers from it there is no such possibility. + unsafe { + let pw = getpwnam(name_cstr.as_ptr()); + if pw.is_null() { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::Interrupted { + continue; + } + if err.raw_os_error() == Some(0) { + return Err(io::Error::from(io::ErrorKind::NotFound)); } - return Ok((*grp).gr_gid); + return Err(err); } + return Ok(((*pw).pw_uid, (*pw).pw_gid)); + } + } + } + + /// Retrieve GID for the given group name. + fn get_gid_for_group(name: &str) -> io::Result { + let name_cstr = CString::new(name).unwrap(); + loop { + // SAFETY: getgrnam() requires a NUL-terminated group name string and + // returns either the group information in static storage, or NULL on error. + // In case of EINTR, getting the group information can be retried. + // + // The user information is stored in static storage so might change if something + // else calls related functions. As this is the only thread up to this point and we + // just extract two integers from it there is no such possibility. + unsafe { + let grp = getgrnam(name_cstr.as_ptr()); + if grp.is_null() { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::Interrupted { + continue; + } + if err.raw_os_error() == Some(0) { + return Err(io::Error::from(io::ErrorKind::NotFound)); + } + + return Err(err); + } + + return Ok((*grp).gr_gid); + } + } + } + + #[cfg(test)] + mod test { + #[test] + fn test_get_uid_gid_for_user() { + match super::get_uid_gid_for_user("root") { + Ok(_) => (), + Err(err) if err.kind() != std::io::ErrorKind::NotFound => { + panic!("{}", err); + } + _ => (), } } + #[test] + fn test_get_gid_for_group() { + match super::get_gid_for_group("root") { + Ok(_) => (), + Err(err) if err.kind() != std::io::ErrorKind::NotFound => { + panic!("{}", err); + } + _ => (), + } + } + } + + /// Drop the process's UID/GID from root to the configured user/group or the user "nobody". + pub fn drop() -> Result<(), Error> { let username = gst_ptp_helper_conf::PTP_HELPER_SETUID_USER.unwrap_or("nobody"); let (uid, gid) = get_uid_gid_for_user(username) @@ -163,6 +222,23 @@ pub fn drop() -> Result<(), Error> { bail!(source: err, "Failed to set user id {} for process", uid); } } + + Ok(()) + } +} + +/// Drop all additional permissions / capabilities the current process might have as they're not +/// needed anymore. +/// +/// This does nothing if no such mechanism is implemented / selected for the target platform. +pub fn drop() -> Result<(), Error> { + #[cfg(ptp_helper_permissions = "setcap")] + { + setcap::drop()?; + } + #[cfg(ptp_helper_permissions = "setuid-root")] + { + setuid_root::drop()?; } Ok(()) diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/rand.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/rand.rs index 48cda11ec5..a89a371786 100644 --- a/subprojects/gstreamer/libs/gst/helpers/ptp/rand.rs +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/rand.rs @@ -8,70 +8,31 @@ // // SPDX-License-Identifier: MPL-2.0 -/// Returns a random'ish 64 bit value. -pub fn rand() -> [u8; 8] { - #[cfg(unix)] - { - // Try getrandom syscall or otherwise first on Linux - #[cfg(target_os = "linux")] - { - use std::io::Read; +#[cfg(unix)] +mod unix { + use std::io; - use crate::ffi::unix::linux::*; + #[cfg(target_os = "linux")] + /// Try using the getrandom syscall on Linux. + pub fn getrandom() -> io::Result<[u8; 8]> { + use std::io::Read; - // Depends on us knowing the syscall number - if SYS_getrandom != 0 { - struct GetRandom; + use crate::ffi::unix::linux::*; - impl Read for GetRandom { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - // SAFETY: `getrandom` syscall fills up to the requested amount of bytes of - // the provided memory and returns the number of bytes or a negative value - // on errors. - unsafe { - let res = syscall(SYS_getrandom, buf.as_mut_ptr(), buf.len(), 0u32); - if res < 0 { - Err(std::io::Error::last_os_error()) - } else { - Ok(res as usize) - } - } - } - } - - let mut r = [0u8; 8]; - if GetRandom.read_exact(&mut r).is_ok() { - return r; - } - } + // Depends on us knowing the syscall number + if SYS_getrandom == 0 { + return Err(io::Error::from(io::ErrorKind::Unsupported)); } - // Otherwise try /dev/urandom - { - use crate::ffi::unix::*; - use std::{io::Read, os::raw::c_int}; + struct GetRandom; - struct Fd(c_int); - - impl Drop for Fd { - fn drop(&mut self) { - // SAFETY: The fd is valid by construction below and closed by this at - // most once. - unsafe { - // Return value is intentionally ignored as there's nothing that - // can be done on errors anyway. - let _ = close(self.0); - } - } - } - - impl Read for Fd { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - // SAFETY: read() requires a valid fd and a mutable buffer with the given size. - // The fd is valid by construction as is the buffer. - // - // read() will return the number of bytes read or a negative value on errors. - let res = unsafe { read(self.0, buf.as_mut_ptr(), buf.len()) }; + impl Read for GetRandom { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + // SAFETY: `getrandom` syscall fills up to the requested amount of bytes of + // the provided memory and returns the number of bytes or a negative value + // on errors. + unsafe { + let res = syscall(SYS_getrandom, buf.as_mut_ptr(), buf.len(), 0u32); if res < 0 { Err(std::io::Error::last_os_error()) } else { @@ -79,37 +40,108 @@ pub fn rand() -> [u8; 8] { } } } + } - let fd = loop { - // SAFETY: open() requires a NUL-terminated file path and will - // return an integer in any case. A negative value is an invalid fd - // and signals an error. On EINTR, opening can be retried. - let fd = unsafe { open(b"/dev/urandom\0".as_ptr(), O_RDONLY) }; - if fd < 0 { - let err = std::io::Error::last_os_error(); - if err.kind() == std::io::ErrorKind::Interrupted { - continue; - } + let mut r = [0u8; 8]; + GetRandom.read_exact(&mut r)?; - break None; - } + Ok(r) + } - break Some(Fd(fd)); - }; + /// Try reading random numbers from /dev/urandom. + pub fn dev_urandom() -> io::Result<[u8; 8]> { + use crate::ffi::unix::*; + use std::{io::Read, os::raw::c_int}; - if let Some(mut fd) = fd { - let mut r = [0u8; 8]; + struct Fd(c_int); - if fd.read_exact(&mut r).is_ok() { - return r; + impl Drop for Fd { + fn drop(&mut self) { + // SAFETY: The fd is valid by construction below and closed by this at + // most once. + unsafe { + // Return value is intentionally ignored as there's nothing that + // can be done on errors anyway. + let _ = close(self.0); } } } + + impl Read for Fd { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + // SAFETY: read() requires a valid fd and a mutable buffer with the given size. + // The fd is valid by construction as is the buffer. + // + // read() will return the number of bytes read or a negative value on errors. + let res = unsafe { read(self.0, buf.as_mut_ptr(), buf.len()) }; + if res < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(res as usize) + } + } + } + + let mut fd = loop { + // SAFETY: open() requires a NUL-terminated file path and will + // return an integer in any case. A negative value is an invalid fd + // and signals an error. On EINTR, opening can be retried. + let fd = unsafe { open(b"/dev/urandom\0".as_ptr(), O_RDONLY) }; + if fd < 0 { + let err = std::io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::Interrupted { + continue; + } + + return Err(err); + } + + break Fd(fd); + }; + + let mut r = [0u8; 8]; + fd.read_exact(&mut r)?; + + Ok(r) } - #[cfg(windows)] - { - // Try BCryptGenRandom(), which is available since Windows Vista - // + + #[cfg(test)] + mod test { + #[test] + fn test_dev_urandom() { + match super::dev_urandom() { + Ok(n) => { + assert_ne!(n, [0u8; 8]); + } + Err(err) if err.kind() != std::io::ErrorKind::NotFound => { + panic!("{}", err); + } + _ => (), + } + } + + #[cfg(target_os = "linux")] + #[test] + fn test_getrandom() { + match super::getrandom() { + Ok(n) => { + assert_ne!(n, [0u8; 8]); + } + Err(err) if err.kind() != std::io::ErrorKind::Unsupported => { + panic!("{}", err); + } + _ => (), + } + } + } +} + +#[cfg(windows)] +mod windows { + use std::io; + + /// Call BCryptGenRandom(), which is available since Windows Vista. + pub fn bcrypt_gen_random() -> io::Result<[u8; 8]> { // SAFETY: BCryptGenRandom() fills the provided memory with the requested number of bytes // and returns 0 on success. In that case, all memory was written and is initialized now. unsafe { @@ -125,12 +157,25 @@ pub fn rand() -> [u8; 8] { BCRYPT_USE_SYSTEM_PREFERRED_RNG, ); if res == 0 { - return r.assume_init(); + Ok(r.assume_init()) + } else { + Err(io::Error::from_raw_os_error(res as i32)) } } } - // As fallback use a combination of the process ID and the current system time + #[cfg(test)] + mod test { + #[test] + fn test_bcrypt_gen_random() { + let n = super::bcrypt_gen_random().unwrap(); + assert_ne!(n, [0u8; 8]); + } + } +} + +/// As fallback use a combination of the process ID and the current system time. +fn fallback_rand() -> [u8; 8] { let now = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .unwrap() @@ -148,3 +193,52 @@ pub fn rand() -> [u8; 8] { now[7] ^ now[8] ^ pid[3], ] } + +/// Returns a random'ish 64 bit value. +pub fn rand() -> [u8; 8] { + #[cfg(unix)] + { + // Try getrandom syscall or otherwise first on Linux + #[cfg(target_os = "linux")] + { + if let Ok(r) = unix::getrandom() { + return r; + } + } + + if let Ok(r) = unix::dev_urandom() { + return r; + } + } + #[cfg(windows)] + { + if let Ok(r) = windows::bcrypt_gen_random() { + return r; + } + } + + fallback_rand() +} + +#[cfg(test)] +mod test { + // While not a very useful test for randomness, we're mostly interested here + // in whether the memory is initialized correctly and nothing crashes because + // of the usage of unsafe code above. If the memory was not initialized fully + // then this test would fail in e.g. valgrind. + // + // Technically, all zeroes could be returned as a valid random number but that's + // extremely unlikely and more likely a bug in the code above. + + #[test] + fn test_rand() { + let n = super::rand(); + assert_ne!(n, [0u8; 8]); + } + + #[test] + fn test_fallback_rand() { + let n = super::fallback_rand(); + assert_ne!(n, [0u8; 8]); + } +}