Skip to content

Commit b3d0a02

Browse files
committed
Small refactorings and note in SECURITY.md
1 parent 2fe9a70 commit b3d0a02

7 files changed

Lines changed: 174 additions & 147 deletions

File tree

docs/SECURITY.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,15 @@
88

99
## Unsafe code
1010

11-
Known problem: A lot of unsafe code. Open are ideas to mitigate this issue.
11+
Known problem: A lot of unsafe code. Open are ideas to mitigate this issue.
12+
13+
## seccomp, AppArmor, SELinux, cgroups mounts, /sys read-write
14+
15+
This is a big TODO. Which permissions can be reduced. Now we assume we are quite privileagued:
16+
- We have all Linux kernel capabilities,
17+
- The default seccomp profile is disabled,
18+
- The default AppArmor profile is disabled,
19+
- The default SELinux process label is disabled,
20+
- all host devices are accessible,
21+
- /sys is read-write,
22+
- cgroups mount is read-write.

vuinputd/src/cuse_device/mod.rs

Lines changed: 3 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -2,153 +2,21 @@
22
//
33
// Author: Johannes Leupolz <dev@leupolz.eu>
44

5+
pub mod state;
56
pub mod vuinput_ioctl;
67
pub mod vuinput_write;
78
pub mod vuinput_release;
89
pub mod vuinput_open;
910

10-
use std::collections::HashMap;
11-
use std::fs::{self, File};
12-
use std::io;
11+
use std::{fs, io};
1312
use std::os::unix::fs::{FileTypeExt, MetadataExt};
14-
use std::sync::{Arc, Mutex, OnceLock, RwLock};
1513
use std::io::{ErrorKind};
1614

1715
use ::cuse_lowlevel::*;
16+
use state::*;
1817

19-
use crate::process_tools::RequestingProcess;
20-
21-
#[derive(Debug)]
22-
struct VuInputDevice {
23-
cuse_fh : u64,
24-
major : u64,
25-
minor : u64,
26-
syspath: String,
27-
devnode: String,
28-
runtime_data: Option<String>,
29-
netlink_data: Option<String>
30-
}
31-
32-
#[derive(Debug)]
33-
pub struct VuInputState {
34-
file: File,
35-
requesting_process: RequestingProcess,
36-
input_device: Option<VuInputDevice>
37-
}
38-
39-
#[derive(Debug,Eq, Hash, PartialEq, Clone)]
40-
pub enum VuFileHandle {
41-
Fh(u64)
42-
}
43-
44-
impl VuFileHandle {
45-
fn from_fuse_file_info(fi: &fuse_lowlevel::fuse_file_info) -> VuFileHandle {
46-
VuFileHandle::Fh(fi.fh)
47-
}
48-
}
49-
50-
impl std::fmt::Display for VuFileHandle {
51-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52-
match self {
53-
VuFileHandle::Fh(fh) => writeln!(f, "VuFileHandle({:?})",fh)?,
54-
}
55-
Ok(())
56-
}
57-
}
58-
59-
#[derive(Debug)]
60-
pub enum VuError {
61-
WriteError
62-
}
63-
64-
pub static VUINPUT_STATE: OnceLock<RwLock<HashMap<VuFileHandle, Arc<Mutex<VuInputState>>>>> = OnceLock::new();
65-
66-
// For log limiting. Idea: Move to log_limit crate
67-
pub static DEDUP_LAST_ERROR: OnceLock<Mutex<Option<(u64,VuError)>>> = OnceLock::new();
68-
69-
70-
pub const SYS_INPUT_DIR: &str = "/sys/devices/virtual/input/";
7118
pub const BUS_USB: u16 = 0x03;
7219

