mirror of
https://github.com/joleuger/vuinputd.git
synced 2026-07-17 16:36:03 +00:00
Small refactorings and note in SECURITY.md
This commit is contained in:
parent
2fe9a70006
commit
b3d0a0225d
7 changed files with 174 additions and 147 deletions
|
|
@ -8,4 +8,15 @@
|
|||
|
||||
## Unsafe code
|
||||
|
||||
Known problem: A lot of unsafe code. Open are ideas to mitigate this issue.
|
||||
Known problem: A lot of unsafe code. Open are ideas to mitigate this issue.
|
||||
|
||||
## seccomp, AppArmor, SELinux, cgroups mounts, /sys read-write
|
||||
|
||||
This is a big TODO. Which permissions can be reduced. Now we assume we are quite privileagued:
|
||||
- We have all Linux kernel capabilities,
|
||||
- The default seccomp profile is disabled,
|
||||
- The default AppArmor profile is disabled,
|
||||
- The default SELinux process label is disabled,
|
||||
- all host devices are accessible,
|
||||
- /sys is read-write,
|
||||
- cgroups mount is read-write.
|
||||
|
|
@ -2,153 +2,21 @@
|
|||
//
|
||||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
pub mod state;
|
||||
pub mod vuinput_ioctl;
|
||||
pub mod vuinput_write;
|
||||
pub mod vuinput_release;
|
||||
pub mod vuinput_open;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
use std::{fs, io};
|
||||
use std::os::unix::fs::{FileTypeExt, MetadataExt};
|
||||
use std::sync::{Arc, Mutex, OnceLock, RwLock};
|
||||
use std::io::{ErrorKind};
|
||||
|
||||
use ::cuse_lowlevel::*;
|
||||
use state::*;
|
||||
|
||||
use crate::process_tools::RequestingProcess;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct VuInputDevice {
|
||||
cuse_fh : u64,
|
||||
major : u64,
|
||||
minor : u64,
|
||||
syspath: String,
|
||||
devnode: String,
|
||||
runtime_data: Option<String>,
|
||||
netlink_data: Option<String>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VuInputState {
|
||||
file: File,
|
||||
requesting_process: RequestingProcess,
|
||||
input_device: Option<VuInputDevice>
|
||||
}
|
||||
|
||||
#[derive(Debug,Eq, Hash, PartialEq, Clone)]
|
||||
pub enum VuFileHandle {
|
||||
Fh(u64)
|
||||
}
|
||||
|
||||
impl VuFileHandle {
|
||||
fn from_fuse_file_info(fi: &fuse_lowlevel::fuse_file_info) -> VuFileHandle {
|
||||
VuFileHandle::Fh(fi.fh)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VuFileHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VuFileHandle::Fh(fh) => writeln!(f, "VuFileHandle({:?})",fh)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VuError {
|
||||
WriteError
|
||||
}
|
||||
|
||||
pub static VUINPUT_STATE: OnceLock<RwLock<HashMap<VuFileHandle, Arc<Mutex<VuInputState>>>>> = OnceLock::new();
|
||||
|
||||
// For log limiting. Idea: Move to log_limit crate
|
||||
pub static DEDUP_LAST_ERROR: OnceLock<Mutex<Option<(u64,VuError)>>> = OnceLock::new();
|
||||
|
||||
|
||||
pub const SYS_INPUT_DIR: &str = "/sys/devices/virtual/input/";
|
||||
pub const BUS_USB: u16 = 0x03;
|
||||
|
||||
pub fn get_vuinput_state(
|
||||
fh:&VuFileHandle,
|
||||
) -> Result<Arc<Mutex<VuInputState>>, String> {
|
||||
let map = VUINPUT_STATE
|
||||
.get()
|
||||
.ok_or("global not initialized".to_string())?;
|
||||
let guard = map.read().map_err(|e| e.to_string())?;
|
||||
guard
|
||||
.get(&fh)
|
||||
.cloned()
|
||||
.ok_or("handle not opened".to_string())
|
||||
}
|
||||
|
||||
|
||||
pub fn insert_vuinput_state(
|
||||
fh:&VuFileHandle,
|
||||
state: VuInputState,
|
||||
) -> Result<(), String> {
|
||||
let map = VUINPUT_STATE
|
||||
.get()
|
||||
.ok_or("global not initialized".to_string())?;
|
||||
let mut guard = map.write().map_err(|e| e.to_string())?;
|
||||
|
||||
if guard.contains_key(&fh) {
|
||||
return Err(format!(
|
||||
"file handle {} already exists. file handles must not be reused!",
|
||||
&fh
|
||||
));
|
||||
}
|
||||
|
||||
let _ = guard.insert(fh.clone(), Arc::new(Mutex::new(state)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_vuinput_state(
|
||||
fh:&VuFileHandle,
|
||||
) -> Result<Arc<Mutex<VuInputState>>, String> {
|
||||
let map = VUINPUT_STATE
|
||||
.get()
|
||||
.ok_or("global not initialized".to_string())?;
|
||||
let mut guard = map.write().map_err(|e| e.to_string())?;
|
||||
let old_value = guard.remove(&fh).ok_or("fh unknown")?;
|
||||
Ok(old_value)
|
||||
}
|
||||
|
||||
pub fn fetch_device_node(path: &str) -> io::Result<String> {
|
||||
for entry in fs::read_dir(path)? {
|
||||
let entry = entry?; // propagate per-entry errors
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
if name.starts_with("event") {
|
||||
return Ok(format!("/dev/input/{}", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no device is found, return an error
|
||||
Err(io::Error::new(ErrorKind::NotFound, "no device found"))
|
||||
}
|
||||
|
||||
/// Returns (major, minor) numbers of a device node at `path`
|
||||
pub fn fetch_major_minor(path: &str) -> io::Result<(u64, u64)> {
|
||||
let metadata = fs::metadata(path)?;
|
||||
|
||||
// Ensure it's a character device
|
||||
if !metadata.file_type().is_char_device() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Not a character device",
|
||||
));
|
||||
}
|
||||
|
||||
let rdev = metadata.rdev();
|
||||
let major = ((rdev >> 8) & 0xfff) as u64;
|
||||
let minor = ((rdev & 0xff) | ((rdev >> 12) & 0xfff00)) as u64;
|
||||
|
||||
Ok((major, minor))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Instance of cuse_lowlevel_ops with all stubs assigned.
|
||||
// Setting to None leads to e.g. "write error: Function not implemented".
|
||||
|
|
|
|||
112
vuinputd/src/cuse_device/state.rs
Normal file
112
vuinputd/src/cuse_device/state.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{File};
|
||||
use std::sync::{Arc, Mutex, OnceLock, RwLock};
|
||||
|
||||
use ::cuse_lowlevel::*;
|
||||
|
||||
use crate::process_tools::RequestingProcess;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VuInputDevice {
|
||||
pub major : u64,
|
||||
pub minor : u64,
|
||||
pub syspath: String,
|
||||
pub devnode: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VuInputState {
|
||||
pub file: File,
|
||||
pub requesting_process: RequestingProcess,
|
||||
pub input_device: Option<VuInputDevice>
|
||||
}
|
||||
|
||||
#[derive(Debug,Eq, Hash, PartialEq, Clone)]
|
||||
pub enum VuFileHandle {
|
||||
Fh(u64)
|
||||
}
|
||||
|
||||
impl VuFileHandle {
|
||||
pub fn from_fuse_file_info(fi: &fuse_lowlevel::fuse_file_info) -> VuFileHandle {
|
||||
VuFileHandle::Fh(fi.fh)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VuFileHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VuFileHandle::Fh(fh) => writeln!(f, "VuFileHandle({:?})",fh)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_vuinput_state(
|
||||
fh:&VuFileHandle,
|
||||
) -> Result<Arc<Mutex<VuInputState>>, String> {
|
||||
let map = VUINPUT_STATE
|
||||
.get()
|
||||
.ok_or("global not initialized".to_string())?;
|
||||
let guard = map.read().map_err(|e| e.to_string())?;
|
||||
guard
|
||||
.get(&fh)
|
||||
.cloned()
|
||||
.ok_or("handle not opened".to_string())
|
||||
}
|
||||
|
||||
|
||||
pub fn insert_vuinput_state(
|
||||
fh:&VuFileHandle,
|
||||
state: VuInputState,
|
||||
) -> Result<(), String> {
|
||||
let map = VUINPUT_STATE
|
||||
.get()
|
||||
.ok_or("global not initialized".to_string())?;
|
||||
let mut guard = map.write().map_err(|e| e.to_string())?;
|
||||
|
||||
if guard.contains_key(&fh) {
|
||||
return Err(format!(
|
||||
"file handle {} already exists. file handles must not be reused!",
|
||||
&fh
|
||||
));
|
||||
}
|
||||
|
||||
let _ = guard.insert(fh.clone(), Arc::new(Mutex::new(state)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_vuinput_state(
|
||||
fh:&VuFileHandle,
|
||||
) -> Result<Arc<Mutex<VuInputState>>, String> {
|
||||
let map = VUINPUT_STATE
|
||||
.get()
|
||||
.ok_or("global not initialized".to_string())?;
|
||||
let mut guard = map.write().map_err(|e| e.to_string())?;
|
||||
let old_value = guard.remove(&fh).ok_or("fh unknown")?;
|
||||
Ok(old_value)
|
||||
}
|
||||
|
||||
pub fn initialize_vuinput_state() {
|
||||
VUINPUT_STATE.set(RwLock::new(HashMap::new())).expect("failed to initialize global state");
|
||||
}
|
||||
|
||||
pub fn initialize_dedup_last_error() {
|
||||
DEDUP_LAST_ERROR.set(Mutex::new(None)).expect("failed to initialize the log deduplication state");
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VuError {
|
||||
WriteError
|
||||
}
|
||||
|
||||
|
||||
pub static VUINPUT_STATE: OnceLock<RwLock<HashMap<VuFileHandle, Arc<Mutex<VuInputState>>>>> = OnceLock::new();
|
||||
|
||||
// For log limiting. Idea: Move to log_limit crate
|
||||
pub static DEDUP_LAST_ERROR: OnceLock<Mutex<Option<(u64,VuError)>>> = OnceLock::new();
|
||||
|
||||
|
|
@ -12,13 +12,15 @@ use std::os::fd::AsRawFd;
|
|||
use std::os::raw::{c_char, c_int, c_uint, c_void};
|
||||
use uinput_ioctls::*;
|
||||
|
||||
use crate::cuse_device::{SYS_INPUT_DIR, VuFileHandle, VuInputDevice, fetch_device_node, get_vuinput_state};
|
||||
use crate::cuse_device::{VuFileHandle, get_vuinput_state};
|
||||
use crate::job_engine::JOB_DISPATCHER;
|
||||
use crate::jobs::inject_in_container_job::InjectInContainerJob;
|
||||
use crate::jobs::remove_from_container_job::RemoveFromContainerJob;
|
||||
use crate::process_tools::SELF_NAMESPACES;
|
||||
use crate::{cuse_device::*, jobs};
|
||||
|
||||
pub const SYS_INPUT_DIR: &str = "/sys/devices/virtual/input/";
|
||||
|
||||
pub unsafe extern "C" fn vuinput_ioctl(
|
||||
_req: fuse_lowlevel::fuse_req_t,
|
||||
_cmd: c_int,
|
||||
|
|
@ -166,7 +168,7 @@ pub unsafe extern "C" fn vuinput_ioctl(
|
|||
debug!("fh {}: devnode: {}", fh, devnode);
|
||||
let (major,minor) = fetch_major_minor(&devnode).unwrap();
|
||||
debug!("fh {}: major: {} minor: {} ", fh, major,minor);
|
||||
vuinput_state.input_device = Some(VuInputDevice {cuse_fh:*fh, major: major, minor: minor, syspath: sysname.clone(), devnode: devnode.clone(), runtime_data: None, netlink_data: None });
|
||||
vuinput_state.input_device = Some(VuInputDevice {major: major, minor: minor, syspath: sysname.clone(), devnode: devnode.clone() });
|
||||
|
||||
// Create device in container, if the request was really from another namespace
|
||||
if ! SELF_NAMESPACES.get().unwrap().equal_mnt_and_net(&vuinput_state.requesting_process.namespaces) {
|
||||
|
|
@ -211,6 +213,7 @@ pub unsafe extern "C" fn vuinput_ioctl(
|
|||
);
|
||||
// replace vendor and product id to the values from sunshine (see inputtino_common.h of sunshine)
|
||||
// The pid is registered for vuinputd, see https://pid.codes/1209/5020/
|
||||
(*setup_ptr).id.bustype = BUS_USB;
|
||||
(*setup_ptr).id.product = 0x5020;
|
||||
(*setup_ptr).id.vendor = 0x1209;
|
||||
ui_dev_setup(fd, setup_ptr).unwrap();
|
||||
|
|
@ -359,4 +362,38 @@ pub unsafe extern "C" fn vuinput_ioctl(
|
|||
fuse_lowlevel::fuse_reply_err(_req, EBADRQC);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn fetch_device_node(path: &str) -> io::Result<String> {
|
||||
for entry in fs::read_dir(path)? {
|
||||
let entry = entry?; // propagate per-entry errors
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
if name.starts_with("event") {
|
||||
return Ok(format!("/dev/input/{}", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no device is found, return an error
|
||||
Err(io::Error::new(ErrorKind::NotFound, "no device found"))
|
||||
}
|
||||
|
||||
/// Returns (major, minor) numbers of a device node at `path`
|
||||
pub fn fetch_major_minor(path: &str) -> io::Result<(u64, u64)> {
|
||||
let metadata = fs::metadata(path)?;
|
||||
|
||||
// Ensure it's a character device
|
||||
if !metadata.file_type().is_char_device() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Not a character device",
|
||||
));
|
||||
}
|
||||
|
||||
let rdev = metadata.rdev();
|
||||
let major = ((rdev >> 8) & 0xfff) as u64;
|
||||
let minor = ((rdev & 0xff) | ((rdev >> 12) & 0xfff00)) as u64;
|
||||
|
||||
Ok((major, minor))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ use libc::{__s32, __u16, input_event};
|
|||
use crate::cuse_device::*;
|
||||
|
||||
|
||||
|
||||
// TODO: compat-mode+ ensure sizeof(struct input_event)
|
||||
pub unsafe extern "C" fn vuinput_write(
|
||||
_req: fuse_lowlevel::fuse_req_t,
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ pub fn remove_input_device(dev_path: String, major: u64, minor: u64) -> Result<(
|
|||
return Err("Device that should be deleted has wrong major and minor".into())
|
||||
}
|
||||
}
|
||||
Err(x) => return Err("Could not execute stat on device file".into())
|
||||
Err(_x) => return Err("Could not execute stat on device file".into())
|
||||
}
|
||||
|
||||
let _ = fs::remove_file(path);
|
||||
|
|
|
|||
|
|
@ -19,16 +19,16 @@
|
|||
|
||||
use ::cuse_lowlevel::*;
|
||||
use log::info;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::{c_char};
|
||||
use std::sync::atomic::{AtomicU64};
|
||||
use std::sync::{Mutex, RwLock};
|
||||
use std::sync::{Mutex};
|
||||
|
||||
pub mod cuse_device;
|
||||
|
||||
use crate::cuse_device::state::{initialize_dedup_last_error, initialize_vuinput_state};
|
||||
use crate::cuse_device::vuinput_open::VUINPUT_COUNTER;
|
||||
use crate::cuse_device::{DEDUP_LAST_ERROR, VUINPUT_STATE, vuinput_make_cuse_ops};
|
||||
use crate::cuse_device::{vuinput_make_cuse_ops};
|
||||
use crate::jobs::monitor_udev_job::MonitorBackgroundLoop;
|
||||
|
||||
pub mod process_tools;
|
||||
|
|
@ -43,15 +43,15 @@ pub mod jobs;
|
|||
fn main() -> std::io::Result<()> {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
|
||||
|
||||
check_permissions().expect("failed to read the capabilities of the vuinputd process");;
|
||||
check_permissions().expect("failed to read the capabilities of the vuinputd process");
|
||||
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
VUINPUT_STATE.set(RwLock::new(HashMap::new())).expect("failed to initialize global state");
|
||||
initialize_vuinput_state();
|
||||
VUINPUT_COUNTER.set(AtomicU64::new(3)).expect("failed to initialize the counter that provides the values of the CUSE file handles"); // 3, because 1 and 2 are usually STDOUT and STDERR
|
||||
JOB_DISPATCHER.set(Mutex::new(Dispatcher::new())).expect("failed to initialize the job dispatcher");
|
||||
SELF_NAMESPACES.set(get_namespace(Pid::SelfPid)).expect("failed to retrieve the namespaces of the vuinputd process");
|
||||
DEDUP_LAST_ERROR.set(Mutex::new(None)).expect("failed to initialize the log deduplication state");
|
||||
initialize_dedup_last_error();
|
||||
JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(MonitorBackgroundLoop::new()));
|
||||
|
||||
info!("Starting vuinputd");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue