Skip to content

Commit d9cad1e

Browse files
committed
fix(Device Hiding): add 'hide_from_root' option to allow moving devnode during hide logic
1 parent c8ff60a commit d9cad1e

File tree

3 files changed

+83
-40
lines changed

3 files changed

+83
-40
lines changed

src/config/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,10 @@ pub struct SourceDevice {
261261
/// will not be hidden or grabbed. Defaults to false.
262262
#[serde(skip_serializing_if = "Option::is_none")]
263263
pub passthrough: Option<bool>,
264+
/// If true, the source device node will be moved to /dev/inputplumber/sources in
265+
/// order to try to prevent elevated processes from reading events from the device.
266+
#[serde(skip_serializing_if = "Option::is_none")]
267+
pub hide_from_root: Option<bool>,
264268
/// Defines which events are included or excluded from input processing by
265269
/// the source device.
266270
#[serde(skip_serializing_if = "Option::is_none")]

src/input/composite_device/mod.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use crate::{
4040
},
4141
target::TargetDeviceTypeId,
4242
},
43-
udev::{hide_device, unhide_device},
43+
udev::{hide_device, unhide_device, HideFlag},
4444
};
4545

4646
use self::{client::CompositeDeviceClient, command::CompositeCommand};
@@ -123,7 +123,7 @@ pub struct CompositeDevice {
123123
source_devices_discovered: Vec<SourceDevice>,
124124
/// Source devices that should be hidden before they are started. This
125125
/// is a list of devnode paths to hide (e.g. ["/dev/input/event10", "/dev/hidraw1"])
126-
source_devices_to_hide: Vec<String>,
126+
source_devices_to_hide: Vec<(String, &'static [HideFlag])>,
127127
/// HashSet of source devices that are blocked from passing their input events to target
128128
/// events.
129129
source_devices_blocked: HashSet<String>,
@@ -650,9 +650,9 @@ impl CompositeDevice {
650650
/// consume.
651651
async fn run_source_devices(&mut self) -> Result<(), Box<dyn Error>> {
652652
// Hide the device if specified
653-
for source_path in self.source_devices_to_hide.drain(..) {
653+
for (source_path, flags) in self.source_devices_to_hide.drain(..) {
654654
log::debug!("Hiding device: {}", source_path);
655-
if let Err(e) = hide_device(source_path.as_str()).await {
655+
if let Err(e) = hide_device(source_path.as_str(), flags).await {
656656
log::warn!("Failed to hide device '{source_path}': {e:?}");
657657
}
658658
log::debug!("Finished hiding device: {source_path}");
@@ -1713,7 +1713,13 @@ impl CompositeDevice {
17131713
!should_passthru && subsystem.as_str() != "iio" && subsystem.as_str() != "tty";
17141714
if should_hide {
17151715
let source_path = device.devnode();
1716-
self.source_devices_to_hide.push(source_path);
1716+
let hide_from_root = source_config.as_ref().and_then(|c| c.hide_from_root);
1717+
let flags: &[HideFlag] = if hide_from_root.unwrap_or_default() {
1718+
&[HideFlag::ChangePermissions, HideFlag::MoveSourceDevice]
1719+
} else {
1720+
&[HideFlag::ChangePermissions]
1721+
};
1722+
self.source_devices_to_hide.push((source_path, flags));
17171723
}
17181724

17191725
match subsystem.as_str() {

src/udev/mod.rs

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@ const RULE_HIDE_DEVICE_EARLY_PRIORITY: &str = "50";
1717
const RULE_HIDE_DEVICE_LATE_PRIORITY: &str = "96";
1818
const RULES_PREFIX: &str = "/run/udev/rules.d";
1919

20+
/// HideFlags can be used to change the behavior of how devices are hidden.
21+
#[derive(Debug, PartialEq, Eq)]
22+
pub enum HideFlag {
23+
ChangePermissions,
24+
MoveSourceDevice,
25+
}
26+
2027
/// Hide the given input device from regular users.
21-
pub async fn hide_device(path: &str) -> Result<(), Box<dyn Error>> {
28+
pub async fn hide_device(path: &str, flags: &[HideFlag]) -> Result<(), Box<dyn Error>> {
2229
// Get the device to hide
2330
let device = get_device(path.to_string()).await?;
2431
let name = device.name.clone();
@@ -30,34 +37,64 @@ pub async fn hide_device(path: &str) -> Result<(), Box<dyn Error>> {
3037
return Err("Unable to create match rule for device".into());
3138
};
3239

33-
// Create the directory to move devnodes to
34-
tokio::fs::create_dir_all("/dev/inputplumber/sources").await?;
40+
// Create the udev rule content to update permissions on the source node.
41+
let mut chmod_early_rule = String::new();
42+
let mut chmod_late_rule = String::new();
43+
if flags.contains(&HideFlag::ChangePermissions) {
44+
// Find the chmod command to use for hiding
45+
let chmod_cmd = if Path::new("/bin/chmod").exists() {
46+
"/bin/chmod".to_string()
47+
} else if Path::new("/usr/bin/chmod").exists() {
48+
"/usr/bin/chmod".to_string()
49+
} else {
50+
let output = Command::new("which").arg("chmod").output().await?;
51+
if !output.status.success() {
52+
return Err("Unable to determine chmod command location".into());
53+
}
54+
str::from_utf8(output.stdout.as_slice())?.trim().to_string()
55+
};
3556

36-
// Find the chmod command to use for hiding
37-
let chmod_cmd = if Path::new("/bin/chmod").exists() {
38-
"/bin/chmod".to_string()
39-
} else if Path::new("/usr/bin/chmod").exists() {
40-
"/usr/bin/chmod".to_string()
41-
} else {
42-
let output = Command::new("which").arg("chmod").output().await?;
43-
if !output.status.success() {
44-
return Err("Unable to determine chmod command location".into());
45-
}
46-
str::from_utf8(output.stdout.as_slice())?.trim().to_string()
47-
};
57+
// Build the rule content
58+
chmod_early_rule = format!(
59+
r#"KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", MODE:="0000", GROUP:="root", RUN+="{chmod_cmd} 000 /dev/input/%k", SYMLINK+="inputplumber/by-hidden/%k"
60+
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", MODE:="0000", GROUP:="root", RUN+="{chmod_cmd} 000 /dev/%k", SYMLINK+="inputplumber/by-hidden/%k"
61+
"#
62+
);
63+
chmod_late_rule = format!(
64+
r#"KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", MODE="000", GROUP="root", TAG-="uaccess", RUN+="{chmod_cmd} 000 /dev/input/%k"
65+
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", MODE="000", GROUP="root", TAG-="uaccess", RUN+="{chmod_cmd} 000 /dev/%k"
66+
"#
67+
);
68+
}
4869

49-
// Find the mv command to use for hiding
50-
let mv_cmd = if Path::new("/bin/mv").exists() {
51-
"/bin/mv".to_string()
52-
} else if Path::new("/usr/bin/mv").exists() {
53-
"/usr/bin/mv".to_string()
54-
} else {
55-
let output = Command::new("which").arg("mv").output().await?;
56-
if !output.status.success() {
57-
return Err("Unable to determine mv command location".into());
58-
}
59-
str::from_utf8(output.stdout.as_slice())?.trim().to_string()
60-
};
70+
// Create the udev rule content to move the device node
71+
let mut mv_early_rule = String::new();
72+
let mut mv_late_rule = String::new();
73+
if flags.contains(&HideFlag::MoveSourceDevice) {
74+
// Create the directory to move devnodes to
75+
tokio::fs::create_dir_all("/dev/inputplumber/sources").await?;
76+
77+
// Find the mv command to use for hiding
78+
let mv_cmd = if Path::new("/bin/mv").exists() {
79+
"/bin/mv".to_string()
80+
} else if Path::new("/usr/bin/mv").exists() {
81+
"/usr/bin/mv".to_string()
82+
} else {
83+
let output = Command::new("which").arg("mv").output().await?;
84+
if !output.status.success() {
85+
return Err("Unable to determine mv command location".into());
86+
}
87+
str::from_utf8(output.stdout.as_slice())?.trim().to_string()
88+
};
89+
90+
// Build the rule content
91+
mv_early_rule = format!(
92+
r#"KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/input/%k /dev/inputplumber/sources/%k"
93+
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/%k /dev/inputplumber/sources/%k"
94+
"#
95+
);
96+
mv_late_rule = mv_early_rule.clone();
97+
}
6198

6299
// Create an early udev rule to hide the device
63100
let rule = format!(
@@ -66,10 +103,8 @@ pub async fn hide_device(path: &str) -> Result<(), Box<dyn Error>> {
66103
{match_rule}, GOTO="inputplumber_valid"
67104
GOTO="inputplumber_end"
68105
LABEL="inputplumber_valid"
69-
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", MODE:="0000", GROUP:="root", RUN+="{chmod_cmd} 000 /dev/input/%k", SYMLINK+="inputplumber/by-hidden/%k"
70-
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", MODE:="0000", GROUP:="root", RUN+="{chmod_cmd} 000 /dev/%k", SYMLINK+="inputplumber/by-hidden/%k"
71-
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/input/%k /dev/inputplumber/sources/%k"
72-
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/%k /dev/inputplumber/sources/%k"
106+
{chmod_early_rule}
107+
{mv_early_rule}
73108
LABEL="inputplumber_end"
74109
"#
75110
);
@@ -87,10 +122,8 @@ LABEL="inputplumber_end"
87122
{match_rule}, GOTO="inputplumber_valid"
88123
GOTO="inputplumber_end"
89124
LABEL="inputplumber_valid"
90-
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", MODE="000", GROUP="root", TAG-="uaccess", RUN+="{chmod_cmd} 000 /dev/input/%k"
91-
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", MODE="000", GROUP="root", TAG-="uaccess", RUN+="{chmod_cmd} 000 /dev/%k"
92-
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/input/%k /dev/inputplumber/sources/%k"
93-
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/%k /dev/inputplumber/sources/%k"
125+
{chmod_late_rule}
126+
{mv_late_rule}
94127
LABEL="inputplumber_end"
95128
"#
96129
);

0 commit comments

Comments
 (0)