73-
pub fn get_vuinput_state(
74-
fh:&VuFileHandle,
75-
) -> Result<Arc<Mutex<VuInputState>>, String> {
76-
let map = VUINPUT_STATE
77-
.get()
78-
.ok_or("global not initialized".to_string())?;
79-
let guard = map.read().map_err(|e| e.to_string())?;
80-
guard
81-
.get(&fh)
82-
.cloned()
83-
.ok_or("handle not opened".to_string())
84-
}
85-
86-
87-
pub fn insert_vuinput_state(
88-
fh:&VuFileHandle,
89-
state: VuInputState,
90-
) -> Result<(), String> {
91-
let map = VUINPUT_STATE
92-
.get()
93-
.ok_or("global not initialized".to_string())?;
94-
let mut guard = map.write().map_err(|e| e.to_string())?;
95-
96-
if guard.contains_key(&fh) {
97-
return Err(format!(
98-
"file handle {} already exists. file handles must not be reused!",
99-
&fh
100-
));
101-
}
102-
103-
let _ = guard.insert(fh.clone(), Arc::new(Mutex::new(state)));
104-
Ok(())
105-
}
106-
107-
pub fn remove_vuinput_state(
108-
fh:&VuFileHandle,
109-
) -> Result<Arc<Mutex<VuInputState>>, String> {
110-
let map = VUINPUT_STATE
111-
.get()
112-
.ok_or("global not initialized".to_string())?;
113-
let mut guard = map.write().map_err(|e| e.to_string())?;
114-
let old_value = guard.remove(&fh).ok_or("fh unknown")?;
115-
Ok(old_value)
116-
}
117-
118-
pub fn fetch_device_node(path: &str) -> io::Result<String> {
119-
for entry in fs::read_dir(path)? {
120-
let entry = entry?; // propagate per-entry errors
121-
if let Some(name) = entry.file_name().to_str() {
122-
if name.starts_with("event") {
123-
return Ok(format!("/dev/input/{}", name));
124-
}
125-
}
126-
}
127-
// If no device is found, return an error
128-
Err(io::Error::new(ErrorKind::NotFound, "no device found"))
129-
}
130-
131-
/// Returns (major, minor) numbers of a device node at `path`
132-
pub fn fetch_major_minor(path: &str) -> io::Result<(u64, u64)> {
133-
let metadata = fs::metadata(path)?;
134-
135-
// Ensure it's a character device
136-
if !metadata.file_type().is_char_device() {
137-
return Err(io::Error::new(
138-
io::ErrorKind::InvalidInput,
139-
"Not a character device",
140-
));
141-
}
142-
143-
let rdev = metadata.rdev();
144-
let major = ((rdev >> 8) & 0xfff) as u64;
145-
let minor = ((rdev & 0xff) | ((rdev >> 12) & 0xfff00)) as u64;
146-
147-
Ok((major, minor))
148-
}
149-
150-
151-
15220

15321
// Instance of cuse_lowlevel_ops with all stubs assigned.
15422
// Setting to None leads to e.g. "write error: Function not implemented".

vuinputd/src/cuse_device/state.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// SPDX-License-Identifier: MIT
2+
//
3+
// Author: Johannes Leupolz <dev@leupolz.eu>
4+
5+
use std::collections::HashMap;
6+
use std::fs::{File};
7+
use std::sync::{Arc, Mutex, OnceLock, RwLock};
8+
9+
use ::cuse_lowlevel::*;
10+
11+
use crate::process_tools::RequestingProcess;
12+
13+
#[derive(Debug)]
14+
pub struct VuInputDevice {
15+
pub major : u64,
16+
pub minor : u64,
17+
pub syspath: String,
18+
pub devnode: String,
19+
}
20+
21+
#[derive(Debug)]
22+
pub struct VuInputState {
23+
pub file: File,
24+
pub requesting_process: RequestingProcess,
25+
pub input_device: Option<VuInputDevice>
26+
}
27+
28+
#[derive(Debug,Eq, Hash, PartialEq, Clone)]
29+
pub enum VuFileHandle {
30+
Fh(u64)
31+
}
32+
33+
impl VuFileHandle {
34+
pub fn from_fuse_file_info(fi: &fuse_lowlevel::fuse_file_info) -> VuFileHandle {
35+
VuFileHandle::Fh(fi.fh)
36+
}
37+
}
38+
39+
impl std::fmt::Display for VuFileHandle {
40+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41+
match self {
42+
VuFileHandle::Fh(fh) => writeln!(f, "VuFileHandle({:?})",fh)?,
43+
}
44+
Ok(())
45+
}
46+
}
47+
48+
pub fn get_vuinput_state(
49+
fh:&VuFileHandle,
50+
) -> Result<Arc<Mutex<VuInputState>>, String> {
51+
let map = VUINPUT_STATE
52+
.get()
53+
.ok_or("global not initialized".to_string())?;
54+
let guard = map.read().map_err(|e| e.to_string())?;
55+
guard
56+
.get(&fh)
57+
.cloned()
58+
.ok_or("handle not opened".to_string())
59+
}
60+
61+
62+
pub fn insert_vuinput_state(
63+
fh:&VuFileHandle,
64+
state: VuInputState,
65+
) -> Result<(), String> {
66+
let map = VUINPUT_STATE
67+
.get()
68+
.ok_or("global not initialized".to_string())?;
69+
let mut guard = map.write().map_err(|e| e.to_string())?;
70+
71+
if guard.contains_key(&fh) {
72+
return Err(format!(
73+
"file handle {} already exists. file handles must not be reused!",
74+
&fh
75+
));
76+
}
77+
78+
let _ = guard.insert(fh.clone(), Arc::new(Mutex::new(state)));
79+
Ok(())
80+
}
81+
82+
pub fn remove_vuinput_state(
83+
fh:&VuFileHandle,
84+
) -> Result<Arc<Mutex<VuInputState>>, String> {
85+
let map = VUINPUT_STATE
86+
.get()
87+
.ok_or("global not initialized".to_string())?;
88+
let mut guard = map.write().map_err(|e| e.to_string())?;
89+
let old_value = guard.remove(&fh).ok_or("fh unknown")?;
90+
Ok(old_value)
91+
}
92+
93+
pub fn initialize_vuinput_state() {
94+
VUINPUT_STATE.set(RwLock::new(HashMap::new())).expect("failed to initialize global state");
95+
}
96+
97+
pub fn initialize_dedup_last_error() {
98+
DEDUP_LAST_ERROR.set(Mutex::new(None)).expect("failed to initialize the log deduplication state");
99+
}
100+
101+
102+
#[derive(Debug)]
103+
pub enum VuError {
104+
WriteError
105+
}
106+
107+
108+
pub static VUINPUT_STATE: OnceLock<RwLock<HashMap<VuFileHandle, Arc<Mutex<VuInputState>>>>> = OnceLock::new();
109+
110+
// For log limiting. Idea: Move to log_limit crate
111+
pub static DEDUP_LAST_ERROR: OnceLock<Mutex<Option<(u64,VuError)>>> = OnceLock::new();
112+

vuinputd/src/cuse_device/vuinput_ioctl.rs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ use std::os::fd::AsRawFd;
1212
use std::os::raw::{c_char, c_int, c_uint, c_void};
1313
use uinput_ioctls::*;
1414

15-
use crate::cuse_device::{SYS_INPUT_DIR, VuFileHandle, VuInputDevice, fetch_device_node, get_vuinput_state};
15+
use crate::cuse_device::{VuFileHandle, get_vuinput_state};
1616
use crate::job_engine::JOB_DISPATCHER;
1717
use crate::jobs::inject_in_container_job::InjectInContainerJob;
1818
use crate::jobs::remove_from_container_job::RemoveFromContainerJob;
1919
use crate::process_tools::SELF_NAMESPACES;
2020
use crate::{cuse_device::*, jobs};
2121

22+
pub const SYS_INPUT_DIR: &str = "/sys/devices/virtual/input/";
23+
2224
pub unsafe extern "C" fn vuinput_ioctl(
2325
_req: fuse_lowlevel::fuse_req_t,
2426
_cmd: c_int,
@@ -166,7 +168,7 @@ pub unsafe extern "C" fn vuinput_ioctl(
166168
debug!("fh {}: devnode: {}", fh, devnode);
167169
let (major,minor) = fetch_major_minor(&devnode).unwrap();
168170
debug!("fh {}: major: {} minor: {} ", fh, major,minor);
169-
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 });
171+
vuinput_state.input_device = Some(VuInputDevice {major: major, minor: minor, syspath: sysname.clone(), devnode: devnode.clone() });
170172

171173
// Create device in container, if the request was really from another namespace
172174
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(
211213
);
212214
// replace vendor and product id to the values from sunshine (see inputtino_common.h of sunshine)
213215
// The pid is registered for vuinputd, see https://pid.codes/1209/5020/
216+
(*setup_ptr).id.bustype = BUS_USB;
214217
(*setup_ptr).id.product = 0x5020;
215218
(*setup_ptr).id.vendor = 0x1209;
216219
ui_dev_setup(fd, setup_ptr).unwrap();
@@ -359,4 +362,38 @@ pub unsafe extern "C" fn vuinput_ioctl(
359362
fuse_lowlevel::fuse_reply_err(_req, EBADRQC);
360363
}
361364
}
362-
}
365+
}
366+
367+
368+
pub fn fetch_device_node(path: &str) -> io::Result<String> {
369+
for entry in fs::read_dir(path)? {
370+
let entry = entry?; // propagate per-entry errors
371+
if let Some(name) = entry.file_name().to_str() {
372+
if name.starts_with("event") {
373+
return Ok(format!("/dev/input/{}", name));
374+
}
375+
}
376+
}
377+
// If no device is found, return an error
378+
Err(io::Error::new(ErrorKind::NotFound, "no device found"))
379+
}
380+
381+
/// Returns (major, minor) numbers of a device node at `path`
382+
pub fn fetch_major_minor(path: &str) -> io::Result<(u64, u64)> {
383+
let metadata = fs::metadata(path)?;
384+
385+
// Ensure it's a character device
386+
if !metadata.file_type().is_char_device() {
387+
return Err(io::Error::new(
388+
io::ErrorKind::InvalidInput,
389+
"Not a character device",
390+
));
391+
}
392+
393+
let rdev = metadata.rdev();
394+
let major = ((rdev >> 8) & 0xfff) as u64;
395+
let minor = ((rdev & 0xff) | ((rdev >> 12) & 0xfff00)) as u64;
396+
397+
Ok((major, minor))
398+
}
399+

vuinputd/src/cuse_device/vuinput_write.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use libc::{__s32, __u16, input_event};
1414
use crate::cuse_device::*;
1515

1616

17-
1817
// TODO: compat-mode+ ensure sizeof(struct input_event)
1918
pub unsafe extern "C" fn vuinput_write(
2019
_req: fuse_lowlevel::fuse_req_t,

vuinputd/src/jobs/mknod_input_device.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub fn remove_input_device(dev_path: String, major: u64, minor: u64) -> Result<(
8787
return Err("Device that should be deleted has wrong major and minor".into())
8888
}
8989
}
90-
Err(x) => return Err("Could not execute stat on device file".into())
90+
Err(_x) => return Err("Could not execute stat on device file".into())
9191
}
9292

9393
let _ = fs::remove_file(path);

0 commit comments

Comments
 (0)